
SQL Server 中 WHILE 循环为什么越跑越慢?
因为每次迭代都可能触发隐式事务、重复编译或锁升级,尤其在没加 BREAK 条件或没控制扫描范围时,容易变成全表扫+行锁堆积。
- 检查循环体内是否含
SELECT *或未走索引的WHERE—— 这会让每次迭代都查一遍大表 - 确认是否在循环里反复执行
INSERT INTO @table但没设主键/索引,后期插入性能断崖下跌 - 避免在
WHILE内调用标量函数(如dbo.fn_GetValue()),它会在每行上执行,无法并行
示例:下面这段会随数据增长线性变慢
WHILE @i <h3>用 <code>SET ROWCOUNT</code> 或游标替代 <code>WHILE</code> 真的更优?</h3><p>不是。SQL Server 2012+ 中 <code>SET ROWCOUNT</code> 已不推荐用于分页逻辑,且对 <code>INSERT/UPDATE/DELETE</code> 行为有副作用;显式游标(<code>DECLARE CURSOR</code>)默认是静态、内存驻留的,大数据量下直接 OOM。</p>
- 优先用集合操作:把“逐条处理”转成“一批处理”,比如用
TOP (1000)+WHERE id > @last_id分批 - 若必须逐行,改用仅前向只读游标:
DECLARE cur CURSOR FORWARD_ONLY READ_ONLY FOR ...,避免生成临时工作表 - 注意游标中不要嵌套另一个游标——容易触发嵌套锁等待,死锁概率陡增
@@FETCH_STATUS 判断失效导致无限循环怎么办?
常见于游标未正确关闭、或 FETCH NEXT 后没立刻检查状态就做业务逻辑,结果最后一次取不到数据仍继续跑。
- 必须把
FETCH NEXT FROM cur INTO @var和IF @@FETCH_STATUS 0 BREAK写在一起,中间不能插其他语句 - 不要依赖
@@ROWCOUNT == 0判断游标结束——它反映的是上一条语句影响行数,和游标无关 - 在
WHILE循环开头就做状态判断,而不是结尾,否则最后一轮逻辑会白跑一次
错误写法:
FETCH NEXT FROM cur INTO @id -- 这里做了 UPDATE、日志记录等一堆事... IF @@FETCH_STATUS 0 BREAK -- 太晚了,逻辑已执行
批量处理时怎么避免日志暴涨和阻塞?
单次处理太多行,事务日志撑爆;太少又频繁提交,锁争用高。关键在平衡点,通常 1000–5000 行较稳,但得看具体语句复杂度和索引情况。
- 用
SET XACT_ABORT ON开头,确保出错时整个事务回滚,避免残留未提交事务 - 每批后加
WAITFOR DELAY '00:00:00.05'(50ms),给其他会话让出锁机会,特别适合 OLTP 环境 - 避免在循环内建临时表(
CREATE TABLE #t),应提前建好,否则每次编译开销叠加
真正容易被忽略的是:即使你用了分批,如果目标表上有触发器,它仍会对每批里的每一行触发——这时得评估是否关掉触发器,或改写成基于变更表(CHANGES)的异步处理。










