You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQL Server 2008游标生成唯一编号重复问题咨询

问题分析与解决方案

首先可以明确:大概率不是SQL Server的更新导致了这个重复编号问题——微软对SQL Server 2008的更新极少会破坏游标锁这类基础功能,更可能是你最近的并发场景发生了变化(比如同时调用的用户量增加、调用频率变高),触发了存储过程本身存在的并发漏洞。

你的存储过程为什么会在高并发下生成重复编号?

你当前的实现用了游标+FOR UPDATE,但这个流程不是原子操作:

  • 多个会话可以同时打开游标,读取到同一个初始PROG值
  • 从FETCH NEXT获取值到执行UPDATE WHERE CURRENT OF之间存在时间窗口,在这个窗口内其他会话也能读到相同的PROG
  • 最终多个会话都会把PROG加1,导致返回重复的编号

哪怕SCROLL_LOCKS能确保游标滚动时数据不被修改,但它无法阻止多个会话同时读取同一个初始值。

如何修复这个问题?

你需要把“读取当前值+更新+返回新值”变成原子操作,这样多个并发会话会排队执行,不会出现重复。针对SQL Server 2008,最简洁可靠的方式是使用OUTPUT子句:

DECLARE @NextProg DECIMAL(20,0);

UPDATE PROGRESSIVI
SET PROG = PROG + 1
OUTPUT inserted.PROG INTO @NextProg
WHERE sezione='DUCATO' AND ISTPR=5034;

SELECT @NextProg;

这个写法中,更新和获取新值是一步完成的,SQL Server会自动处理并发锁,确保每个会话都拿到唯一的递增编号。

如果你更倾向于保留“先读再更”的逻辑,也可以通过添加锁提示来确保读取时就锁定行,阻止其他会话读取:

DECLARE @PROG AS DECIMAL(20,0);

BEGIN TRANSACTION;

-- 用UPDLOCK+HOLDLOCK确保读取时就加排他锁,直到事务结束
SELECT @PROG = PROG
FROM PROGRESSIVI WITH (UPDLOCK, HOLDLOCK)
WHERE sezione='DUCATO' AND ISTPR=5034;

UPDATE PROGRESSIVI
SET PROG = @PROG + 1
WHERE sezione='DUCATO' AND ISTPR=5034;

SELECT @PROG + 1;

COMMIT TRANSACTION;

额外说明

你的存储过程之前稳定运行9年,是因为之前的并发量没达到触发漏洞的阈值——当多个用户在同一秒调用时,这个时间窗口就被触发了。建议尽快替换成原子操作的实现,避免重复编号带来的业务问题。

内容的提问来源于stack exchange,提问作者Giovanni Tardino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:49:44