MySQL 5.7/8.0 与 MySQL 5.6 中的每个重复选择都会重新评估子查询的 rand() 列

Subquery's rand() column re-evaluated for every repeated selection in MySQL 5.7/8.0 vs MySQL 5.6

我正在执行一个子查询,其中有一个涉及随机数生成的计算列。在基本查询中,我 select 此列两次。 MySQL 5.6 如我所料,计算值被调用一次并固定。 5.7+/8.0+ 执行似乎为每个 selection 单独重新评估子查询的列值。这是正确的行为吗?我可以做些什么来强制它在较新版本的 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 |
+---+-----+-----+

The MySQL 8.0.0 Milestone Release is available

所述

In MySQL 5.6 and earlier, derived tables were always materialized. In 5.7, derived tables are merged into the outer query in most cases, and materialized in some cases.

...

Enabling merging a derived table or view through a optimizer hint (WL#9307) — This work by Guilhem Bichot allows users to control whether a derived table or view will be merged or materialized using the “merge” and “no_merge” hints.

我想这就是我在 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> 添加到派生的 table 定义(我在下面使用带符号的 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.

中的优化器错误