MYSQL加入更新内部步骤

时间:2013-08-08 04:06:05

标签: mysql sql join group-by

使用表格和数据:

CREATE TABLE test.tem(a INT,b INT,INDEX (a),INDEX (b));
INSERT INTO test.tem VALUES(1,2),(1,1),(1,NULL),(2,3);

现在数据应该是:

+------+------+
| a    | b    |
+------+------+
|    1 |    2 |
|    1 |    1 |
|    1 | NULL |
|    2 |    3 |
+------+------+

我想按列a将列b更新到min(b)组 我知道一个正确的SQL是:

UPDATE tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
    USING (a)
SET t1.b = t2.m; 

产生正确的结果是:

+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    1 |    1 |
|    1 |    1 |
|    2 |    3 |
+------+------+

但是,使用此SQL查询在一个表中更新需要大约5分钟,其中包含450万条记录。

所以,我有一个自己的SQL:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

但结果不正确:

+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    1 |    1 |
|    1 |    2 |
|    2 |    3 |
+------+------+

我认为原因在于更新时MYSQL的工作原理。任何人都可以告诉我 错误的结果出来了吗?如果有人可以修复我的 SQL,那也会有所帮助。

2 个答案:

答案 0 :(得分:2)

对于“查询未正确更新行”:

对于具有相同b

的所有行,您希望将列b更新为a最小

您建议使用以下JOIN来执行此操作:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

与您的想法相反,JOIN不会执行1-1 JOIN。它实际上是一个多对多JOIN,因为as I said yesterday你不在join子句中使用主键(也不是非null唯一键)。

事实上,将该查询重写为SELECT可能会帮助您理解问题:

SELECT t1.a as t1a, t1.b as t1b, t2.a as t2a,t2.b as t2b FROM tem t1 JOIN tem t2
    ON t1.a = t2.a
WHERE t1.b > t2.b
     OR t1.b IS NULL;

+------+---------+------+--------+
| T1A  |  T1B    | T2A  |  T2B   |
+------+---------+------+--------+
|   1  | (null)  |   1  | 2      |
|   1  | 2       |   1  | 1      |
|   1  | (null)  |   1  | 1      |
|   1  | (null)  |   1  | (null) |
+------+---------+------+--------+

http://sqlfiddle.com/#!2/856a7/8

正如您现在所看到的,行(1, null)匹配(1, 1)(1, 2)(1, null)。根据查询执行的(非确定性)顺序,这可能会为b分配三个可能值中的任何一个('我不确定,但可能甚至更新它< em>几次次。在某种程度上,你很幸运在测试时发现了“错误”的结果!

我希望这可以解释为什么您的查询不会产生预期结果。由于多表UPDATE语句不允许ORDER BYGROUP BY条款,因为我自己要找到“好”结果,我没有看到许多其他选项而不是找到最小第一个通过子查询...

答案 1 :(得分:1)

为提高查询执行速度,您可以做的一件事就是使用正确的索引来优化WHERE / USING子句。

UPDATE tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
    USING (a)
SET t1.b = t2.m;

此请求具有USING(a)子句。最快的MySQL能够匹配将运行最快查询的列a。因此,您必须在该列上添加索引:

ALTER TABLE tem ADD INDEX (a);

事实上,由于子查询同时使用GROUB BY(a)MIN(b)(a,b)上的索引最有可能表现得更好:

ALTER TABLE tem ADD INDEX (a,b);

为了确保这一点,您可能需要检查查询计划(请参阅下面的EXPLAIN)。


第二个查询是:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

在这里回答“我的查询有什么问题”。但是从纯粹的性能角度来看,由于此查询使用b子句中的列WHERE,因此您将优化其执行,至少要在b上添加索引。

ALTER TABLE tem ADD INDEX (b);

作为提示,当您有 SELECT查询时,您可以使用EXPLAIN SELECT ...检查查询计划以便检查如果/如何使用MySQL。

在这里将两个查询重写为SELECT语句,这将给出:

EXPLAIN SELECT t1.b = t2.b
  FROM tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
  USING (a);

EXPLAIN SELECT t1.b = t2.b
  FROM test.tem t1
  JOIN test.tem t2
  ON t1.a = t2.a
  WHERE t1.b > t2.b
     OR t1.b IS NULL;