MySQL – JOIN 自动执行不相关的 SELECT 表达式

MySQL – JOIN automatically executes irrelevant SELECT expressions

在尝试使用 JOIN 实现累积和查询时,我 运行 遇到了一个令人惊讶的问题:累积和表达式有时会在 整个 table,顺序出乎意料,并且没有遵守 LIMIT.

考虑以下 table 模式:

CREATE TABLE t ( 
    v INT NOT NULL, 
    q_val INT
) ENGINE=InnoDB;
CREATE TABLE q ( val INT ) ENGINE=InnoDB;

INSERT INTO t VALUES 
    (5, 1), 
    (3, 1), 
    (1, 1), 
    (4, 1), 
    (2, 1);
INSERT INTO q VALUES (1);
-- (Table `q` exists solely for the purpose of the JOIN)

t中的数据是故意乱序输入的

这是有问题的查询:

SELECT
  v, @cumulative:=@cumulative+v
  FROM t
  [INNER/LEFT/RIGHT] JOIN q ON t.q_val=q.val
  CROSS JOIN (
      SELECT (@cumulative:=0)
  ) z
  ORDER BY t.v DESC
  LIMIT 2;

我们应该期望得到左边的结果,但通常我们得到的结果是右边:

+---+----------------------------+      +---+----------------------------+
| v | @cumulative:=@cumulative+v |      | v | @cumulative:=@cumulative+v |
+---+----------------------------+      +---+----------------------------+
| 5 |                          5 |  VS  | 5 |                          5 |
| 4 |                          9 |      | 4 |                         13 |
+---+----------------------------+      +---+----------------------------+

如果我们删除表现出意外行为的查询的 LIMIT 子句,我们就会看到真正发生的事情:

+---+----------------------------+
| v | @cumulative:=@cumulative+v |
+---+----------------------------+
| 5 |                          5 |
| 4 |                         13 |
| 3 |                          8 |
| 2 |                         15 |
| 1 |                          9 |
+---+----------------------------+

显然,在这些情况下:

  1. 总和是按照 table 的 "default" 顺序计算的 — 没有显式 ORDER BY 语句的顺序,在这种情况下,插入顺序 — and
  2. 它在 JOIN 循环中以某种方式在其余查询之前执行,绕过了 LIMIT限制。

第二种行为特别令人困惑,因为求和项来自的列甚至不是 ON 子句中的列。

有两个因素似乎有助于表现出行为:索引的组合和类型(常规与 PRIMARY)以及 JOIN 的类型。我已经测试了具有显着效果的组合,并将它们编译如下:

** 奇怪的是,使用 INNER JOIN q FORCE INDEX(val) 使得 "KEY(q.val) AND KEY(t.q_val)" 行为出于某种原因
* 我怀疑 SELECT STRAIGHT_JOIN 在所有情况下都会自动创建意外结果,但 STRAIGHT_JOIN 类型的 JOIN 似乎会自动为所有组合键创建 预期的 结果。

这引出了一个问题:为什么会这样?还有其他情况吗?而且,由于这个 "default" 顺序是 unpredictable and dangerous,是否可以始终避免这种行为?

SQL Fiddle

混合使用副作用累加器、ORDER BY 和 LIMIT 子句会产生不可预测的table 结果。这是因为 ORDER BY 对结果行进行操作 生成累加器结果集之后。 MySQL,实际上所有 SQL table 服务器,在没有 ORDER BY 的情况下,return 行以正式不可预测的 table 顺序排列。看,SQL Fiddle 与您显示的问题相同。 (http://sqlfiddle.com/#!9/44007/4/0)

您可以通过在子查询中以谓词table 顺序生成结果集,然后使用(令人讨厌的 MySQL hack of a)副作用累加器来控制结果。像这样。 (http://sqlfiddle.com/#!9/44007/10/0)

SELECT v, @cumulative := @cumulative+v
FROM (
  SELECT t.v
    FROM t
    LEFT JOIN q ON t.q_val=q.val
   ORDER BY t.v DESC
) a
JOIN (SELECT @cumulative := 0) b
LIMIT 2

什么是副作用累加器?

本题涉及@cumulative的查询模式是副作用累加器。之所以这样称呼是因为 @cumulative := @cumulative+v 声明了结果集列的内容,并且还会产生副作用(增加变量)。

SQL 本质上是一种声明性语言。柏拉图式理想形式的查询不会说 how 来生成结果,而是说 what 来生成。在现实世界中,SQL 到处都是编译指示、提示和副作用。

这类事情的问题在于它们通常取决于服务器如何计划和执行查询的内部细节。但是,在由有能力的查询规划器实现的声明性语言中,这些细节是故意模糊和不可预测的table。查询规划器被允许(为了效率)以它想要的任何顺序做它想做的任何事情,只要它在最后产生正确的结果。所以它可以随心所欲地进行副作用计算。

这位提问者被行生成和排序操作的顺序模糊不清,以及结果中行排序的不可预测性所困扰。

请注意,我们中的许多人宁愿让 MySQL 和 MariaDB 团队花时间在其他 table 服务器上实现排名和窗口功能,而不是做很多工作来实现这些副作用查询更多 predictable。排名和开窗将为我们提供声明性的方式来生成提问者想要的那种结果。