必须分批+显式事务+索引支撑:MySQL用UPDATE JOIN或临时表,SQL Server用MERGE,均需主键升序分批、每批2000行起调优、复合索引及TRY/CATCH或HANDLER错误捕获。

直接用单条 UPDATE 更新上万行,在生产环境基本等于主动触发数据库雪崩——锁表、日志暴涨、超时中断、SSMS 卡死都是标配。真要跑通,必须拆批 + 显式事务 + 索引支撑。
MySQL 存储过程里别写循环 UPDATE,改用 JOIN 或临时表
常见错误是把应用层思维带进存储过程:用游标逐行取 ID,再对每行执行一次 UPDATE。这在百万级数据下会慢到不可接受,且极易死锁。
- 优先用
UPDATE ... JOIN语法,例如:UPDATE orders o JOIN temp_batch t ON o.id = t.id SET o.status = t.new_status—— 注意 MySQL 不支持UPDATE ... FROM写法,写错直接报ERROR 1064 - 大批量时先建临时表:
CREATE TEMPORARY TABLE temp_batch (id INT PRIMARY KEY, new_status TINYINT),再INSERT INTO temp_batch VALUES (1,1),(2,2),...,最后 JOIN 更新 - 避免
WHERE id IN (SELECT ...)形式,MySQL 5.7 及以前版本可能锁全表;改成 JOIN 后能走索引,且 InnoDB 只锁匹配行
SQL Server 必须用 MERGE,别硬写 UPDATE JOIN
SQL Server 不允许 UPDATE t1 JOIN t2 这种写法,硬写会直接报错 Incorrect syntax near the keyword 'JOIN'。MERGE 是唯一安全、原生支持“存在则更新、不存在则插入”的批量操作语法。
- 基础结构必须完整:
MERGE target_table AS t USING source_data AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.col = s.val—— 漏掉ON或WHEN MATCHED都会语法报错 -
source_data推荐用表变量(@batch TABLE(id INT, val VARCHAR(50)))或 JSON(SQL Server 2016+),别拼逗号字符串,防注入也利于索引 - 每批
MERGE必须包裹在BEGIN TRY ... BEGIN TRANSACTION ... COMMIT块里,否则异常后部分数据已提交,无法回滚
所有数据库都得按主键升序分批,且每批显式事务
不分批或批次乱序,是死锁和超时的头号来源。哪怕只更新两行,不同会话按不同顺序访问,InnoDB 或 SQL Server 都可能升级为间隙锁冲突。
- SQL Server 示例:用
TOP (5000)+ 主键推进,每次更新后查SELECT @next_id = MIN(id) FROM t WHERE id > @last_id AND status = 'pending',并加IF @@ROWCOUNT = 0 BREAK - MySQL 示例:用变量模拟游标,但必须
ORDER BY id且SET @row_index := -1每次重置,否则第二次运行会漏数据 - 批次大小建议从 2000 起调优:太小(如 100)事务开销占比高;太大(如 10000)容易触发锁等待超时或日志满
-
status和id字段必须有复合索引,否则每次WHERE都是全表扫描
事务边界和错误处理不是可选项,是必填项
没加事务控制的分批,失败时根本不知道卡在哪一批;没加错误捕获的存储过程,一次死锁就整条流程中断,后续批次全丢。
- SQL Server 必须用
TRY/CATCH捕获错误 1205(死锁),记录日志后重试,而不是让整个过程崩掉 - MySQL 存储过程中,
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION是刚需,否则异常直接退出,不执行ROLLBACK - PostgreSQL 的 DO 块不能裸写批量更新,必须封装进函数并设
SECURITY DEFINER,否则事务自动提交不可控 - Oracle 推荐用
BULK COLLECT+FORALL,减少上下文切换,但要注意SAVE EXCEPTIONS开关,否则一条失败整批回滚
真正麻烦的从来不是语法怎么写,而是每批执行后要不要等 WAL 刷盘、是否触发 autovacuum、主从延迟会不会被拉长——这些细节不压测根本看不出问题。

















