哪个最快?从“表”中选择SELECT SQL_CALC_FOUND_ROWS,或选择COUNT(*)


179

当您限制通常用于分页的SQL查询返回的行数时,有两种方法可以确定记录总数:

方法1

SQL_CALC_FOUND_ROWS选项包括在原始选项中SELECT,然后通过运行获取总行数SELECT FOUND_ROWS()

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

方法2

正常运行查询,然后通过运行获取总行数 SELECT COUNT(*)

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

哪种方法最好/最快?

Answers:


121

这取决于。看到这个问题的MySQL性能博客文章:SQL_CALC_FOUND_ROWS还是不要SQL_CALC_FOUND_ROWS

简要介绍一下:Peter说,这取决于您的索引和其他因素。帖子中的许多评论似乎都说,SQL_CALC_FOUND_ROWS运行两个查询几乎总是慢一些,有时甚至慢10倍。


28
我可以确认这一点-我刚刚在168,000行数据库中更新了带有4个联接的查询。仅选择前100行,SQL_CALC_FOUND_ROWS耗时超过20秒;使用单独的COUNT(*)查询花费了不到5秒的时间(对于count + results查询)。
山姆·杜菲

11
非常有趣的发现。由于MySQL的文档明确建议这样SQL_CALC_FOUND_ROWS做会更快,所以我想知道在什么情况下(如果有)它实际上更快!
svidgen

13
旧话题,但对于那些仍然很有趣的人!刚刚完成了对10个检查的INNODB检查,我可以确定它是9.2(1个查询)为26(2个查询)SELECT SQL_CALC_FOUND_ROWS tblA。*,tblB.id AS'b_id',tblB.city AS'b_city',tblC.id AS 'C_ID',tblC.type AS 'c_type',tblD.id AS 'D_ID',tblD.extype AS 'd_extype',tblY.id AS 'y_id',tblY.ydt AS y_ydt FROM tblAtblBtblCtblDtblY WHERE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
我只是运行了这个实验,SQLC_CALC_FOUND_ROWS比两个查询快得多。现在我的主表只有65k,有两个数百个联接,但是无论是否使用SQLC_CALC_FOUND_ROWS,主查询都需要0.18秒,但是当我使用COUNT(id)运行第二个查询时,它的花费仅为0.25。
transilvlad14年

3
除了可能的性能问题外,请考虑FOUND_ROWS()在MySQL 8.0.17中已弃用该功能。另请参阅@ madhur-bhaiya的答案。
arueckauer

21

选择“最佳”方法时,比速度更重要的考虑因素可能是代码的可维护性和正确性。如果是这样,则最好使用SQL_CALC_FOUND_ROWS,因为您只需要维护一个查询。使用单个查询完全可以避免主查询和计数查询之间存在细微差别的可能性,这可能会导致COUNT错误。


11
这取决于您的设置。如果您使用某种ORM或查询构建器,则对两个查询使用相同的where条件非常容易,将select字段交换为一个计数,然后删除限制。您永远不应两次写下标准。
mpen 2014年

我要指出的是,我宁愿使用两个简单而相当标准,易于理解的SQL查询来维护代码,而不是使用专有的MySQL功能来维护代码-值得注意的是,较新的MySQL版本已弃用该查询。
thomasrutter

19

MySQL从SQL_CALC_FOUND_ROWS8.0.17版本开始就开始弃用功能。

因此,它总是优先考虑与执行查询LIMIT,然后用第二个查询COUNT(*),并没有LIMIT确定是否有其他行。

文档

从MySQL 8.0.17开始,SQL_CALC_FOUND_ROWS查询修饰符和随附的FOUND_ROWS()函数已弃用,并将在以后的MySQL版本中删除。

COUNT(*)受到某些优化。SQL_CALC_FOUND_ROWS导致一些优化被禁用。

使用以下查询:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

此外,SQL_CALC_FOUND_ROWSMySQL WL#12615中所述,通常会遇到更多问题:

SQL_CALC_FOUND_ROWS有很多问题。首先,它很慢。通常,使用LIMIT运行查询,然后针对同一查询使用单独的SELECT COUNT(运行会更便宜,因为COUNT()可以利用搜索整个结果集(例如文件排序)时无法完成的优化可以跳过COUNT(*),而对于CALC_FOUND_ROWS,我们必须禁用一些文件排序优化以保证正确的结果)

更重要的是,它在许多情况下具有非常不清楚的语义。特别是,当一个查询具有多个查询块(例如,使用UNION)时,根本无法在生成有效查询的同时计算“必须拥有”的行数。随着迭代器执行器朝着此类查询的方向发展,实际上很难尝试保留相同的语义。此外,如果查询中有多个LIMIT(例如,对于派生表),则不一定清楚SQL_CALC_FOUND_ROWS应该指向哪个。因此,与以前相比,这种非平凡的查询在迭代器执行器中必然会获得不同的语义。

最后,大多数使用SQL_CALC_FOUND_ROWS似乎有用的用例应该只通过除LIMIT / OFFSET之外的其他机制来解决。例如,电话簿应按字母(在UX和索引使用方面)而不是记录号进行分页。讨论越来越按日期(再次允许使用索引)进行无限滚动,而不是按帖子编号分页。等等。


如何执行这两个选择作为原子操作?如果有人在SELECT COUNT(*)查询之前插入一行怎么办?谢谢。
Dom,

@Dom(如果您拥有MySQL8 +),则可以使用Window函数在单个查询中运行两个查询;但这不是最佳解决方案,因为索引无法正确使用。另一个选择是用LOCK TABLES <tablename>和将这两个查询括起来UNLOCK TABLES。第三种选择(最好的恕我直言)是重新考虑分页。请阅读:mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya,


9

恕我直言,为什么2查询的原因

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

比使用SQL_CALC_FOUND_ROWS更快

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

必须视为一个特殊情况。

实际上,与隐式一个等效于ORDER + LIMIT的选择性相比,它取决于WHERE子句的选择性。

正如Arvids在评论中所述(http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394),无论是否使用EXPLAIN,临时表,应该是了解SCFR是否会更快的良好基础。

但是,正如我所添加的(http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482)一样,结果确实取决于情况。对于特定的分页器,您可以得出以下结论:“对于前三页,使用2个查询;对于以下页面,请使用SCFR”!


6

删除一些不必要的SQL,然后COUNT(*)比删除速度更快SQL_CALC_FOUND_ROWS。例:

SELECT Person.Id, Person.Name, Job.Description, Card.Number
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
LEFT JOIN Card ON Card.Person_Id = Person.Id
WHERE Job.Name = 'WEB Developer'
ORDER BY Person.Name

然后算上没有不必要的部分:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'

3

还有其他基准可供选择:

1.)窗口函数将直接返回实际大小(在MariaDB中测试):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2.)开箱即用,大多数时候用户不需要知道表的确切大小,通常一个近似值就足够了。

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.