SQL Server存储过程并发触发快照隔离更新冲突,能否按FIFO排队执行?
报错原因
你遇到的错误是SQL Server快照隔离级别的正常机制:快照隔离属于乐观并发控制,事务启动时会读取数据的行版本,不会加锁,等到实际执行更新操作时,才会校验该行是否在事务启动后被其他事务修改过,如果已被修改就会抛出更新冲突错误。
可选择的解决方案
方案1:精准加行锁(最推荐,性能影响最小)
如果你的冲突仅出现在多个请求同时更新同一个@IdJournee对应的行的场景,不需要全局串行,仅需要在更新语句上加锁提示即可:
update Journee WITH (UPDLOCK, HOLDLOCK) set UVGlobalJournee = (select case when max(E.UVGlobale) is null then 0 else max(E.UVGlobale) end from Examen E inner join IT_Journee_Examen IT on E.IdExamen = IT.IDEXAMEN where Journee.IdJournee = IT.IDJOURNEE) where Test = '1' and Journee.IdJournee = @IdJournee
UPDLOCK提示会在读取行时就加上互斥的更新锁,第一个获取锁的事务执行完成之前,其他同IdJournee的更新请求会进入等待状态,不会触发快照冲突,不同IdJournee的更新请求互不影响,性能损失极小。
方案2:全局FIFO串行执行
如果确实需要所有存储过程调用严格按先进先出顺序执行,不管更新的IdJournee是否相同,可以使用SQL Server自带的应用程序锁实现全局排队:
CREATE PROCEDURE [你的存储过程名称] @IdJournee INT -- 原有参数 AS BEGIN SET NOCOUNT ON; -- 请求全局独占锁,锁名称自定义为全局唯一值即可,@LockTimeout=-1表示无限等待 EXEC sp_getapplock @Resource = 'Global_Lock_Journee_Update_Proc', @LockMode = 'Exclusive', @LockOwner = 'Transaction', @LockTimeout = -1; -- 原有更新逻辑 update Journee set UVGlobalJournee = (select case when max(E.UVGlobale) is null then 0 else max(E.UVGlobale) end from Examen E inner join IT_Journee_Examen IT on E.IdExamen = IT.IDEXAMEN where Journee.IdJournee = IT.IDJOURNEE) where Test = '1' and Journee.IdJournee = @IdJournee; -- 显式释放锁,事务提交时也会自动释放 EXEC sp_releaseapplock @Resource = 'Global_Lock_Journee_Update_Proc', @LockOwner = 'Transaction'; END
该方案可以实现严格的FIFO执行,缺点是所有请求串行执行,并发量较高时性能下降明显。
方案3:调整隔离级别或增加重试逻辑
- 可以单独将该存储过程的事务隔离级别调整为默认的读提交,避免快照隔离的乐观冲突,读提交级别会自动等待行锁释放,不会抛出冲突错误:在存储过程开头添加
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;即可。 - 如果冲突概率极低,也可以在应用层捕获错误码为3960的快照冲突异常,延迟1~3秒后重试存储过程调用即可。
内容的提问来源于stack exchange,提问作者Patrice Hinodev
相关产品推荐
相关产品推荐

