让我们分析一下这段代码:
begin transaction
您使用默认设置开始事务READCOMMITTED
。
select top 1 @number = NextNumber FROM settings
您正在从Settings
表中选择最大的数字(顺便说一句:您应该无论如何添加一个ORDER BY
子句- 否则,不能保证排序!您可能会在这里得到意想不到的结果)。
然而,这个操作不是阻塞的——两个或多个线程可以同时读取相同的值,例如 100——SELECT
只需要一个非常短的时间段的共享锁,并且共享锁是兼容的——多个读取器可以读取同时值。
Update Settings
set NextNumber = NextNumber + 1
现在在这里,一个线程获得绿灯并将新值(在我们的示例中为 101)写回到表中。该表有一个UPDATE
独占锁(后来升级为独占锁) - 只有一个线程可以同时写入
UPDATE Data
set number = @nnumber, currentDate = GetDate(), IdUser = user_id(current_user)
FROM Data
INNER JOIN inserted on inserted.IdData = Data.IdData
同样的事情 - 一个幸运的线程可以更新Data
表,将数字设置为100
并且它正在更新的表的行被锁定直到事务结束。
commit transaction
现在,幸运线程提交了他的事务并完成了。
但是:读取相同原始值 100 的第二个(可能还有第三个、第四个、第五个......)线程仍然“在循环中” - 现在线程 #1 已经完成,这些线程中的第二个开始做它的事——它做了。它Settings
正确地将表更新为新值 102,并继续对表进行第二次更新,这里也使用它已读入其变量Data
的“当前”值....100
@number
最后,您可能有多个线程都从Settings
表中读取相同的原始值(100),并且每个线程都会将Settings
表更新为相同的“新”值(101)。
您在这里使用的这种方法在 load 下是不安全的。
可能的解决方案:
首先也是最重要的 -推荐的方法:让数据库自己处理,通过使用INT IDENTITY
表中的列(或者如果您已经在使用 SQL Server 2012 - 使用SEQUENCE
对象来处理所有同步)
如果你不能这样做——无论出于何种原因——那么至少要确保你的代码即使在繁忙的系统上也能正常工作!例如,当第一个线程到来并读取当前值时,您需要在表上SELECT .... WITH (UPDLOCK)
放置一个(独占)UPDATE
锁——这将阻止所有其他线程甚至读取“当前”值,直到第一个线程完成。或者还有其他替代方法,例如在单个操作中更新和分配旧值。Settigns
UPDATE