【问题标题】:Subquery's rand() column re-evaluated for every repeated selection in MySQL 5.7/8.0 vs MySQL 5.6为 MySQL 5.7/8.0 与 MySQL 5.6 中的每个重复选择重新评估子查询的 rand() 列
【发布时间】:2017-11-04 07:20:02
【问题描述】:

我正在做一个子查询,其中我有一个涉及随机数生成的计算列。在基本查询中,我选择了此列两次。 MySQL 5.6 按我的预期工作,计算值被调用一次并修复。 5.7+/8.0+ 执行似乎为每个选择单独重新评估子查询的列值。这是正确的行为吗?我可以做些什么来强制它在较新版本的 MySQL 中按预期工作?

CREATE TABLE t (
  `id` BIGINT(20) NOT NULL PRIMARY KEY AUTO_INCREMENT
) ENGINE=InnoDB;

insert into t values();
insert into t values();
insert into t values();
insert into t values();
insert into t values();

SELECT  
        q.i,
        q.r,
        q.r
FROM    (
        SELECT  
                id AS i,
                (FLOOR(RAND(100) * 4)) AS r
        FROM t
        ) q;

MySQL 5.6 产生(值相同):

+---+-----+-----+
| i |  r  |  r  |
+---+-----+-----+
| 1 |   0 |   0 |
| 2 |   2 |   2 |
| 3 |   3 |   3 |
| 4 |   2 |   2 |
| 5 |   1 |   1 |
+---+-----+-----+

而 5.7 的产量(值不同):

+---+-----+-----+
| i |  r  |  r  |
+---+-----+-----+
| 1 |   0 |   2 |
| 2 |   3 |   2 |
| 3 |   1 |   1 |
| 4 |   2 |   1 |
| 5 |   2 |   0 |
+---+-----+-----+

【问题讨论】:

  • 也许这取决于子查询是否物化。看起来您可以使用提示 /*+ SUBQUERY(MATERIALIZATION) */ (在您的内部选择之后添加)强制在 5.7 中实现(我希望这会让您获得 5.6 的行为)
  • Oracle 说它不是bug。他们或多或少说优化器可以忽略优化查询的不确定性。但是他们自己的文档说非确定性函数会阻碍优化,所以我怀疑它们不符合 SQL 标准,当我阅读了我的参考文献后,我会发布答案。请参阅 MySQL 12.6.2 Mathematical Functions8.2.1.17 Function Call Optimization
  • 回想起来,我的链接错误是 re ORDER BY RAND(),所以它不适用。 MySQL RAND() 文档说它在每个 WHERE 行中调用一次,并说明(但实际上并没有说)它在每个 SELECT 行中调用一次(并说它不能在 GROUP BY 或 ORDER BY 中使用;并且 ON & HAVING 未解决,但它们是根据 WHERE 定义的)。 (它也没有阐明一个子句中的多个调用。)将这一点添加到查询的 SQL 概念执行中,即 SELECT 子句应从 FROM 的输出中选择,这会成为一个错误。
  • @philipxy 我在 MySQL 提交了相应的bug report
  • 我同意我的仅供参考是一个单独的问题。但是,无论对多个文本调用给出什么合理的行为,您的双重选择子句都有 none。遗憾的是,MySQL 文档并没有说明在给定程序文本的情况下您可以依赖什么来实现非确定性功能。也不是变量——他们谈论某种涉及“客户端”的执行模型,但他们没有解释那个

标签: mysql sql subquery materialized-views derived-table


【解决方案1】:

The MySQL 8.0.0 Milestone Release is available中所述,

在 MySQL 5.6 和更早的版本中,派生表总是被物化的。在 5.7,派生表在大多数情况下合并到外部查询中,并在某些情况下具体化。

...

通过优化器提示启用合并派生表或视图 (WL#9307) — Guilhem Bichot 的这项工作允许用户使用“merge”和“no_merge”提示控制是合并还是实现派生表或视图.

我想这是我在较新版本的 MySQL 中观察到的行为的原因。 提到的提示可以与 MySQL 8.0 一起使用来强制 RAND() 只被调用一次:

SELECT  /* NO_MERGE(q) */
        q.i,
        q.r,
        q.r
FROM    (
        SELECT 
                id AS i,
                (FLOOR(RAND(100) * 4)) AS r
        FROM t
        ) AS q;

+---+-----+-----+
| i |  r  |  r  |
+---+-----+-----+
| 1 |   0 |   0 |
| 2 |   2 |   2 |
| 3 |   3 |   3 |
| 4 |   2 |   2 |
| 5 |   1 |   1 |
+---+-----+-----+

然而,这在 5.7 中不可用。要使用 5.7 实现所需的行为,请将 LIMIT <a very high number> 添加到派生表定义中(我在下面使用带符号的 LONG_MAX)。感谢 Roy Lyseng 提供的 workaround

SELECT
        q.i,
        q.r,
        q.r
FROM    (
        SELECT 
                id AS i,
                (FLOOR(RAND(100) * 4)) AS r
        FROM t LIMIT 9223372036854775807
        ) AS q;

+---+-----+-----+
| i |  r  |  r  |
+---+-----+-----+
| 1 |   0 |   0 |
| 2 |   2 |   2 |
| 3 |   3 |   3 |
| 4 |   2 |   2 |
| 5 |   1 |   1 |
+---+-----+-----+

正如评论中提到的philipxy,无论是否应用了任何优化,都必须严格定义查询表达式的结果。这意味着它是 MySQL 5.7/8.0 中的优化器错误。

【讨论】:

  • 所有这意味着优化器有一个错误。优化与查询表达式的值的定义无关。
  • @philipxy 我已将其添加到答案中。感谢您的贡献!
  • 对我来说,使用GROUP BY id 代替LIMIT high number 也可以,而且有点“干净”:stackoverflow.com/questions/48432654/…
猜你喜欢
  • 2019-02-08
  • 2018-09-26
  • 2020-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
相关资源
最近更新 更多