在多线程环境中将记录插入DB表

时间:2013-01-09 17:26:04

标签: sql tsql

我有这个TSQL代码,用于检查'sadsadsad'是否存在,如果没有,则将其插入表中。

if not exists(select id from [ua_subset_composite] where ua = 'sadsadsad')
  begin
    insert into [ua_subset_composite]
    select 'sadsadsad',1,null,null,null,null
  end

我担心的是,在生产中会同时运行多个线程,可能会出现记录在不存在的select和insert之间滑动的情况。

我不想在列上添加唯一约束,并想知道我是否可以改进此SQL代码以确保它的唯一性

5 个答案:

答案 0 :(得分:2)

解决此问题的一种方法是使用更高级别的隔离(即锁定)。您可以将整个语句包装在事务中,并使用更严格的隔离级别。

例如:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION

   <your code here>

COMMIT TRANSACTION

答案 1 :(得分:0)

您可以在数据库上实施锁定策略。你可以选择悲观:

  

当您锁定记录供您独家使用时,直到您拥有   完成它。它具有比乐观锁定更好的完整性   但要求您小心应用程序设计以避免   死锁。

或乐观:

  

您在那里阅读记录,记下版本号并检查   在您将记录写回之前,版本没有更改。当你   写回记录,过滤更新版本   确定它是原子的。 (即检查时间之间没有更新   版本并将记录写入磁盘)并更新版本   一击。

     

如果记录是脏的(即你的版本不同),你就中止了   交易和用户可以重新启动它。

source

答案 2 :(得分:0)

当您执行select时,请在所选范围内放置updlockholdlock

begin transaction

if not exists(
    select id 
    from [ua_subset_composite] with (updlock, holdlock) 
    where ua = 'sadsadsad')
  begin
    insert into [ua_subset_composite]
    select 'sadsadsad',1,null,null,null,null
  end

commit

holdlock,相当于serializable isolation level,具有以下效果:

  
      
  • 语句无法读取已修改但尚未由其他交易提交的数据。

  •   
  • 在当前交易完成之前,没有其他交易可以修改当前交易所读取的数据。

  •   
  • 其他事务无法插入新行,其键值将落在由任何语句读取的键范围内   直到当前交易完成的当前交易。

  •   
     

范围锁定位于与之匹配的键值范围内   搜索事务中执行的每个语句的条件。这个   阻止其他事务更新或插入任何行   有资格获得当前执行的任何陈述   交易。这意味着如果事务中的任何语句   第二次执行,他们将读取相同的行集。该   范围锁保持到事务完成。这是最多的   限制隔离级别因为它锁定了整个范围   密钥并保持锁定,直到事务完成。的因为   并发性较低,仅在必要时使用此选项

updlock之外还需要holdlock ...添加updlock我们会阻止单独的进程在同一范围内执行自己的select with (updlock, holdlock)语句在同一时间。

答案 3 :(得分:0)

这就是我最终做的事情

insert into [ua_subset_composite]  WITH (TABLOCKX) (ua, os)
select @r, 1 
where not exists (select 1 from [ua_subset_composite] nolock where ua = @r

为了测试代码,我从多个窗口同时运行了这段代码

declare @r nvarchar(30);
while(1=1)
begin

set @r =  convert(nvarchar(30),getdate(),21 )
insert into [ua_subset_test]  WITH (TABLOCKX) (ua, os)
select @r, 1 
where not exists (select 1 from [ua_subset_test] nolock where ua = @r

)
end

答案 4 :(得分:-1)

不幸的是,上述答案都不正确。请注意启动BEGIN TRAN SELECT的任何“锁定”解决方案。是的,如果隔离级别是SERIALIZABLE,SELECT会创建阻止其他进程更新所选数据的锁。但是如果没有选择数据呢?锁定什么?

IOW,BEGIN TRAN设置竞争条件:

/* spid */
 /* 1 */   SELECT ... -- returns no rows
 /* 2 */   SELECT ... -- returns no rows
 /* 1 */   INSERT ... -- whew! 
 /* 2 */   INSERT ... -- error

要在写入之前阅读(比如向用户显示数据),有特殊的时间戳数据类型。但在你的情况下,它只是一个插入。使用原子事务,即单个语句:

insert into [ua_subset_composite] (column1, column2)
values ('sadsadsad', 1)
where not exists (
    select 1 from ua_subset_composite 
    where column1 = 'sadsadsad'
)

服务器保证行插入或未插入。锁定是为您完成的,需要在最短的时间内由知道如何操作的人完成。 : - )

  

我不想添加唯一约束

嗯,你可能应该,你知道。上面的代码将阻止尝试添加非唯一值,并避免出现错误消息。一个独特的约束将阻止某人不小心成功