【问题标题】:Does MySQL eliminate common subexpressions between SELECT and HAVING/GROUP BY clauseMySQL 是否消除了 SELECT 和 HAVING/GROUP BY 子句之间的公共子表达式
【发布时间】:2014-07-12 12:49:43
【问题描述】:

我经常看到人们用这样的查询回答 MySQL 问题:

SELECT DAY(date), other columns
FROM table
GROUP BY DAY(date);

SELECT somecolumn, COUNT(*)
FROM table
HAVING COUNT(*) > 1;

我总是喜欢给列一个别名,并在 GROUP BYHAVING 子句中引用它,例如

SELECT DAY(date) AS day, other columns
FROM table
GROUP BY day;

SELECT somecolumn, COUNT(*) AS c
FROM table
HAVING c > 1;

MySQL 是否足够聪明地注意到后面的子句中的表达式与SELECT 中的表达式相同,并且只执行一次?我不确定如何对此进行测试——EXPLAIN 没有显示出任何区别,但它似乎并没有显示出它首先是如何进行分组或过滤的;它似乎主要用于优化连接和WHERE 子句。

我倾向于对 MySQL 优化持悲观态度,所以我愿意尽我所能提供帮助。

【问题讨论】:

  • 以防万一您需要立即回答,作为权宜之计,直到我找到权威参考(我怀疑它可能必须来自源头),我很确定解析器识别对确定性函数(实际上是表达式)的调用并缓存结果以在查询中重复使用。
  • +1 一个连贯且有用的问题!
  • 巴尔玛在问问题?我担心我的指南针已经不可逆转地失准了。
  • 函数结果被缓存的一个简单演示是SELECT RAND() FROM table ORDER BY RAND() - 因为结果列确实是排序的,MySQL 必须对RAND() 的两次调用使用相同的值。
  • 我正在尝试根据优化器源代码整理出一个更权威的答案 - 只是认为现在值得一提。

标签: mysql group-by query-optimization having


【解决方案1】:

我认为这可以使用 sleep() 函数进行测试,
例如看看这个演示:http://sqlfiddle.com/#!2/0bc1b/1

Select * FROM t;

| X |
|---|
| 1 |
| 2 |
| 2 |

SELECT x+sleep(1)
FROM t
GROUP BY x+sleep(1);

SELECT x+sleep(1) As name
FROM t
GROUP BY name;

两个查询的执行时间约为 3000 毫秒(3 秒)。
表中有 3 条记录,每条记录查询仅休眠 1 秒,
所以这意味着表达式只对每条记录计算一次,而不是两次。

【讨论】:

  • 进一步的“证明”是当您将 一个 的睡眠更改为 (2) 时。现在需要 9 秒。
  • select x+sleep(1), count(*) from t group by x+sleep(1) 需要 6 秒。发生什么了?也许原来的例子只是转换为select distinct x+sleep(1) from t
  • 另外:select x+sleep(1) as c from t having c > 0 也需要 6 秒。所以我想,每个别名都在内部被它后面的表达式替换,并且每次都会再次评估。
  • 但是...SLEEP() 并不是真正的“确定性”。 SQRT 是; RAND 不是——因此SQRT 被记住是“好的”,但RAND 可能不会。 OTOH,NOW() 是故意记住的,但 SYSDATE 不是。
【解决方案2】:

在咨询了一位 MySQL 工程师后,我给出了这个冗长的答案。

  • 缓存 - 查询的任何部分都不会被“记住”以供以后在该(或后续)查询中使用。 (对比:查询缓存。)
  • 通用子表达式消除 - 没有。这是一种常见的编译器技术,但 MySQL 不使用它。示例:(a-b)*(a-b) 将执行两次减法。
  • 从循环中删除常量 - 是的,但有限制。这是另一种编译器技术。
  • 各种以 SQL 为中心的 hack - 是的;见下文。
  • 重新评估子查询 - 视情况而定。此外,优化器也在逐渐完善。
  • VIEWs - 这取决于。在某些情况下,VIEW 的性能注定要低于等效的SELECT。示例:无条件下推到VIEW 中的UNION。实际上,这更多是延迟行动的问题。
  • 我认为一些较新版本的 MariaDB 具有“子查询缓存”。

(警告:我对我的任何答案都没有 100% 的信心,但我相信大部分都是正确的,从 MySQL 5.7、MariaDB 10.1 等开始)

将多行 SELECT 视为一个循环。许多,也许是所有的“确定性”表达式都被评估一次。示例:常量日期表达式,甚至涉及函数调用。但是……

NOW() 在查询开始时专门评估一次。此外,该值在复制时传递给从站。也就是说,当查询存储在从属设备上时,NOW() 可能已经过时了。 (SYSDATE() 是另一种动物。)

特别是随着only_full_group_by 的出现,GROUP BY 需要知道它是否匹配SELECT 表达式。所以,这会寻找类似的代码。

HAVINGORDER BY 可以使用SELECT 列表中的别名(与WHEREGROUP BY 不同)。所以SELECT expr AS x ... HAVING expr 似乎重新评估了expr,但SELECT expr AS x ... HAVING x 似乎达到了已经评估的expr

MariaDB 10.2 的 Windowing 函数对它们可以/不能重用的地方有一些非常严格的限制;我还没有他们的全貌。

一般来说,这些都不重要——重新评估一个表达式(DATE(date) 甚至COUNT(*))会得到相同的答案。此外,翻阅行通常比表达式评估更昂贵。所以,除非你有一个好的秒表,否则你不会分辨出区别。

【讨论】:

  • 考虑到另一个答案中的演示,这是否意味着它认为SLEEP(1) 是一个确定性表达式,所以它只计算一次?
  • 确定性 - 不。否则查询将花费 1 秒,而不是 3 秒。我认为 x+sleep(1) 属于我在 GROUP BY 上所说的黄鼠狼的话。注意GROUP BY x+sleep(2) 需要 9 秒;我不知道x 是否参与了only_full_group_by 检查。
猜你喜欢
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 2021-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多