【问题标题】:Avoid inserting duplicate records in SQL Server避免在 SQL Server 中插入重复记录
【发布时间】:2015-09-25 05:29:14
【问题描述】:

我无法找到这个问题的答案。假设我有以下表/查询:

桌子:

create table ##table
(
   column1 int,
   column2 nvarchar(max)
)

查询(在现实生活场景中,条件会更复杂):

declare @shouldInsert bit
set @shouldInsert = case when exists(
    select * 
    from ##table
    where column2 = 'test') then 1 else 0 end

--Exaggerating a possible delay:
waitfor delay '00:00:10'

if(@shouldInsert = 0)
   insert into ##table
   values(1, 'test')

如果我同时运行此查询两次,则可能会插入重复记录(强制执行唯一约束是不可能的,因为实际条件比表中的“column1”唯一性更复杂)

我看到了两种可能的解决方案:

  1. 我在可序列化模式下运行两个并发事务,但它会产生死锁(首先是 select 中的共享锁,然后是插入中的 x 锁 - 死锁)。

  2. 1234563 /p>

哪个更容易接受?有第三种解决方案吗?

谢谢。

【问题讨论】:

  • 你不能只使用 LEFT JOIN insert into ##table SELECT t.col1, t.col2 FROM (SELECT 1 as col1, 'test' as col2) t LEFT JOIN ##table temp ON t.col1 = temp.col1 WHERE temp.col1 IS NULL
  • 您是否考虑过将 shouldInsert 测试和实际插入合并到单个 SQL 语句中。类似于“插入##table(select 1,'test' from dual where not exist ...”的内容)
  • 我会选择第二种解决方案,但在子查询中使用带有锁定提示的INSERT...SELECT...WHERE NOT EXISTS 语法。我不认为并发是一个问题,除非你有一个非常高的插入率,假设 column2 上有一个唯一的索引。

标签: sql-server database


【解决方案1】:

如果可以,您应该在定义唯一性的任何列上放置 UNIQUE 约束(或索引)。

有了这个,您可能仍然会收到“OK,尚不存在”响应,以便您对两个单独的进程进行初始检查 - 但两个进程中的一个将是第一个并插入他的行,而第二个将从数据库返回“违反唯一约束”异常。

【讨论】:

    【解决方案2】:

    无论您的“现实生活状况”如何“参与”,您都有两个选择:强制执行 UNIQUE 或处理多条记录。任何解决方法都可能很脆弱。

    例如,如果您需要添加另一个数据库服务器或过大的负载会减慢单个线程的执行速度,那么您的 delay hack 将毫无用处

    您可以允许一个应该是唯一的值的多个副本的一种方法是创建另一个可以充当队列并且不强制唯一性的表和一个串行工作者将其出列。或者更改数据结构以允许一对多并在查询时选择第一个。仍然是一个 hack,但至少不是非常“有创意”,而且它不能破坏

    【讨论】:

      【解决方案3】:
      declare @shouldInsert bit
      set @shouldInsert = case when exists(
          select * 
          from ##table
          where column2 = 'test') then 1 else 0 end
      
      --Exaggerating a possible delay:
      waitfor delay '00:00:10'
      
      truncate table #temp
      
      if(@shouldInsert = 0)
         insert into #temp
         values(1, 'test')
      

      --如果记录在##table中不可用,则数据将从#temp table插入到##table

      insert into ##table
      select * from #temp
      except
      select * from ##table
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-08-16
        • 1970-01-01
        • 1970-01-01
        • 2019-10-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多