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
相关产品推荐
相关产品推荐

