0

我有这个 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

我担心的是,在将有多个线程同时运行的生产环境中,可能会出现记录将在不存在选择和插入之间滑过的情况。

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

4

5 回答 5

2

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

例如:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION

   <your code here>

COMMIT TRANSACTION
于 2013-01-09T17:39:31.373 回答
0

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

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

或乐观:

在您阅读记录的地方,记下版本号并在写回记录之前检查版本是否未更改。当您写回记录时,您会过滤版本上的更新以确保它是原子的。(即在您检查版本并将记录写入磁盘之间尚未更新)并一键更新版本。

如果记录是脏的(即与您的不同版本),您将中止事务并且用户可以重新启动它。

来源

于 2013-01-09T17:30:20.600 回答
0

当您执行select, 放置一个updlock,holdlock在被选择的范围内:

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隔离级别,将产生以下效果:

  • 语句不能读取已被其他事务修改但尚未提交的数据。

  • 在当前事务完成之前,任何其他事务都不能修改当前事务已读取的数据。

  • 在当前事务完成之前,其他事务不能插入键值落在当前事务中的任何语句读取的键范围内的新行。

范围锁被放置在与事务中执行的每个语句的搜索条件相匹配的键值范围内。这会阻止其他事务更新或插入符合当前事务执行的任何语句的任何行。这意味着如果事务中的任何语句第二次执行,它们将读取相同的行集。范围锁一直保持到事务完成。这是隔离级别中最具限制性的,因为它锁定整个范围的键并保持锁定直到事务完成。由于并发性较低,仅在必要时使用此选项

除了...updlock之外还需要 ,holdlock通过添加 ,我们可以防止单独的进程同时在同一范围上updlock执行自己的语句。select with (updlock, holdlock)

于 2013-01-09T17:31:05.577 回答
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
于 2013-01-10T13:32:03.123 回答
-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'
)

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

我不想添加唯一约束

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

于 2013-01-10T05:01:52.727 回答