哪个最快? SELECT SQL_CALC_FOUND_ROWS FROM `table`,或 SELECT COUNT(*)
Posted
技术标签:
【中文标题】哪个最快? SELECT SQL_CALC_FOUND_ROWS FROM `table`,或 SELECT COUNT(*)【英文标题】:Which is fastest? SELECT SQL_CALC_FOUND_ROWS FROM `table`, or SELECT COUNT(*) 【发布时间】:2010-09-16 05:46:47 【问题描述】:当你限制一个SQL查询返回的行数时,通常用于分页,有两种方法来确定总记录数:
方法一
在原来的SELECT
中加入SQL_CALC_FOUND_ROWS
选项,然后运行SELECT FOUND_ROWS()
得到总行数:
SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();
方法二
正常运行查询,然后运行SELECT COUNT(*)
得到总行数
SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;
哪种方法最好/最快?
【问题讨论】:
【参考方案1】:这取决于。请参阅有关此主题的 mysql 性能博客文章:To SQL_CALC_FOUND_ROWS
or not to SQL_CALC_FOUND_ROWS
?
简单总结一下:Peter 说这取决于您的索引和其他因素。帖子中的许多 cmets 似乎都说 SQL_CALC_FOUND_ROWS
几乎总是比运行两个查询慢 - 有时慢 10 倍。
【讨论】:
我可以确认这一点 - 我刚刚在 168,000 行数据库上更新了一个包含 4 个连接的查询。仅选择带有SQL_CALC_FOUND_ROWS
的前 100 行花费了 20 多秒;使用单独的 COUNT(*)
查询花费了不到 5 秒的时间(对于计数 + 结果查询)。
非常有趣的发现。由于MySQL's documentation 明确表明SQL_CALC_FOUND_ROWS
会更快,我想知道在什么情况下(如果有的话)它实际上 更快!
老话题,但对于那些仍然有趣的人来说!刚刚从 10 次检查中完成了对 INNODB 的检查,我可以告诉它是 26(2 查询)与 9.2(1 查询)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 tblA
, tblB
, tblC
, tblD
, tblY
其中 tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
我刚刚进行了这个实验,SQLC_CALC_FOUND_ROWS 比两个查询快得多。现在我的主表只有 65k 和几百个的两个连接,但是无论是否使用 SQLC_CALC_FOUND_ROWS,主查询都需要 0.18 秒,但是当我使用 COUNT(id
) 运行第二个查询时,仅需要 0.25 秒。
除了可能的性能问题外,请考虑FOUND_ROWS()
在 MySQL 8.0.17 中已被弃用。另请参阅@madhur-bhaiya 的回答。【参考方案2】:
MySQL 从 8.0.17 版本开始弃用 SQL_CALC_FOUND_ROWS
功能。
因此,始终首选考虑使用LIMIT
执行查询,然后使用COUNT(*)
和不使用LIMIT
执行第二个查询以确定是否还有其他行。 p>
来自docs:
SQL_CALC_FOUND_ROWS 查询修饰符和随附的 FOUND_ROWS() 自 MySQL 8.0.17 起不推荐使用该函数,并将在 未来的 MySQL 版本。
COUNT(*) 受到某些优化。 SQL_CALC_FOUND_ROWS 导致一些优化被禁用。
改用这些查询:
SELECT * FROM tbl_name WHERE id > 100 LIMIT 10; SELECT COUNT(*) WHERE id > 100;
此外,SQL_CALC_FOUND_ROWS
已被观察到通常存在更多问题,如MySQL 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 如果你有 MySQL8+,你可以使用窗口函数在一个查询中运行这两个查询;但这不是最佳解决方案,因为索引不会被正确使用。另一种选择是用LOCK TABLES <tablename>
和UNLOCK TABLES
包围这两个查询。第三个选项和(最好的恕我直言)是重新考虑分页。请阅读:mariadb.com/kb/en/library/pagination-optimization【参考方案3】:
在选择“最佳”方法时,比速度更重要的考虑因素可能是代码的可维护性和正确性。如果是这样,最好使用 SQL_CALC_FOUND_ROWS,因为您只需要维护一个查询。使用单个查询完全排除了主查询和计数查询之间存在细微差异的可能性,这可能导致 COUNT 不准确。
【讨论】:
这取决于您的设置。如果您使用某种 ORM 或查询构建器,则很容易对两个查询使用相同的 where 条件,将选择字段交换为计数,然后取消限制。您永远不应该两次写出标准。 我会指出,我宁愿使用两个简单的相当标准、易于理解的 SQL 查询来维护代码,而不是使用专有 MySQL 功能的一个 - 值得注意的是,在较新的 MySQL 版本中已弃用。 【参考方案4】:根据以下文章:https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
如果您的 where 子句中有 INDEX(如果 id 在您的情况下被索引),那么最好不要使用 SQL_CALC_FOUND_ROWS 而是使用 2 个查询,但是,如果您没有关于在 where 子句中放入的内容的索引(在您的情况下为 id),那么使用 SQL_CALC_FOUND_ROWS 会更有效。
【讨论】:
【参考方案5】:恕我直言,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;
必须被视为一个特殊情况。
它实际上取决于 WHERE 子句的选择性与等价于 ORDER + LIMIT 的隐式子句的选择性。
正如 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),结果真的,真的取决于具体情况。对于特定的分页器,您可以得出这样的结论:“对于 3 个首页,使用 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'
【讨论】:
【参考方案7】:还有其他可供您进行基准测试的选项:
1.) 窗口函数将直接返回实际大小(在 MariaDB 中测试):
SELECT
`mytable`.*,
COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20
2.) 开箱即用,大多数时候用户不需要知道表格的 EXACT 大小,一个近似值通常就足够了.
SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
AND `TABLE_TYPE` = "BASE TABLE"
AND `TABLE_NAME` = ?
【讨论】:
【参考方案8】:具有 2.000.000 行的表上的简单示例和这样的查询:
select fieldname
from table_add
where
descryption_per like '%marihuana%'
or addiction_per like '%alkohol%';
每个查询都进行全表扫描 - 所以需要时间 x 2。 我的意思是“从.....中选择计数(*)
【讨论】:
以上是关于哪个最快? SELECT SQL_CALC_FOUND_ROWS FROM `table`,或 SELECT COUNT(*)的主要内容,如果未能解决你的问题,请参考以下文章
FirstOrDefault()、SingleOrDefault()、Any() 等...哪个最快?