Mysql在非常大的表上计算性能

hot*_*ips 36 mysql sql count query-optimization database-performance

我在Innodb有一张超过1亿行的表.

我必须知道外键是否超过5000行= 1.我不需要确切的数字.

我做了一些测试:

SELECT COUNT(*) FROM table WHERE fk = 1=> 16秒
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000=> 16秒
SELECT primary FROM table WHERE fk = 1=> 0.6秒

我将拥有更大的网络和治疗时间,但它可能是15.4秒的超载!

你有更好的主意吗?

谢谢

编辑:[添加了OP的相关评论]

我尝试了SELECT SQL_NO_CACHE COUNT(fk)FROM表WHERE fk = 1但是耗时25秒

使用Mysql Tuner调整了Mysod的Innodb.

CREATE TABLE table ( pk bigint(20) NOT NULL AUTO_INCREMENT,
fk tinyint(3) unsigned DEFAULT '0', 
PRIMARY KEY (pk), KEY idx_fk (fk) USING BTREE ) 
ENGINE=InnoDB AUTO_INCREMENT=100380914 DEFAULT CHARSET=latin1
Run Code Online (Sandbox Code Playgroud)

DB Stuff:

'have_innodb', 'YES' 'ignore_builtin_innodb', 'OFF' 'innodb_adaptive_hash_index', 'ON'    
'innodb_additional_mem_pool_size', '20971520' 'innodb_autoextend_increment', '8' 
'innodb_autoinc_lock_mode', '1' 'innodb_buffer_pool_size', '25769803776' 
'innodb_checksums', 'ON' 'innodb_commit_concurrency', '0',
'innodb_concurrency_tickets', '500' 'innodb_data_file_path',
'ibdata1:10M:autoextend' 'innodb_data_home_dir', '', 'innodb_doublewrite', 'ON'     
'innodb_fast_shutdown', '1' 'innodb_file_io_threads', '4' 
'innodb_file_per_table', 'OFF', 'innodb_flush_log_at_trx_commit', '1' 
'innodb_flush_method', '' 'innodb_force_recovery', '0' 'innodb_lock_wait_timeout', '50' 
'innodb_locks_unsafe_for_binlog', 'OFF' 'innodb_log_buffer_size', '8388608' 
'innodb_log_file_size', '26214400' 'innodb_log_files_in_group', '2' 
'innodb_log_group_home_dir', './' 'innodb_max_dirty_pages_pct', '90'     
'innodb_max_purge_lag', '0' 'innodb_mirrored_log_groups', '1' 'innodb_open_files', 
'300' 'innodb_rollback_on_timeout', 'OFF' 'innodb_stats_on_metadata', 'ON' 
'innodb_support_xa', 'ON' 'innodb_sync_spin_loops', '20' 'innodb_table_locks', 'ON' 
'innodb_thread_concurrency', '8' 'innodb_thread_sleep_delay', '10000'      
'innodb_use_legacy_cardinality_algorithm', 'ON'
Run Code Online (Sandbox Code Playgroud)

更新'15: 到目前为止,我使用了相同的方法,每天有6亿行和64万个新行.它仍然工作正常.

Sal*_*n A 20

您似乎对实际计数不感兴趣,请尝试一下:

SELECT 1 FROM table WHERE fk = 1 LIMIT 5000, 1
Run Code Online (Sandbox Code Playgroud)

如果返回一行,则您有5000条以上的记录.我认为该fk列已编入索引.

  • @ypercube:我检查了 3M 行的虚拟数据,fk 上没有索引,并且始终在 < 1s 内得到结果(第一次运行是 ~3s)。这个查询在很大程度上取决于数据的分布,所以 YMMV。 (2认同)

scr*_*tin 20

计数器表或其他缓存机制是解决方案:

InnoDB不保留表中的内部行数,因为并发事务可能同时"看到"不同数量的行.为了处理SELECT COUNT(*)FROM t语句,InnoDB扫描表的索引,如果索引不完全在缓冲池中,则需要一些时间.如果您的表不经常更改,使用MySQL查询缓存是一个很好的解决方案.要快速计数,您必须使用自己创建的计数器表,并让应用程序根据插入和删除更新它.如果大概行数足够,可以使用SHOW TABLE STATUS.请参见第14.3.14.1节"InnoDB性能调整技巧".


Ric*_*mes 7

我要添加另一个答案-到目前为止,我对注释和答案有很多更正/补充。

对于MyISAM而言,SELECT COUNT(*)没有任何问题WHERE-很快。所有其他情况(包括Question中的InnoDB)都必须遍历数据的BTree或索引的BTree才能得到答案。因此,我们需要计算多少。

InnoDB缓存数据和索引块(每个16KB)。但是,当表的数据或索引BTree大于时innodb_buffer_pool_size,可以保证您可以访问磁盘。击打磁盘几乎总是所有SQL中最慢的部分。

当涉及到查询高速缓存时,通常导致大约1毫秒的查询时间。引用的任何时间似乎都不是问题。因此,我不再赘述。

但是... 连续两次运行相同的查询通常会显示:

  • 第一次跑步:10秒
  • 第二轮:1秒

这是第一次运行必须从磁盘中获取大多数块的征兆,而第二次运行则必须在RAM(buffer_pool)中找到所有块。我怀疑列出的某些时间是虚假的,因为未意识到缓存问题。(16秒VS 0.6秒可以由此进行说明。)

我将竖琴“磁盘命中”或“需要触摸的块”,因为它是SQL更快的真实指标。

COUNT(x)检查xIS NOT NULL清点之前。这会增加少量的处理,但不会更改磁盘命中数。

提供的表具有PK和第二列。我想知道那是不是真正的桌子?有所不同-

  • 如果优化器决定读取数据(即按PRIMARY KEY顺序扫描),它将读取数据BTree,该数据通常比二级索引BTree宽(但在本示例中不是这样)。
  • 如果优化器决定读取二级索引(但不需要进行排序),则需要触摸的块将更少。因此,更快。

对原始查询的评论:

SELECT COUNT(*) FROM table WHERE fk = 1 => 16 seconds
    -- INDEX(fk) is optimal, but see below
SELECT COUNT(*) FROM table WHERE fk = 1 LIMIT 5000 => 16 seconds
    -- the LIMIT does nothing, since there is only one row in the result
SELECT primary FROM table WHERE fk = 1 => 0.6 seconds
    -- Again INDEX(fk), but see below
Run Code Online (Sandbox Code Playgroud)

WHERE fk = 1乞求INDEX(fk, ...),最好只是INDEX(fk)。请注意,在InnoDB中,每个二级索引都包含pk的副本。也就是说,INDEX(fk)有效INDEX(fk, primary)。因此,第三查询可以将其用作“覆盖”,而无需触摸数据。

如果表是真正公正的两列,然后可能辅助索引B树会胖比数据B树。但是在实际表中,二级索引会更小。因此,索引扫描将比表扫描更快(要触摸的块更少)。

第三个查询也提供了很大的结果集;这可能会导致查询花费很长时间- 不会包含在加引号的“时间”中;它是网络时间,而不是查询时间。

innodb_buffer_pool_size = 25,769,803,776 我猜该表及其二级索引(来自FK)分别约为3-4GB。因此,任何时间可能首先必须加载很多东西。然后第二轮将被完全缓存。(当然,我不知道有多少行fk=1;大概少于所有行?)

但是 ...在600M行中,表及其索引各自接近25GB的buffer_pool。因此,可能很快就会变成受I / O约束的一天-这将使您希望回到16(或25)秒。但是您将无法。然后,我们可以讨论替代方案COUNT

SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000,1-让我们分析一下。它将扫描索引,但将在5000行之后停止。您所需要的只是“超过5K”,这是获得它的最佳方法。无论表中的行总数如何,它都将始终保持快速(仅触及十几个块)的速度。(它仍然受系统的buffer_pool_size和缓存特性的影响。但是,即使使用冷缓存,十几个块也要花费不到一秒钟的时间。)

MariaDB LIMIT ROWS_EXAMINED可能值得研究。没有那个,你可以做

SELECT COUNT(*) AS count_if_less_than_5K
    FROM ( SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000 );
Run Code Online (Sandbox Code Playgroud)

可能比将行交付给客户端更快。它将不得不内部收集tmp表中的行,但仅交付COUNT

附带说明:每天插入640K行- INSERTs使用当前在HDD(而非SDD)上的设置,这接近MySQL中单行的限制。如果您需要讨论潜在的灾难,请打开另一个问题。

底线:

  • 确保避免查询缓存。(通过使用SQL_NO_CACHE或关闭质量控制)
  • 运行任何计时查询两次;使用第二次。
  • 了解所涉及的BTree的结构和大小。
  • COUNT(x)除非需要空检查,否则不要使用。
  • 不要使用PHP的mysql_*界面;切换到mysqli_*PDO


hot*_*ips 0

最后,最快的方法是使用 C# 查询前 X 行并计算行数。

我的应用程序正在批量处理数据。两批之间的时间长度取决于需要处理的行数

SELECT pk FROM table WHERE fk = 1 LIMIT X
Run Code Online (Sandbox Code Playgroud)

我在 0.9 秒内得到了结果。

感谢大家的想法!

  • 我不明白你是如何计算行数的。介意添加该代码吗? (3认同)