【问题标题】:Order of variable assignment evaluation in SELECT can differ from order of rows returned. Under what conditions can this occur?SELECT 中的变量赋值评估顺序可能与返回的行顺序不同。在什么条件下会发生这种情况?
【发布时间】:2019-07-08 16:24:55
【问题描述】:

我最近尝试使用用户定义的变量从结果集中返回的最后行中捕获一些信息。

我的意思是,例如,如果我有一个从 'Aaron' 到 'Zzarx' 的名字列表,

SELECT @n:=Name FROM people ORDER BY Name;
SELECT @n;

第二个SELECT 应该返回“Zzarx”。

这是一个简单的例子。它按预期工作;变量赋值可靠地以与行发送到客户端相同的顺序发生,因此最后一个赋值对应于最后返回的行。

但是当查询更复杂时,似乎会发生奇怪的事情:

SELECT DISTINCT IFNULL(@n:=Name,'unknown') FROM people ORDER BY <some non-indexed expression> LIMIT 10;
SELECT @n;

在 MariaDB v10.3.16 上执行类似的操作,我得到了 @n 的最终值(来自第二个 SELECT不对应于第一个 SELECT返回的任何行!。 (注意 Name 是 NOT NULL 列,所以 IFNULL() 实际上是多余的,但仍然是触发此行为所必需的)。

请注意,它似乎仅在以下所有条件成立时才会发生:

  • SELECT DISTINCT
  • ORDER BY 不能使用索引
  • 变量赋值发生在某个表达式中

我的理论是:

  • SELECT DISTINCT 强制对返回的列表达式进行早期评估。
  • ORDER BY (non-indexed expression) 在评估列数据后强制执行显式排序操作。
  • SQL 引擎足够智能,可以识别简单的SELECT @var := (expression) 模式并仅在将行发送到客户端时评估@var,但如果@var:=... 赋值嵌入在更大的表达式中,则无法进行优化,就像我的示例中的 IFNULL() 一样。

然而,这一切都只是猜测。 用户定义变量的手册页在这方面并没有真正说明任何有用的信息(MySQL 和 MariaDB 都没有)。

在我看来,在多行查询中使用@variable 从最后返回的行中捕获某些内容是一个有用且可能很常见的技巧,但现在我不确定是否或何时可以依赖它。同样,对于我看到的许多行编号和其他巧妙的方案,它们在SELECT 的结果集中使用@variables。

SO 上的某个人是否有任何关于这应该如何工作的明确信息,具体来说,在什么条件下,行变量赋值表达式的评估顺序可以保证与行的实际顺序相对应退了吗?

...因为这似乎是一件很重要的事情!


另一个稍微不那么病态的例子:

说表 t 有 1000 行:

SET @n:=0;
SELECT @n:=@n+1 FROM t ORDER BY 1 DESC LIMIT 5;
SELECT @n;

返回的结果集是:

1000
 999
 998
 997
 996

1000

请注意,@n 的最终值与返回的最后一行不对应,并且确实给出了查询的语义,在这种情况下它不能。

【问题讨论】:

  • ...因为我只知道有人会打电话给我:“什么意思是 Zzarx 不是一个名字?我喜欢一个叫 Zzarx 的好家伙!!!”

标签: mysql sql mariadb


【解决方案1】:

虽然您使用的不是 8.0.13,但以下即将推出。你已经找到了它到来的原因。

-- 2018-10-22 8.0.13 正式发布 -- -- 重要变化 ------

在语句以外的语句中设置用户变量 SET 是 由于此处列出的问题,现已弃用:

涉及用户变量的表达式的求值顺序是 未定义。

变量的默认结果类型基于其在 声明的开头,这可能会产生意想不到的影响 在语句开头保存一种类型的值的变量 在同一个语句中被分配了一个不同类型的新值。

引用变量时的 HAVING、GROUP BY 和 ORDER BY 子句 在选择表达式列表中分配了一个值,不起作用 正如预期的那样,因为表达式是在客户端上评估的,所以 前一行中的陈旧列值可能是 用过。

SELECT @var, @var:=@var+1 等语法在 MySQL 中仍然被接受 8.0 以实现向后兼容性,但在未来的版本中可能会被删除。

-- 来自“更改日志”。

认为DISTINCT 类似于GROUP BY

SELECT @v := ... FROM t ORDER BY x;

案例 1:INDEX(x) 但优化器可能会选择获取行,然后对它们进行排序。

案例 2:INDEX(x) 并且优化器选择根据索引获取行。

SELECT @v := ... FROM t  GROUP BY w  ORDER BY x;

这几乎肯定需要生成一个临时表(用于排序),可能需要生成两个(一个用于分组,一个用于排序)。运行查询的唯一合理方法是评估SELECT 中的表达式(包括@v),收集结果,然后进行分组和排序。因此,评估顺序不太可能是x。但它可能模仿w

PARTITIONing 呢?目前,MySQL 对SELECT 的评估没有并行性。但是,如果它真的存在呢?让我们看一个“明显”的案例——单独的线程在表的单独PARTITIONs 上工作。所有投注均按评估顺序关闭。

一旦实现,如何拆分未分区的SELECT 以获得一些并行性?

你不会赢得争论。

是的,它可能会长期处于“弃用”状态。或者也许会有一个sql_mode 以“旧”方式运行查询。或者@variables 的存在会抑制某些优化(有利于可预测性)。等等。

我可以建议您在 bugs.mysql.com 上写一个“功能请求”,说明您希望看到的内容。 (你也可以在 mariadb.com 上做,但他们看的是前者。)

【讨论】:

  • 是的,我实际上已经在 MySQL v8 手册的“用户变量”页面上阅读了一些类似的内容。所以它在 MySQL 中已被弃用(不是 MariaDB 中的 AFAIK,至少现在还没有)。但我一点也不相信他们会真正删除它。它会严重破坏向后兼容性。是否已弃用,我仍然想知道何时可以依赖 (evaluation order)=(row order)。
  • 另外我不认为GROUP BY 的事情是完全相同的...更多的是关于GROUP BY 使用与SELECT 中返回的最终值不同的值。而我的问题是关于评估顺序与返回行的顺序,我认为这是一个不同的问题。
  • 另外,他们提到SELECT @var, @var:=@var+1 可能会被删除......但这包含一个完全不同的问题:Column 评估顺序不确定,这是你的主要原因永远不要那样做。
  • @Doin - 我在我的回答中谈了更多。
  • 谢谢瑞克。我想你回答了我的问题。我在这里并没有真正的功能请求,我只是想弄清楚何时可以/不安全地承担评估订单 - 你已经非常清楚地回答了!
猜你喜欢
  • 1970-01-01
  • 2015-11-02
  • 1970-01-01
  • 1970-01-01
  • 2012-02-02
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2016-09-27
相关资源
最近更新 更多