应使用 insert ... on duplicate key update 批量更新,比循环单条 update 快5–10倍;需确保有唯一索引,一次不超过1000行;避免 where in 大列表,改用临时表 join;务必走索引防锁表。

用 INSERT ... ON DUPLICATE KEY UPDATE 替代逐条 UPDATE
单条 UPDATE 在百万级数据上跑几万次,IO 和网络开销会直接拖垮性能。MySQL 原生支持批量“存在则更新、不存在则忽略(或插入)”的写法,比循环发 UPDATE 快 5–10 倍。
常见错误是误以为 ON DUPLICATE KEY UPDATE 只能配合 INSERT,其实它本质是利用唯一索引冲突触发更新逻辑,只要表有 PRIMARY KEY 或 UNIQUE 约束就能用。
- 必须确保目标字段上有唯一约束,否则不触发更新,变成纯插入(可能报错或静默失败)
- 一次最多写 1000 行左右 —— 太大会触发
max_allowed_packet限制,报错Packets larger than max_allowed_packet are not allowed - 如果只更新部分字段,
VALUES(col)引用的是当前这一行的输入值,不是原表值;想保留原值就写col = col
INSERT INTO user_score (user_id, score, updated_at) VALUES (123, 95, NOW()), (456, 87, NOW()), (789, 91, NOW()) ON DUPLICATE KEY UPDATE score = VALUES(score), updated_at = VALUES(updated_at);
避免在 WHERE IN 中塞几万个 ID
UPDATE t SET status=1 WHERE id IN (1,2,3,...) 看起来简洁,但 ID 列表超 5000 个后,MySQL 解析和执行计划会明显变慢,还容易触发 max_allowed_packet 或内存溢出。
真正可行的替代方式是把 ID 先写进临时表,再用 JOIN 更新 —— 这样走索引快,语句长度可控,也方便分批。
- 临时表必须建
INDEX或PRIMARY KEY,否则JOIN变全表扫描 - 用
CREATE TEMPORARY TABLE,避免污染正式表空间,且会话结束自动清理 - 如果原始 ID 来自应用层,别拼 SQL 字符串,改用
LOAD DATA INFILE或批量INSERT导入临时表
CREATE TEMPORARY TABLE tmp_ids (id BIGINT PRIMARY KEY); INSERT INTO tmp_ids VALUES (123), (456), (789); UPDATE user_info u JOIN tmp_ids t ON u.id = t.id SET u.status = 1;
注意 UPDATE 的锁行为:别让批量操作卡住线上查询
批量 UPDATE 默认加行级锁,但若没走索引,会升级为表锁;更危险的是,它还会阻塞后续的 SELECT ... FOR UPDATE 和其他写操作。线上环境常因此出现慢查询堆积。
关键不是“少更新”,而是“让 MySQL 尽快定位到要改的行”。索引缺失、类型隐式转换、函数包裹字段,都会让优化器放弃走索引。
- 检查执行计划:用
EXPLAIN FORMAT=TREE看是否显示using_index_condition或access_type: range - 别在
WHERE里写WHERE DATE(created_at) = '2024-01-01',改用created_at >= '2024-01-01' AND created_at - 字符串比较时注意字符集和排序规则,
utf8mb4_0900_as_cs和utf8mb4_general_ci混用会导致索引失效
分批更新时,用主键范围比 LIMIT 更可靠
很多人用 UPDATE ... LIMIT 1000 分页,但并发执行时容易漏数据或重复更新 —— 因为 LIMIT 不保证顺序,且没事务隔离保障。
更稳的方式是按主键切片:每次取一个 id 区间,更新完记录最大 id,下一批从它开始。这样可重复、可中断、无竞态。
- 别用
OFFSET配合LIMIT,偏移越大越慢,底层仍要扫描前面所有行 - 区间边界用
BETWEEN或>= AND ,后者更容易复用上一批末尾值 - 每批大小建议 500–5000 行,太小事务开销大,太大可能超锁等待超时(
innodb_lock_wait_timeout)
UPDATE orders SET status = 2 WHERE id >= 100001 AND id 分批逻辑本身不难,难的是边界判断和状态跟踪 —— 特别是跨多个字段组合做条件更新时,很容易漏掉某些分片,或者重复扫同一行。上线前务必用小范围数据验证最终行数是否对得上。











