在我回答这太问题 ,我建议使用一个INSERT语句,以选择增加一个值,如下图所示。
Insert Into VersionTable
(Id, VersionNumber, Title, Description, ...)
Select @ObjectId, max(VersionNumber) + 1, @Title, @Description
From VersionTable
Where Id = @ObjectId
我认为这是因为我相信 ,如果另一插入相同的对象ID在同一时间运行该语句是安全的并发性方面,在,没有出现重复的版本号的机会。
我对么?
正如保罗写道: 不,这不是安全的 ,这是我想补充的经验证据:创建表Table_1
有一个字段ID
和值为1个的记录0
。 然后两个Management Studio中的查询窗口中同时执行以下代码:
declare @counter int
set @counter = 0
while @counter < 1000
begin
set @counter = @counter + 1
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
end
然后执行
SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1
在我的SQL Server 2008中,一个ID( 662
)创建的两倍。 因此,施加到单个语句默认隔离水平是不够的。
编辑:很明显,包裹INSERT
与BEGIN TRANSACTION
和COMMIT
不会修复它,因为事务的默认隔离级别仍是READ COMMITTED
,这是不够的。 请注意,设置事务隔离级别REPEATABLE READ
也是不够的。 使上面的代码安全的唯一方法是添加
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
在顶部。 然而,这引起死锁飘飞在我的测试。
编辑:我发现的唯一的解决方案,它是安全的,不会产生死锁(至少在我的测试)是明确锁定表专(默认事务隔离级别足以在这里)。 虽然小心; 这种解决方案可能会严重影响性能:
...loop stuff...
BEGIN TRANSACTION
SELECT * FROM Table_1 WITH (TABLOCKX, HOLDLOCK) WHERE 1=0
INSERT INTO Table_1
SELECT MAX(ID) + 1 FROM Table_1
COMMIT
...loop end...
COMMITED读的默认隔离使这个不安全的,如果完美运行相同常其中的两个,你会得到一个重复的,因为没有施加读锁。
你需要重复读或SERIALIZABLE隔离级别,使之安全。
我想你的假设是不正确。 当您查询VERSIONNUMBER表,你只把该行读锁定。 这并不妨碍其他用户从同一个表中读取同一行。 因此,可能的是两个过程,以在同一时间读取在VERSIONNUMBER表中的相同行,并产生相同的VERSIONNUMBER值。