通过join更新MySQL-为什么它很慢?

r7xajy2e  于 2023-04-10  发布在  Mysql
关注(0)|答案(4)|浏览(253)

bounty将在17小时后到期。回答此问题可获得+50声望奖励。dai.hop希望引起更多关注此问题。

我有以下查询:

UPDATE
    `temp_table`,
    `sms`
SET
    `sms`.`to` = `temp_table`.`new_value`
WHERE
    `sms`.`to` = `temp_table`.`old_value`;

temp_table有大约200,000行。
sms有大约2,000,000行。
sms . totemp_table . new_valuetemp_table . old_value是带索引的VARCHAR(255)。
值为英国电话号码。
更新查询很慢,从来没有完成过。有人知道为什么吗?

说明:

建表文本结果:

  • 来自phpMyAdmin*
CREATE TABLE `temp_table` (
    `old_value` varchar(255) DEFAULT NULL,
    `new_value` varchar(255) DEFAULT NULL
) ENGINE = InnoDB DEFAULT CHARSET = latin1 COLLATE = latin1_swedish_ci;

ALTER TABLE `temp_table`
    ADD KEY `old_value` (`old_value`),
    ADD KEY `new_value` (`new_value`);

CREATE TABLE `sms` (
    `id` int(11) NOT NULL,
    `branch_id` int(11) DEFAULT NULL,
    `customer_id` int(11) DEFAULT NULL,
    `message_id` int(11) DEFAULT NULL,
    `message` text DEFAULT NULL,
    `from` varchar(255) DEFAULT NULL,
    `to` varchar(255) DEFAULT NULL,
    `status` varchar(255) DEFAULT NULL,
    `created_at` int(11) DEFAULT NULL,
    `updated_at` int(11) DEFAULT NULL
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COLLATE = utf8_general_ci;

ALTER TABLE `sms`
    ADD PRIMARY KEY (`id`),
    ADD KEY `idx-sms-branch_id` (`branch_id`),
    ADD KEY `idx-sms-customer_id` (`customer_id`),
    ADD KEY `idx-sms-message_id` (`message_id`),
    ADD KEY `idx-sms-to` (`to`),
    ADD KEY `idx-sms-created_at` (`created_at`),
    ADD KEY `idx-sms-updated_at` (`updated_at`);

ALTER TABLE `sms`
    MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
30byixjq

30byixjq1#

  • 如果优化器选择遍历sms(然后JOIN到另一个表),那么temp_table最好使用INDEX(old_value, new_value) _而不是INDEX(old_value)
  • 为什么message_id和消息文本都在这个表中?可能将这对放在另一个表中会使这个查询快得多。(然后在这个表中只有message_id。如果text通常很大,这一点尤其重要。
  • UPDATEing 200K行一次更新需要很多时间和RAM。一个计划是一次更新1K。参见 chunking

我无法预测这三个建议中哪一个最有帮助。如果可行的话,我建议你把所有的建议都做一遍。

g6ll5ycj

g6ll5ycj2#

您没有ON clause,但您使用了WHERE子句:
INNER JOIN和,(逗号)在没有连接条件的情况下在语义上等价**:两者都在指定的表之间产生笛卡尔积**(即,第一个表中的每一行都与第二个表中的每一行连接)。
...
与ON一起使用的search_condition是可以在WHERE子句中使用的形式为的任何条件表达式。通常,ON子句用于指定如何连接表的条件,WHERE子句限制结果集中包含哪些行。
在你的例子中,最终结果是相同的but in other cases is not)。从概念上讲,“where”过滤发生在表以笛卡尔积连接之后,查询优化器肯定可以做一些事情并“下推”where predicate ,但我建议初学者使用dedicated syntax指导MySQL,以允许它构建一个明智的执行计划。
如果你注意到在documentation about update它说:
table_references子句列出了连接中涉及的表。**其语法在13.2.13.2节“JOIN子句”中进行了描述。以下是一个示例:

> UPDATE items,month SET items.price=month.price WHERE
> items.id=month.id;

前面的示例显示了使用逗号运算符的内部连接,但多表UPDATE语句可以使用SELECT语句中允许的任何类型的连接,如LEFT JOIN。
因此,MySQL文档中使用的示例使用了一个可用的连接,该连接是笛卡尔积

llmtgqce

llmtgqce3#

Update语句的解释显示了temp_table的'old_value'(possible_keys)的用法,而sms表的select_type是'UPDATE',两者都与OP输出不同。
除此之外,这两个表的COLLATE和CHARSET定义也不一致,这也可能对性能产生一些副作用。

describe UPDATE     `temp_table`,     `sms` SET     `sms`.`to` = `temp_table`.`new_value` WHERE     `sms`.`to` = `temp_table`.`old_value`;

+----+-------------+------------+------------+------+---------------+------------+---------+--------------------------+-------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key        | key_len | ref                      | rows  | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------------+---------+--------------------------+-------+----------+-------------+
|  1 | SIMPLE      | temp_table | NULL       | ALL  | old_value     | NULL       | NULL    | NULL                     | 21702 |   100.00 | Using where |
|  1 | UPDATE      | sms        | NULL       | ref  | idx-sms-to    | idx-sms-to | 768     | sms.temp_table.old_value |     1 |   100.00 | Using where |
+----+-------------+------------+------------+------+---------------+------------+---------+--------------------------+-------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

使用20k temp_table测试大约20k sms,我的简陋机器在0.63秒内完成,其中所有记录都被更新。只有更新查询的相关字段具有唯一值。
更新:

UPDATE     `temp_table`,     `sms` SET     `sms`.`to` = `temp_table`.`new_value` WHERE     `sms`.`to` = `temp_table`.`old_value`;
Query OK, 20000 rows affected (0.68 sec)
Rows matched: 20000  Changed: 20000  Warnings: 0

表temp_table:

describe temp_table;
+-----------+--------------+------+-----+---------+-------+
| Field     | Type         | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| old_value | varchar(255) | YES  | MUL | NULL    |       |
| new_value | varchar(255) | YES  | MUL | NULL    |       |
+-----------+--------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
mysql  Ver 8.0.32

祝你好运

cnwbcb6i

cnwbcb6i4#

您可以在查询中使用MySQL Update with Join

UPDATE `sms`
JOIN `temp_table` ON `sms`.`to` = `temp_table`.`old_value`
SET `sms`.`to` = `temp_table`.`new_value`;

另外,如果您可以索引搜索列**“temp_table.old_value”**,则会更好。

ALTER TABLE `temp_table` ADD INDEX `idx_old_value` (`old_value`);

所以,搜索结果更快。

相关问题