在TSQL是用Select语句并发的安全方面的插入?在TSQL是用Select语句并发的安全方面的插

2019-05-12 03:11发布

在我回答这太问题 ,我建议使用一个INSERT语句,以选择增加一个值,如下图所示。

Insert Into VersionTable 
(Id, VersionNumber, Title, Description, ...) 
Select @ObjectId, max(VersionNumber) + 1, @Title, @Description 
From VersionTable 
Where Id = @ObjectId 

我认为这是因为我相信 ,如果另一插入相同的对象ID在同一时间运行该语句是安全的并发性方面,在,没有出现重复的版本号的机会。

我对么?

Answer 1:

正如保罗写道: 不,这不是安全的 ,这是我想补充的经验证据:创建表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 )创建的两倍。 因此,施加到单个语句默认隔离水平是不够的。


编辑:很明显,包裹INSERTBEGIN TRANSACTIONCOMMIT不会修复它,因为事务的默认隔离级别仍是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...


Answer 2:

COMMITED读的默认隔离使这个不安全的,如果完美运行相同常其中的两个,你会得到一个重复的,因为没有施加读锁。

你需要重复读或SERIALIZABLE隔离级别,使之安全。



Answer 3:

我想你的假设是不正确。 当您查询VERSIONNUMBER表,你只把该行读锁定。 这并不妨碍其他用户从同一个表中读取同一行。 因此,可能的是两个过程,以在同一时间读取在VERSIONNUMBER表中的相同行,并产生相同的VERSIONNUMBER值。



Answer 4:

  • 你需要(同上,VERSIONNUMBER)唯一约束来强制执行

  • 我会使用ROWLOCK,XLOCK提示来阻止其他人读锁定的行,你算

  • 或包裹INSERT在TRY / CATCH。 如果我得到一个副本,再试一次...



文章来源: In tsql is an Insert with a Select statement safe in terms of concurrency?