存储过程执行过慢求优化:从VARCHAR类型Marks列派生计算列
绝对不用等!用批量更新替代逐行循环,效率提升N倍
你的问题根源在于逐行循环+多次单条更新的方式——34万条数据要执行34万次查询+3*34万次更新,这会产生巨量的磁盘IO和事务日志开销,速度慢是必然的。SQL是为集合操作设计的,完全可以用单条语句一次性完成所有计算和更新,速度会快到离谱。
最优方案:单条批量UPDATE语句
直接对整个表执行一次更新,所有计算逻辑在SET子句中完成,不需要任何循环或变量:
UPDATE [dbo].[Drv Tevta 3 Full] SET [Marked Obtained] = CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT), [Total Marks] = CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT), [Average] = (CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT) * 100) / CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT)
为什么这更快?
- 只需要扫描表一次,而不是34万次
- 所有更新操作批量执行,大幅减少磁盘IO和事务日志生成
- 避免了循环变量的维护、逐行查询的额外开销
健壮性优化:处理异常数据
如果你的Marks列存在格式不规范的记录(比如没有/、空值、非数字内容),上面的语句会报错。可以用TRY_CAST和条件过滤来避免问题:
UPDATE [dbo].[Drv Tevta 3 Full] SET [Marked Obtained] = TRY_CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT), [Total Marks] = TRY_CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT), [Average] = CASE -- 避免除以0的情况 WHEN TRY_CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT) <> 0 THEN (TRY_CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT) * 100) / TRY_CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT) ELSE NULL END -- 只处理包含/的有效记录 WHERE CHARINDEX('/', [Marks]) > 0
超大表备选:分批更新
如果表实在太大(比如超过百万级),担心一次性更新会占满事务日志或锁表,可以分批处理,每次更新1万条:
DECLARE @BatchSize INT = 10000; DECLARE @UpdatedRows INT = 1; WHILE @UpdatedRows > 0 BEGIN UPDATE TOP(@BatchSize) [dbo].[Drv Tevta 3 Full] SET [Marked Obtained] = CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT), [Total Marks] = CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT), [Average] = (CAST(SUBSTRING([Marks], 0, CHARINDEX('/', [Marks])) AS INT) * 100) / CAST(SUBSTRING([Marks], CHARINDEX('/', [Marks]) + 1, 10) AS INT) -- 只更新未处理过的记录(假设新增列初始为NULL) WHERE [Marked Obtained] IS NULL; SET @UpdatedRows = @@ROWCOUNT; END
总结
绝对不要继续用原有的循环方式,这是对SQL集合操作能力的浪费。用上面的批量更新方案,34万条数据应该在几秒到几十秒内就能完成,而不是等几个小时甚至更久。
内容的提问来源于stack exchange,提问作者MakesReal
相关产品推荐
相关产品推荐

