【问题标题】:Why is my `sql_calc_found_rows` faster than `count(*)` in MySQL?为什么我的 `sql_calc_found_rows` 比 MySQL 中的 `count(*)` 快?
【发布时间】:2019-12-07 15:51:00
【问题描述】:

我知道 here 已经讨论过,在许多情况下,MySQL 中的 sql_calc_found_rows 被认为比运行第二个 count(*) 查询要慢。此外,我们现在真的别无选择,因为sql_calc_found_rows 已被弃用,并将从未来版本的 MySQL 中删除。但是在我的查询中,它由几个左连接、多个带有限制和偏移量的动态 where 子句组成,根据我的测试,添加 sql_calc_found_rows 和随后的 FOUND_ROWS() 查询没有开销。但是,将count(*) 作为没有限制和偏移子句的第二个查询运行,以获取找到的总行数,几乎加倍 执行时间。让我感到非常困惑的是,其他所有来源都断言count(*) 由于内部优化而更快,但在我的实践中它要慢得多。值得注意的是,此时我没有索引。如果有人能澄清一下这个问题,我将不胜感激。

【问题讨论】:

  • 我假设您在这里谈论和基准测试 MySQL 的 InnoDB 引擎?
  • 确实是InnoDB。但是在这个开发阶段,数据库是本地的,而不是分布式的。
  • 如果您想获得具体的优化建议,请提供SELECT语句、涉及表的CREATE TABLE语句以及SELECT语句的EXPLAIN输出。
  • 根据我的基本理解,当我查看源代码 sql_calc_found_rows 时,需要在连接线程中限制和缓存 FOUND_ROWS() 的 row_num 之前加载和处理(“计数”)完整的结果集.. 意味着在正确配置的 MySQL 服务器(“innodb_buffer_pool 主要”)中的 indexed COUNT(*) 查询和使用 InnoDB 引擎最有可能(很多)更快,因为处理的结果集最有可能(很多)更小..在使用非索引表的其他大小上,sql_calc_found_rows 很可能会更快..
  • COUNT(*) 方法在最坏的情况下会慢两倍。但它可以快 10 倍、100 倍甚至 1000 倍。这取决于查询。最坏的情况是当您使用高 OFFSET(这是一种罕见的情况)或您的查询和数据库未优化以使用 ORDER BY 的索引时。但是 - 我不明白你的问题的重点。没有人告诉您,count(*)总是更快。应该清楚的是,有时并非如此。那么你的问题到底是什么?

标签: mysql count


【解决方案1】:

It still depends.

由于您想要进行一般评估,而不是针对您的查询:您不能做出一般假设,即其中一种方法会更快。 MySQL 将单独处理每个查询,如果找到更快的执行计划,它将使用它。

如果 MySQL 不需要所有行来为您提供请求的结果,添加 limit 可以缩短执行时间。

  • 一个简单的例子是select * from table limit 1。 MySQL 可以取第一行然后停止,而不是读取整个表。

  • 一个简单的反例是select * from table order by some_unindexed_column limit 1。 MySQL 必须读取整个表,对其进行排序,然后返回第一行。

  • 让反例再次变快的一个简单对策是在some_unindexed_column 上添加一个索引。 MySQL 现在可以从索引中读取第一个条目,从表中读取相应的行并停止。 (这里的两个步骤使这个查询比第一个略慢)。

显然,如果添加limit 不会影响执行时间(我不包括必须向客户端发送更少行的影响),sql_calc_found_rows 也不会影响它。

评估相关count(*)-query 的执行时间通常更难,因为它通常只是一个不同的查询,不再具有直接可比性。例如。对于select count(*) from (select * from table order by some_unindexed_column) suborder by 显然是多余的,如果您手动编写它,您只需将其删除,即使不是,MySQL 也会根据您的版本实际删除它。

所以你实际上会运行select count(*) from table

完全巧合,这也是相关的count(*)-查询select * from table limit 1

这个计数查询比:

  • select * from table order by some_unindexed_column 因为它不需要做订购
  • select * from table order by some_unindexed_column limit 1select sql_calc_found_rows * from table order by some_unindexed_column limit 1 以及它不需要进行排序(并且sql_calc_found_rows 对这种情况无效)

它比: - select * from table limit 1 因为它必须读取所有行,而此查询可以在第一行之后停止 - select * from table order by indexed_column limit 1 因为它必须读取所有行,而 limit 查询可以使用 indexed(!) 列提前停止

一个特殊的,可能是唯一有趣的案例,与您阅读的blog 中的情况密切相关,是

select sql_calc_found_rows * from table order by indexed_column limit 1

此查询将读取索引,然后使用索引信息从原始表中读取数据(因为它需要所有列)。这个两步过程非常缓慢。它实际上非常慢,如果 MySQL 怀疑它必须从原始表中读取相当大比例的行,它可以决定不使用索引。另一方面,select count(*) from table 不需要这种两步查找。

这基本上是最相关的一般情况,count-query 表现更好,这正是您博客中发生的情况。 YMMV,但是通过这种方式可以实现 10 倍的性能提升,尽管通常会更少。

还有一种方法可以缓解这个问题:如果你添加一个covering index。如果你例如有一个索引 (a,b,c) 并且只能从中选择,例如select b, c from table order by a,这可以完全在索引内发生,不需要两步查找。

另一种情况是count(*) 允许 MySQL 选择尽可能小的索引。例如。对于select count(*) from table,MySQL 可以选择该表上的任何可用索引,对于小索引,您需要读取更少的字节。这比读取更多字节更快。 (对于 MyISAM 表,我将忽略这一点,计数存储在表中并且可以立即查找)。

可能还有更多的情况(或者像 select very_expensive_function(a) from table 这样的事情,如果你不执行该函数显然会变得更快),这可能会给你带来好处。

总结一下:

  • 如果limit 不改变执行速度,添加sql_calc_found_rows 没有效果,另外运行count(*) 查询显然只会增加更多时间。
  • 如果limit 确实改变了执行速度,那么添加sql_calc_found_rows 会使其变慢。如果运行额外的count(*) 比添加sql_calc_found_rows 更快取决于查询。

值得注意的是,这个分析是针对一个非常琐碎的查询。您的查询越复杂,count-version 比sql_calc_found_rows 快的可能性就越小(只是运气)。它还取决于您的 MySQL 版本。

根据您的描述,您的查询目前属于第一类,如果 MySQL 自己没有找到优化count-version 的方法,预计与原始的(这将使两个查询的总时间增加一倍)。任何更具体的内容(尤其是在您需要优化查询的帮助时)都需要查看您的特定查询。

【讨论】:

  • 哇,谢谢你的详细解释。我查看了我的表,发现第一个表和其他一些连接表实际上将 MyISAM 指定为 sql 引擎。由于我正在使用现有数据库并且没有自己创建这些表,因此我对每个特定表的详细信息知之甚少。我将尝试在有序列上添加索引以查看它是否会改变任何内容,然后返回此处报告结果。此外,我将尝试提交修剪后的 sql 查询并创建表语句来说明问题,而不会转储太多不相关的信息。
猜你喜欢
  • 1970-01-01
  • 2011-08-06
  • 1970-01-01
  • 2019-08-02
  • 2010-09-16
  • 2016-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多