Sql 在多线程环境中将记录插入数据库表

Sql 在多线程环境中将记录插入数据库表,sql,tsql,Sql,Tsql,我有一个TSQL代码,它检查“sadsad”是否存在,如果不存在,则将其插入表中 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 我担心的是,在多线程同时运行的生产环境中,可能会出现这样的情况,即记录将

我有一个TSQL代码,它检查“sadsad”是否存在,如果不存在,则将其插入表中

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
我担心的是,在多线程同时运行的生产环境中,可能会出现这样的情况,即记录将在not exists select和insert之间滑动


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

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

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

或乐观:

在读取记录时,记下版本号并检查 在您回写记录之前,版本没有更改。当你 将过滤更新的记录写回要生成的版本 当然是原子弹。(即,在您检查 版本,并将记录写入磁盘),然后在中更新版本 一炮打响

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


当您执行
选择时,在所选范围上放置
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
之外,还需要
updlock
。。。通过添加
updlock
我们可以防止一个单独的进程在同一时间在同一范围内执行自己的
select with(updlock,holdlock)
语句。

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

例如:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION

   <your code here>

COMMIT TRANSACTION
将事务隔离级别设置为可序列化;
开始交易
提交事务

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

下面,
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'
)
服务器保证插入或不插入该行。锁定是由知道如何锁定的人在需要的地方以尽可能短的时间为您完成的。:-)

我不想添加唯一的约束


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

这就是我最后做的事

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

它对那些试图插入的人有什么影响?炸毁他们的交易?@Woot4Moo其他试图插入的进程将被阻止,等待阻止进程完成并释放锁,然后继续。不一定会爆炸,但肯定会降低并发性。好吧,澄清一下,这不会导致脏的插入/更新吗?我可能错了,只是想弄清楚而已。@Woot4Moo-hmm。。。你说得对。。。读取不会阻止另一个读取,从而允许插入继续添加重复的值。让我更新我的答案。如果您想知道为什么不想在一个显然应该是唯一的列上添加
UNIQUE
约束,那将很有趣?如果重复项很少,那么尝试插入并捕获结果错误(如果有)是一种合理的方法。使用唯一约束是解决此问题的唯一合理方法。+1表示唯一约束。。。我可以问一下您为什么要避免这种情况吗?同意给定唯一约束的所述问题的答案不是一个选项。
SERIALIZABLE
如果两个进程同时运行,其本身将导致死锁。。。
updlock
也是需要的…”但是如果没有选择数据怎么办?要锁定什么?
SERIALIZABLE
将发出范围锁定。。。即使数据在选择时不存在,在事务期间,条件仍将保持锁定状态。您的答案与th具有完全相同的种族条件