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

时间:2012-06-11 07:59:24

标签: 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为Innodb调整了Mysql。

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

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'

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

7 个答案:

答案 0 :(得分:19)

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

  

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

答案 1 :(得分:18)

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

SELECT 1 FROM table WHERE fk = 1 LIMIT 5000, 1

如果返回一行,则您有5000条以上的记录。我假设fk列已编入索引。

答案 2 :(得分:6)

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

对于MyISAM,没有SELECT COUNT(*)的{​​{1}}被认为是非常快的。所有其他情况(包括问题中的InnoDB)必须通过数据的BTree或索引的BTree来获得答案。所以我们需要知道要计算多少。

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

查询缓存在涉及时通常会导致查询时间约为1毫秒;对于引用的任何时间而言,这似乎不是问题。所以我不会纠结于此。

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

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

这是第一次运行必须从磁盘获取大部分块的症状,而第二次运行在RAM(buffer_pool)中找到它。我怀疑列出的一些时间是假的,因为没有意识到这个缓存问题。 (16秒vs 0.6秒可以解释。)

我会竖起来"磁盘命中"或者"需要触摸的块#34;作为SQL更快的真实指标。

在计算之前,

innodb_buffer_pool_size会检查COUNT(x) x。这会增加少量处理,但不会改变磁盘命中数。

提供的表有PK和第二列。我想知道那是真正的表吗?它有所作为 -

  • 如果优化程序决定读取数据 - 即以IS NOT NULL顺序扫描 - 它将读取数据BTree,通常 (但不是在这个蹩脚的例子中)比二级指数BTree宽得多。
  • 如果优化器决定读取辅助索引(但不需要进行排序),则触摸的块将会减少。因此,更快。

对原始查询的评论:

PRIMARY KEY

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 请求WHERE fk = 1,最好是INDEX(fk, ...)。请注意,在InnoDB中,每个二级索引都包含pk的副本。也就是说,INDEX(fk)实际上是INDEX(fk)。因此,第三个查询可以使用它作为"覆盖"而不需要触摸数据。

如果表格确实只是两列,那么可能二级索引BTree将比数据BTree更胖。但在实际表中,二级索引会更小。因此,索引扫描比表扫描更快(触摸的块更少)。

第三个查询也提供了一个大型结果集;这可能会导致查询花费很长时间 - 它不会被包含在引用的" time&#34 ;;它是网络时间,而不是查询时间。

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

但是 ...在600M行中,表及其索引每个接近25GB buffer_pool。所以,很快它就会变成I / O界限 - 这将使你希望回到16(或25)秒;然而你却无法做到。然后我们可以讨论做fk=1的替代方法。

COUNT - 让我们分析一下。它将扫描索引,但它将在5000行后停止。你所需要的只是"超过5K",这是获得它的最佳方式。无论表中的总行数如何,它都将一直很快(仅触摸十几个块)。 (它仍然受制于系统的buffer_pool_size和缓存特性。但是即使使用冷缓存,十几个块也需要不到一秒的时间。)

MariaDB' LIMIT ROWS_EXAMINED可能值得研究。没有它,你可以做到

SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000,1

可能比将行传递给客户端更快;它必须在tmp表中内部收集行,但只提供SELECT COUNT(*) AS count_if_less_than_5K FROM ( SELECT 1 FROM tbl WHERE fk = 1 LIMIT 5000 );

附注:每天插入640K行 - 这接近MySQL中单行COUNT的限制,并且当前设置在HDD(不是SDD)上。如果您需要讨论潜在的灾难,请打开另一个问题。

底线:

  • 请务必避免使用查询缓存。 (通过使用INSERTs或关闭QC)
  • 两次运行任何计时查询;第二次使用。
  • 了解所涉及的BTree的结构和大小。
  • 除非您需要空检查,否则不要使用SQL_NO_CACHE
  • 不要使用PHP的COUNT(x)接口;切换到mysql_*mysqli_*

答案 3 :(得分:1)

如果你使用的是PHP,你可以对mysql_num_rows得到的结果SELECT primary FROM table WHERE fk = 1 => 0.6 seconds,我认为这会很有效。

但取决于您使用的服务器端语言

答案 4 :(得分:1)

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

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

SELECT pk FROM table WHERE fk = 1 LIMIT X

我在0.9秒内得到了结果。

感谢所有人的想法!

答案 5 :(得分:0)

如果您不想知道行数,并且只想针对某个值测试COUNT,则可以使用下面的标准脚本:

SELECT 'X'
FROM mytable
WHERE myfield='A'
HAVING COUNT(*) >5

这将返回一行或根本不返回任何行,具体取决于是否满足条件。

此脚本符合ANSI标准,可以在不评估COUNT(*)的完整值的情况下完全运行。如果MySQL实现优化以在满足某些条件后停止评估行(我真的希望它确实如此),那么您将获得性能改进。不幸的是我自己无法测试这种行为,因为我没有可用的大型MySQL数据库。如果您进行此测试,请在此处分享结果:)

答案 6 :(得分:0)

这是一个老问题,但我遇到了同样的问题,也许这会对某人有所帮助:有 400 万条记录,COUNT 查询需要超过 20 秒。 所以,在我的例子中,在我添加了一个简单的主键过滤后,它变得更快了,只需要 4 秒。 所以最后的查询是:

SELECT COUNT(*) FROM Table
WHERE PK > 0;

就我而言,PK 是 INT。