我在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个新行。它仍然正常工作。
答案 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毫秒;对于引用的任何时间而言,这似乎不是问题。所以我不会纠结于此。
但是......连续两次运行相同的查询通常会显示:
这是第一次运行必须从磁盘获取大部分块的症状,而第二次运行在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)SQL_NO_CACHE
。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。