在尝试使用JOIN
实现累积求和查询时,我遇到了一个令人惊讶的问题:累积求和表达式有时会在整个表中意外执行,出现意外情况订单,并且不遵守LIMIT
。
考虑以下表格架构:
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 |
+---+----------------------------+
显然,在这些情况下:
ORDER BY
语句,在这种情况下,是插入顺序 - 和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似乎会自动为所有组合键创建预期结果。 < / p>
这引出了一个问题:为什么会发生这种情况?还有其他案例吗?而且,由于此“默认”订单为unpredictable和dangerous,是否可以始终如一地避免此行为?
答案 0 :(得分:3)
混合副作用累加器,ORDER BY和LIMIT子句会产生不可预测的结果。这是因为在生成累加器结果集之后,ORDER BY对生成的行进行操作。 MySQL,以及所有SQL表服务器,在没有ORDER BY的情况下以正式不可预测的顺序返回行。请参阅,SQL Fiddle有同样的问题。 (http://sqlfiddle.com/#!9/44007/4/0)
您可以通过在子查询中以可预测的顺序生成结果集来控制结果,然后使用(令人讨厌的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本质上是一种声明性语言。以柏拉图式理想形式提出的查询并未说 如何生成结果,而是说要生成什么。在现实世界中,SQL充满了编译指示,提示和副作用。
这类问题的问题在于它们通常依赖于服务器计划和执行查询的内部细节。但是,在一个由主管查询计划程序实现的声明性语言中,这些细节是故意模糊和不可预测的。允许查询规划器(为了效率)以任何顺序执行它想要的任何操作,只要它在结束时产生正确的结果。因此它可以执行副作用计算,但它很高兴。
这个提问者被行生成和排序操作序列的模糊处理以及结果中行排序的不可预测性所困扰。
请注意,我们很多人宁愿让MySQL和MariaDB团队花时间实现其他表服务器中可用的排名和窗口功能,而不是做大量的工作来使这些副作用查询更具可预测性。排名和窗口化将为我们提供声明性的方法来生成这个提问者想要的结果。