【问题标题】:MYSQL join update internal stepsMYSQL join 更新内部步骤
【发布时间】:2013-08-08 04:06:04
【问题描述】:

使用表格和数据:

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 查询在一个包含 450 万条记录的表中更新大约需要 5 分钟。

所以,我有一个自己的 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,那也会有所帮助。

【问题讨论】:

  • 我根据您在stackoverflow.com/a/18081509/2363712 上留下的cmets 对您的问题进行了大量编辑。请检查这些更改并修复可能的拼写错误、误解和其他错误...

标签: mysql sql join group-by


【解决方案1】:

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

对于具有相同a 的所有行,您希望将b 列更新为b最小值

您建议使用以下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 在您的联接子句中没有使用主键(也没有非空唯一键)。

事实上,将该查询重写为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 分配三个可能值中的任何一个(我不确定,但可能甚至更新它几次)。在某种程度上,您很幸运在测试时发现了“错误”的结果!

我希望这能进一步解释为什么您的查询没有产生预期的结果。由于多表 UPDATE 语句不允许 ORDER BYGROUP BY 子句,就我自己而言,要找到“好”的结果,除了找到最小值 first 通过子查询...

【讨论】:

  • 这正是我想要的。非常感谢您的耐心等待。本来以为(1 NULL)条记录会更新几次,没想到MYSQL先查询数据,再通过查询数据更新记录。我在这种情况下如何找到更好的更新方法,直到现在“加入子查询”是最好的。
【解决方案2】:

您可以做的一件事主要是使用适当的索引来优化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;

在这里回答“我的查询出了什么问题”。但从纯粹的性能角度来看,由于此查询在 WHERE 子句中使用列 b,因此您将优化其执行,您至少需要在 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;

【讨论】:

  • 我已经在开头添加了索引。 “5分钟450万条记录”表也有类似的索引(表结构不同)。我认为 MYSQL 将为 SELECT a,MIN(b) AS m FROM tem GROUP BY a 生成一个临时表,并且该表没有索引。那是它浪费一些时间的地方。这个话题最重要的是找出为什么我的 SQL 结果不正确:)
  • +1 表示(a,b) 索引。这对于两个版本的查询(正确的工作版本和不工作的版本)都是必需的。
  • @ypercube 完全正确:我非常专注于 WHERE 子句,以至于我没有看到 ON 连接子句 O_o(顺便说一句,感谢您编辑我的答案!)
  • @bluearrow 子查询SELECT a, MIN(b) ...返回多少行?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-20
  • 2019-07-28
  • 2013-12-10
相关资源
最近更新 更多