ON DUPLICATE KEY UPDATE 比 UPDATE 快

dot*_*hen 5 mysql update

我有一个大约有 1700 万行的表:

mysql> describe humans_we_respect;
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
| Field               | Type                                                                    | Null | Key | Default | Extra |
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
| id                  | bigint(20)                                                              | NO   | PRI | NULL    |       |
| name                | varchar(63)                                                             | YES  |     | NULL    |       |
| address             | varchar(127)                                                            | YES  |     | NULL    |       |
| city                | varchar(63)                                                             | YES  |     | NULL    |       |
| state               | varchar(3)                                                              | YES  | MUL | NULL    |       |
| zip                 | varchar(15)                                                             | YES  |     | NULL    |       |
| country             | varchar(15)                                                             | YES  |     | NULL    |       |
| email               | varchar(127)                                                            | YES  |     | NULL    |       |
| website             | varchar(127)                                                            | YES  |     | NULL    |       |
| area_code_state     | varchar(3)                                                              | YES  | MUL | NULL    |       |
| timezone            | set('other','pacific','mountain','central','eastern','alaska','hawaii') | YES  |     | other   |       |
+---------------------+-------------------------------------------------------------------------+------+-----+---------+-------+
12 rows in set (0.01 sec)
Run Code Online (Sandbox Code Playgroud)

由于只联系那些对时事通讯表示感兴趣的人的严格性质,以及从不联系要求不被联系的人的严格性质,在邮寄之前,我添加了一个字段expressed_interest (tinyint) deafult null,我1为那些表达兴趣的人添加了一个字段,然后切换到null那些要求不被联系的人。

以下查询,其中每个查询更新 10000 行,运行时间很长(半小时后终止):

UPDATE humans_we_respect SET expressed_interest=1 WHERE id IN (1,...,10000);
Run Code Online (Sandbox Code Playgroud)

但是,以下查询会在几秒钟内完成:

INSERT INTO humans_we_respect (id) VALUES (1),...,(10000) ON DUPLICATE KEY UPDATE expressed_interest=1;
Run Code Online (Sandbox Code Playgroud)

在什么条件下会ON DUPLICATE KEY UPDATE比 快UPDATE我想知道这一点,以备将来与这样的大表一起使用。

这是在Amazon RDS 中运行的 MySQL 5.5.33 上。

小智 3

我知道从 MySQL 获取更新的执行计划并不容易,因为它只提供 onSELECT语句。但线索可能在于记录更新的顺序、WHERE包含IN大量静态数据的a的评估,以及与之相关的连接的读写量、中间缓存。

该声明

UPDATE humans_we_respect SET expressed_interest=1 WHERE id IN (1,...,10000); 
Run Code Online (Sandbox Code Playgroud)

这是我们在更新大型数据库时尽量避免的一种语句,因为解析器似乎有时会对它们感到疯狂。IN ( a,b,c,...,ZZZZ )对我来说已经成为一种只适合数据中非常小的项目编号的编码风格IN。我正在开发一个开源项目,在该项目中我经常遇到所谓的“远程连接”,后半部分通常看起来与您的问题完全相同。

SELECT id FROM all_our_customers WHERE happytospam=1 AND LENGTH(email) > 6;
...
Storing result on client side as string like 
LOOP over results
$all_ids += ",$next_result";
END_LOOP
$all_ids = SUBSTRING($all_ids,1); 
ending up with a string like 
"1,2,3,4,5,8,10,100,1000,...,100000" in $all_ids
...
UPDATE humans_we_respect SET expressed_interest=1 WHERE id IN ( $all_ids )
Run Code Online (Sandbox Code Playgroud)

虽然第一部分通常执行得快如闪电,但第二部分却需要很长时间,这也是您所描述的。这些查询通常可以通过将它们重写为:

UPDATE humans_we_respect,all_our_customers 
SET humans_we_respect.expressed_interest=1 
WHERE all_our_customers.id = humans_we_respect.id 
AND all_our_customers.happytospam=1 
AND LENGTH(all_our_customers.email) > 6
Run Code Online (Sandbox Code Playgroud)

我们还用过

UPDATE humans_we_respect 
SET expressed_interest=1 
WHERE id IN ( 
SELECT id 
FROM all_our_customers 
WHERE happytospam=1 
AND LENGTH(email) > 6 
)
Run Code Online (Sandbox Code Playgroud)

它的性能比原来的好,但不如我建议的版本。

这一切都假设您使用适当的索引,其中主 ID 和组合多列索引,其中多个列经常一起使用或一起具有良好的意义,并且通常出现在您的查询中。

线索是,子句中的大量静态值IN在具有许多匹配记录的查询上几乎呈指数级增加执行时间,因为它们基本上不使用任何索引或优化,并且通常以全表扫描结束,恕我直言,执行将在其中检查将表中的每条记录/行与IN()列表中的每一项一一进行比较。

声明就像

INSERT INTO humans_we_respect (id) VALUES (1),...,(10000) ON DUPLICATE KEY UPDATE expressed_interest=1;
Run Code Online (Sandbox Code Playgroud)

然而,使用索引来定位记录然后更新它,即使不适合这种用途,由于在 ID 上使用索引(如果有一个)并且只会对 ID 进行索引查找,因此它会运行得更好记录而不是数千次比较!然而,如果 ID 列表源自同一服务器上的另一个表,则直接使用两个表可能会更快,可以使用更多优化,而且您不必将数据传入或传出 mysql 服务器进程。

就像一些额外的信息一样:

UPDATE humans_we_respect SET expressed_interest=1 WHERE id='1' OR id='2' OR ...
Run Code Online (Sandbox Code Playgroud)

是一种优化IN()元素数量非常少的查询的好技术,因为它将为每个元素创建一个并行索引查询,这对于第一个元素来说非常有用,但随着元素数量的增加,性能会下降,并且在某些时候会达到极限解析器的优化(恕我直言,一个查询中可能有 255 个元素),此时它将再次以蜗牛般的速度前进......