百万级数据更新需分批处理:SQL Server用TOP+主键推进并建复合索引,MySQL用变量游标+ORDER BY,均须显式事务、错误处理、合理批次(2000起)、索引优化及执行计划验证。

直接在存储过程中对百万级以上数据执行单次 UPDATE,基本等于给数据库“上刑”——锁升级、日志爆满、超时断连、SSMS卡死不动都是常态。真正有效的解法不是调优单条语句,而是用分段 + 批量 + 显式事务控制把大操作切成可控小块。
SQL Server 中用 TOP + 主键推进实现安全分段更新
别碰 OFFSET/FETCH 做分页更新,它每次都要扫描前 N 行,10 万行后性能断崖式下跌。稳定做法是基于主键或时间戳推进:
-
DECLARE @min_id BIGINT = (SELECT MIN(id) FROM orders WHERE status = 'pending')—— 起始点必须查一次,不能硬编码 - 循环内用
UPDATE TOP (5000) ... WHERE id >= @min_id AND status = 'pending' ORDER BY id——ORDER BY不可省,否则TOP行为无定义 - 更新完立刻刷新
@min_id:SELECT @min_id = MIN(id) FROM orders WHERE status = 'pending' AND id > @min_id - 加
IF @@ROWCOUNT = 0 BREAK,防止空结果集导致无限循环 -
status和id字段必须有复合索引,否则每次WHERE都全表扫
MySQL 中用变量游标模拟分批,避开 LIMIT 直接 UPDATE 的限制
MySQL 不允许 UPDATE ... LIMIT 在子查询中直接关联源表,但可以用变量构造逻辑游标:
- 每次执行前重置变量:
SET @row_index := -1,否则第二次运行会从上次位置继续,漏数据 - 子查询必须带
ORDER BY id,否则@row_index分配顺序不可控 - 写法示例:
UPDATE orders SET status = 'processed' WHERE id IN (SELECT id FROM (SELECT id, @row_index := @row_index + 1 AS row_num FROM orders WHERE status = 'pending' ORDER BY id LIMIT 5000) AS t) - 并发场景下可能漏行或重复,建议加
SELECT ... FOR UPDATE预占,或由应用层用分布式锁协调
所有数据库都必须处理的事务边界与错误陷阱
分批不等于安全。没加事务控制或错误处理,失败时根本不知道卡在哪一批:
- 每批必须显式
BEGIN TRANSACTION→ 执行 →COMMIT或ROLLBACK,不能依赖自动提交 - SQL Server 必须包在
TRY...CATCH块里,捕获死锁(错误 1205)后记录并重试,而不是让整个过程崩掉 - MySQL 存储过程中,
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION是刚需,否则异常直接中断流程 - 批次大小不是越小越稳:500 行太碎,网络和事务开销反升;10000 行又容易触发锁等待超时。实测从 2000 行起步,在预发环境压测调整
- 更新前后检查
@@ROWCOUNT(SQL Server)或ROW_COUNT()(MySQL),确认是否真有行被改,避免 WHERE 条件写错却以为成功
最常被跳过的其实是索引维护和隔离级别。哪怕分段逻辑完全正确,如果 WHERE 字段没索引,或者事务卡在 REPEATABLE READ 级别上,照样锁表数分钟。上线前务必查 EXPLAIN 或执行计划,确认走的是索引查找,不是全表扫描。


















