唯一可靠批量更新方式是case when写在update的set子句中,必须显式写else column_name防null,多字段需各自独立case,where应先过滤以提升性能,且所有分支返回类型须一致。

UPDATE 本身不支持“批量传参”式更新(比如像 INSERT 那样一次写多组 VALUES),但可以通过几种可靠方式实现单条 SQL 完成多行更新——这才是真正高效、安全的“批量更新”。
用 CASE WHEN 实现小批量条件更新
适合几十到几百条记录,逻辑清晰、无需建表、事务原子性强。
常见错误是漏写 WHERE id IN (...),导致全表扫描或误更新其他行;或者 CASE 没写 ELSE,把非目标行的字段设为 NULL。
UPDATE users SET status = CASE WHEN id = 1 THEN 2 WHEN id = 2 THEN 3 WHEN id = 3 THEN 1 ELSE status END WHERE id IN (1, 2, 3);- 必须用
WHERE id IN (...)限制作用范围,否则CASE外的行会被设为NULL(除非显式写ELSE status) - 每增加一个分支,SQL 长度线性增长,超过 1000 行后解析和执行开销明显上升
- 不能用于需要关联计算的场景(比如新值来自另一张表的聚合结果)
用临时表 + JOIN 更新大批量数据
适合几千到百万级更新,尤其当新值来自复杂逻辑或外部输入时。
容易踩的坑:忘记给临时表加主键或唯一索引,导致 JOIN 结果重复或性能暴跌;或者没控制事务大小,一次提交太大引发锁超时。
- 先建临时表:
CREATE TEMPORARY TABLE temp_updates (id BIGINT PRIMARY KEY, new_score INT, updated_at DATETIME); - 批量插入待更新数据:
INSERT INTO temp_updates VALUES (1, 85, NOW()), (2, 92, NOW()), ...; - 执行更新:
UPDATE users u JOIN temp_updates t ON u.id = t.id SET u.score = t.new_score, u.updated_at = t.updated_at; -
TEMPORARY表会话结束自动销毁,不用手动DROP;但务必加PRIMARY KEY或UNIQUE约束,否则JOIN可能产生笛卡尔积
用 INSERT ... ON DUPLICATE KEY UPDATE 替代更新
本质是“存在则更新,不存在则插入”,适合有唯一键约束(主键/唯一索引)且允许插入新记录的场景。
不是所有批量更新都适用——如果只允许修改已有数据,而不能新增,这个方案会污染主表。
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'a@x'), (2, 'Bob', 'b@x') ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email);- 冲突检测只基于主键或唯一索引,普通字段变更不会触发
UPDATE分支 -
VALUES(col)是占位符,表示该次INSERT中对应列的值,不是函数调用 - 返回影响行数:1=插入,2=更新,n=混合总数;可用于判断是否意外插入了新行
分批更新大表时如何避免锁表和超时
直接对千万级表执行无 LIMIT 的 UPDATE 极易锁表、拖垮线上服务。
关键不是“怎么写 SQL”,而是“怎么控制执行节奏”——很多人忽略 ORDER BY 和主键连续性,导致跳过或重复更新。
- 按主键分片:
UPDATE users SET status = 1 WHERE id BETWEEN 10000 AND 19999 AND status != 1; - 每次更新后加
SLEEP(0.1)(应用层控制),避免瞬间压垮 IO - 务必用
WHERE ... AND status != 1这类条件,防止重复执行时反复更新已处理行 - 不要依赖
LIMIT+OFFSET分页,主键不连续时会漏数据;优先用WHERE id > last_id LIMIT 5000游标式分页
真正麻烦的从来不是语法,而是更新范围是否精准、锁粒度是否可控、失败后能否幂等重试。写完 SQL 后,先用 SELECT 模拟 WHERE 条件查一遍,再加事务包装,最后才执行。











