直接拆成小事务并显式commit是唯一能同时缓解锁升级、日志暴涨和超时的通用解法,因为未结束事务会阻止日志截断、持续持有锁资源,导致log_reuse_wait_desc卡在active_transaction、日志使用率超90%、ssms卡死及锁升级阻塞其他操作。

直接拆成小事务并显式 COMMIT,是唯一能同时缓解锁升级、日志暴涨和超时的通用解法。
为什么单个大事务必然导致日志满和挂起
只要事务没结束,SQL Server 就不能截断日志——所有已提交但未 COMMIT 的日志记录都必须保留,log_reuse_wait_desc 会卡在 ACTIVE_TRANSACTION。此时 DBCC SQLPERF(logspace) 显示日志使用率持续 >90%,后续 DML 全部阻塞,SSMS 卡在“正在执行”,甚至触发 Error: 9002。这不是磁盘不够,而是事务机制本身不允许。
SQL Server 存储过程里安全分批的硬性要求
用 TOP (5000) + 主键推进,别用 OFFSET/FETCH:后者每次扫描前 N 行,10 万行后性能断崖下跌。
- 必须带
ORDER BY id,否则TOP行为不可预测,漏更新或重复更新都可能发生 -
WHERE条件字段(如status = 'pending')必须建索引,否则每批都是全表扫描 - 每次更新后,用
SELECT @min_id = MIN(id) FROM table WHERE id > @min_id AND status = 'pending'刷新起点 - 加
IF @@ROWCOUNT = 0 BREAK,防止无匹配行时无限循环 - 每批执行完立刻
COMMIT,绝不依赖隐式提交或外部控制
MySQL 存储过程中避免变量残留和漏处理
MySQL 不支持带 LIMIT 的直接分页更新,靠变量模拟游标极易出错:
- 子查询中
@row_index必须配合ORDER BY id,否则顺序不保,UPDATE乱序执行 - 每次调用前必须重置变量,例如
SET @row_index := -1,否则第二次从上次结束位置继续 - 并发场景下可能漏行或重复处理,建议加
SELECT ... FOR UPDATE预占,或由应用层加分布式锁 - 绝对不要用
UPDATE ... LIMIT直接更新——它不保证按索引顺序执行,且无法跨语句复用偏移量
日志满了光 SHRINKFILE 没用,关键在事务控制层
收缩 .ldf 文件只是物理空间回收,前提是日志已被截断。如果 log_reuse_wait_desc 是 REPLICATION 或 ACTIVE_TRANSACTION,DBCC SHRINKFILE 永远无效。真正起效的操作永远在事务控制层:查 DBCC OPENTRAN 看长事务,查 sys.databases 确认等待原因,必要时清理还原库遗留的复制配置(sp_removedbreplication)。简单恢复模式切换虽快,但会丢失时间点恢复能力,生产环境慎用。










