LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

SQL语句优化:While循环的正确使用与性能优化

admin
2026年4月15日 14:58 本文热度 48

在SQL开发中,循环处理是常见的需求,但While循环如果使用不当,会严重影响性能。今天我们来深入探讨如何正确使用While循环,以及如何优化循环操作。

一、While循环基础语法

-- 基础语法DECLARE @counter INT = 1DECLARE @maxCount INT = 10
WHILE @counter <= @maxCountBEGIN    -- 执行操作    PRINT '当前计数:' + CAST(@counter AS VARCHAR)
    -- 更新计数器(重要:避免死循环)    SET @counter = @counter + 1END

二、常见While循环场景及优化

场景1:批量插入数据(最常用)

-- ❌ 错误示范:逐行插入(性能极差)CREATE TABLE #BadExample (ID INT, Name VARCHAR(50))DECLARE @i INT = 1
WHILE @i <= 100000BEGIN    INSERT INTO #BadExample VALUES (@i'Name_' + CAST(@i AS VARCHAR))    SET @i = @i + 1END-- 耗时:约30-60秒
-- ✅ 优化方案1:批量插入(每次1000条)CREATE TABLE #GoodExample (ID INT, Name VARCHAR(50))DECLARE @i INT = 1DECLARE @BatchSize INT = 1000DECLARE @MaxCount INT = 100000
WHILE @i <= @MaxCountBEGIN    INSERT INTO #GoodExample (ID, Name)    SELECT TOP (@BatchSize        @i + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS ID,        'Name_' + CAST(@i + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS VARCHARAS Name    FROM sys.objects a, sys.objects b  -- 生成临时数据源    WHERE @i <= @MaxCount
    SET @i = @i + @BatchSizeEND-- 耗时:约2-5秒,性能提升10倍以上
-- ✅ 优化方案2:一次性插入(最佳方案)CREATE TABLE #BestExample (ID INT, Name VARCHAR(50))
;WITH Numbers AS (    SELECT TOP 100000 ROW_NUMBER() OVER (ORDER BY a.object_id) AS n    FROM sys.objects a    CROSS JOIN sys.objects b)INSERT INTO #BestExample (ID, Name)SELECT n, 'Name_' + CAST(n AS VARCHAR)FROM Numbers-- 耗时:约0.5秒,性能提升100倍以上

场景2:批量更新数据

-- ❌ 错误示范:逐行更新(性能极差)DECLARE @id INTDECLARE cur CURSOR FOR SELECT ID FROM LargeTableOPEN curFETCH NEXT FROM cur INTO @id
WHILE @@FETCH_STATUS = 0BEGIN    UPDATE LargeTable SET UpdateTime = GETDATE() WHERE ID = @id    FETCH NEXT FROM cur INTO @idENDCLOSE curDEALLOCATE cur-- 10万条数据耗时:约2-3分钟
-- ✅ 优化方案:分批更新(推荐)DECLARE @BatchSize INT = 5000DECLARE @MinID INTDECLARE @MaxID INT
SELECT @MinID = MIN(ID), @MaxID = MAX(ID) FROM LargeTable
WHILE @MinID <= @MaxIDBEGIN    UPDATE LargeTable     SET UpdateTime = GETDATE()    WHERE ID >= @MinID         AND ID < @MinID + @BatchSize
    SET @MinID = @MinID + @BatchSize
    -- 可选:添加延迟减少锁竞争    WAITFOR DELAY '00:00:00.100'END-- 10万条数据耗时:约2-5秒
-- ✅ 优化方案2:直接批量更新(如果可以一次完成)UPDATE LargeTable SET UpdateTime = GETDATE()-- 10万条数据耗时:约0.5秒

场景3:分批删除大量数据

-- ❌ 错误示范:一次性删除(可能导致事务日志爆满)DELETE FROM LargeTable WHERE CreateDate < '2024-01-01'-- 100万条数据可能导致日志暴涨或超时
-- ✅ 优化方案:分批删除(推荐)DECLARE @BatchSize INT = 10000DECLARE @RowCount INT = 1
WHILE @RowCount > 0BEGIN    DELETE TOP (@BatchSize    FROM LargeTable     WHERE CreateDate < '2024-01-01'
    SET @RowCount = @@ROWCOUNT
    -- 输出进度(可选)    PRINT '已删除 ' + CAST(@@ROWCOUNT AS VARCHAR+ ' 条记录'
    -- 检查点:减少日志压力(非生产环境慎用)    IF @RowCount > 0 AND @RowCount % 50000 = 0        CHECKPOINTEND

场景4:复杂业务逻辑处理

-- 创建示例表CREATE TABLE Orders (    OrderID INT PRIMARY KEY,    OrderDate DATETIME,    Status INT DEFAULT 0,    ProcessTime DATETIME NULL)
-- 插入测试数据INSERT INTO Orders (OrderID, OrderDate)SELECT TOP 100000 ROW_NUMBER() OVER (ORDER BY a.object_id), GETDATE()FROM sys.objects a, sys.objects b
-- ✅ 优化方案:带状态标记的分批处理CREATE PROCEDURE usp_ProcessOrdersASBEGIN    DECLARE @BatchSize INT = 1000    DECLARE @ProcessedCount INT = 0
    -- 先标记待处理数据    UPDATE Orders     SET Status = 1     WHERE Status = 0         AND OrderDate < DATEADD(day-7, GETDATE())
    WHILE 1 = 1    BEGIN        -- 批量处理        UPDATE TOP (@BatchSize) Orders        SET             Status = 2,            ProcessTime = GETDATE()        OUTPUT inserted.OrderID, inserted.OrderDate        INTO #ProcessedLog (OrderID, OrderDate)        WHERE Status = 1
        SET @ProcessedCount = @ProcessedCount + @@ROWCOUNT
        -- 检查是否处理完        IF @@ROWCOUNT < @BatchSize             BREAK
        -- 可选:添加延迟减少系统压力        WAITFOR DELAY '00:00:00.500'    END
    -- 记录处理结果    INSERT INTO ProcessLog (ProcessDate, RecordCount)    VALUES (GETDATE(), @ProcessedCount)
    SELECT @ProcessedCount AS [处理记录数]END

三、While循环性能优化技巧

技巧1:避免循环内重复查询

-- ❌ 错误:每次循环都查询系统表DECLARE @i INT = 1WHILE @i <= 1000BEGIN    IF EXISTS (SELECT 1 FROM sys.objects WHERE name = 'Table_' + CAST(@i AS VARCHAR))    BEGIN        PRINT 'Table_' + CAST(@i AS VARCHAR+ ' 已存在'    END    SET @i = @i + 1END
-- ✅ 优化:一次查询,临时存储DECLARE @i INT = 1DECLARE @Tables TABLE (TableName VARCHAR(100))INSERT INTO @Tables SELECT name FROM sys.objects WHERE name LIKE 'Table_%'
WHILE @i <= 1000BEGIN    IF EXISTS (SELECT 1 FROM @Tables WHERE TableName = 'Table_' + CAST(@i AS VARCHAR))    BEGIN        PRINT 'Table_' + CAST(@i AS VARCHAR+ ' 已存在'    END    SET @i = @i + 1END

技巧2:使用WHILE代替游标

-- ❌ 使用游标(性能差)DECLARE cur CURSOR FOR SELECT ID, Name FROM LargeTableOPEN curDECLARE @id INT@name VARCHAR(100)FETCH NEXT FROM cur INTO @id@nameWHILE @@FETCH_STATUS = 0BEGIN    -- 处理逻辑    FETCH NEXT FROM cur INTO @id@nameENDCLOSE curDEALLOCATE cur
-- ✅ 使用WHILE + 临时表(性能好)CREATE TABLE #Data (ID INT PRIMARY KEY, Name VARCHAR(100))INSERT INTO #Data SELECT ID, Name FROM LargeTable
DECLARE @id INT@name VARCHAR(100)
WHILE EXISTS (SELECT 1 FROM #Data)BEGIN    SELECT TOP 1 @id = ID, @name = Name FROM #Data
    -- 处理逻辑
    DELETE FROM #Data WHERE ID = @idEND

技巧3:添加进度监控

-- 创建进度监控存储过程CREATE PROCEDURE usp_BatchProcess    @TotalCount INT,    @BatchSize INT = 1000ASBEGIN    DECLARE @CurrentCount INT = 0    DECLARE @BatchCount INT = 0    DECLARE @StartTime DATETIME = GETDATE()
    WHILE @CurrentCount < @TotalCount    BEGIN        SET @BatchCount = @BatchCount + 1
        -- 执行批量操作        -- ... 业务逻辑 ...
        SET @CurrentCount = @CurrentCount + @BatchSize
        -- 每10批输出一次进度        IF @BatchCount % 10 = 0        BEGIN            DECLARE @Percent DECIMAL(5,2= @CurrentCount * 100.0 / @TotalCount            DECLARE @ElapsedSeconds INT = DATEDIFF(SECOND@StartTime, GETDATE())            DECLARE @EstimateSeconds INT = @ElapsedSeconds * 100 / @Percent
            PRINT CONVERT(VARCHAR, GETDATE(), 120+                   ' - 进度:' + CAST(@Percent AS VARCHAR+ '%' +                  ' - 已处理:' + CAST(@CurrentCount AS VARCHAR+                  ' - 预计剩余:' + CAST(@EstimateSeconds - @ElapsedSeconds AS VARCHAR+ '秒'        END    END
    PRINT '处理完成,总耗时:' +           CAST(DATEDIFF(SECOND@StartTime, GETDATE()) AS VARCHAR+ '秒'END

四、While循环的适用场景与替代方案

场景决策树

需要循环处理?    ↓是否可以用集合操作? → 是 → 使用UPDATE/INSERT/SELECT(最优)    ↓ 否数据量是否<1000? → 是 → While循环可接受    ↓ 否是否需要分批处理(防日志爆满)? → 是 → While分批(推荐)    ↓ 否是否需要复杂业务逻辑? → 是 → While循环(可接受)    ↓ 否→ 重新设计,用集合操作

替代方案对比

方案
适用场景
性能
复杂度
集合操作(SET-based)
纯数据操作
⭐⭐⭐⭐⭐
分批While循环
大数据量、防日志
⭐⭐⭐⭐
While + 临时表
复杂业务逻辑
⭐⭐⭐
游标
逐行复杂处理
递归CTE
层级数据处理
⭐⭐⭐

五、实战案例:订单状态批量更新

-- 完整示例:订单状态批量更新CREATE PROCEDURE usp_BatchUpdateOrderStatus    @BatchSize INT = 5000,    @MaxBatchCount INT = 100,    @DelayMs INT = 100  -- 批次间延迟(毫秒)ASBEGIN    SET NOCOUNT ON
    DECLARE @BatchCount INT = 0    DECLARE @ProcessedCount INT = 0    DECLARE @StartTime DATETIME = GETDATE()
    -- 创建进度表    CREATE TABLE #Progress (        BatchNo INT,        ProcessedCount INT,        BatchTime DATETIME,        DurationMs INT    )
    WHILE @BatchCount < @MaxBatchCount    BEGIN        DECLARE @BatchStartTime DATETIME = GETDATE()        SET @BatchCount = @BatchCount + 1
        -- 分批更新        UPDATE TOP (@BatchSize) Orders        SET             Status = 1,            ProcessDate = GETDATE()        WHERE Status = 0            AND OrderDate < DATEADD(day-30, GETDATE())
        DECLARE @AffectedRows INT = @@ROWCOUNT        SET @ProcessedCount = @ProcessedCount + @AffectedRows
        -- 记录进度        INSERT INTO #Progress (BatchNo, ProcessedCount, BatchTime, DurationMs)        VALUES (            @BatchCount,            @AffectedRows,            GETDATE(),            DATEDIFF(ms, @BatchStartTime, GETDATE())        )
        -- 打印进度        PRINT CONVERT(VARCHAR, GETDATE(), 120+               ' - 第' + CAST(@BatchCount AS VARCHAR+ '批' +              ' - 处理:' + CAST(@AffectedRows AS VARCHAR+ '条' +              ' - 累计:' + CAST(@ProcessedCount AS VARCHAR+ '条'
        -- 如果没有更多数据,退出循环        IF @AffectedRows < @BatchSize            BREAK
        -- 批次间延迟,减少系统压力        IF @DelayMs > 0 AND @BatchCount < @MaxBatchCount            WAITFOR DELAY '00:00:00.' + RIGHT('000' + CAST(@DelayMs AS VARCHAR), 3)    END
    -- 输出汇总信息    DECLARE @TotalDuration INT = DATEDIFF(second@StartTime, GETDATE())
    PRINT ''    PRINT '========== 处理完成 =========='    PRINT '总批次数:' + CAST(@BatchCount AS VARCHAR)    PRINT '总处理数:' + CAST(@ProcessedCount AS VARCHAR)    PRINT '总耗时:' + CAST(@TotalDuration AS VARCHAR+ '秒'    PRINT '平均每批:' + CAST(@TotalDuration * 1000 / @BatchCount AS VARCHAR+ '毫秒'
    -- 返回详细进度    SELECT         BatchNo AS 批次,        ProcessedCount AS 处理条数,        BatchTime AS 处理时间,        DurationMs AS 耗时毫秒    FROM #Progress    ORDER BY BatchNo
    DROP TABLE #ProgressEND

六、While循环性能对比测试

-- 性能对比测试脚本CREATE PROCEDURE usp_CompareWhilePerformanceASBEGIN    SET NOCOUNT ON
    -- 测试数据准备    IF OBJECT_ID('tempdb..#TestData'IS NOT NULL DROP TABLE #TestData    CREATE TABLE #TestData (ID INT PRIMARY KEYValue VARCHAR(100))
    -- 插入10万条测试数据    ;WITH Numbers AS (        SELECT TOP 100000 ROW_NUMBER() OVER (ORDER BY a.object_id) AS n        FROM sys.objects a, sys.objects b    )    INSERT INTO #TestData (ID, Value)    SELECT n, 'Value_' + CAST(n AS VARCHARFROM Numbers
    -- 测试1:逐行更新    PRINT '测试1:逐行更新'    DECLARE @StartTime DATETIME = GETDATE()    DECLARE @i INT = 1
    WHILE @i <= 100000    BEGIN        UPDATE #TestData SET Value = 'Updated_' + CAST(@i AS VARCHARWHERE ID = @i        SET @i = @i + 1    END
    PRINT '逐行更新耗时:' + CAST(DATEDIFF(ms, @StartTime, GETDATE()) AS VARCHAR+ 'ms'
    -- 重置数据    UPDATE #TestData SET Value = 'Value_' + CAST(ID AS VARCHAR)
    -- 测试2:分批更新    PRINT '测试2:分批更新(每批1000条)'    SET @StartTime = GETDATE()    SET @i = 1
    WHILE @i <= 100000    BEGIN        UPDATE #TestData         SET Value = 'Batch_' + CAST(@i AS VARCHAR)        WHERE ID BETWEEN @i AND @i + 999
        SET @i = @i + 1000    END
    PRINT '分批更新耗时:' + CAST(DATEDIFF(ms, @StartTime, GETDATE()) AS VARCHAR+ 'ms'
    -- 测试3:一次性更新    PRINT '测试3:一次性更新'    SET @StartTime = GETDATE()
    UPDATE #TestData SET Value = 'Set_Updated'
    PRINT '一次性更新耗时:' + CAST(DATEDIFF(ms, @StartTime, GETDATE()) AS VARCHAR+ 'ms'END
-- 执行测试EXEC usp_CompareWhilePerformance

七、最佳实践总结

✅ 推荐做法

  1. 优先考虑集合操作:能用一条SQL解决的,绝不用循环

  2. 分批处理大数据:使用WHILE + TOP/BETWEEN分批操作

  3. 添加进度监控:让运维人员了解执行进度

  4. 控制批次大小:根据数据量调整BatchSize(通常1000-10000)

  5. 添加延迟:WAITFOR避免长时间锁表

❌ 避免做法

  1. 循环内使用游标:游标 + WHILE = 性能灾难

  2. 循环内使用SELECT*FROM:重复查询浪费资源

  3. 无限制循环:忘记更新计数器导致死循环

  4. 循环内使用DISTINCT/ORDER BY:每次都排序

  5. 忽略事务日志:大事务不分批导致日志暴涨

总结

数据量
推荐方案
示例
<1000条
直接集合操作
UPDATE...WHERE
1000-10万条
分批While循环
每批1000-5000条
>10万条
分批+索引优化
每批5000-10000条
复杂业务逻辑
While + 临时表
先标记,后处理

核心原则:能用集合不用循环,必须循环时控制批次和进度。


阅读原文:原文链接


该文章在 2026/4/15 18:19:39 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号