假设MyTable的表结构(KEY, datafield1, datafield2…)

通常,我希望更新现有记录,或者插入不存在的新记录。

从本质上讲:

IF (key exists)
  run update command
ELSE
  run insert command

最好的写法是什么?


当前回答

假设您想要插入/更新单行,最优的方法是使用SQL Server的REPEATABLE READ事务隔离级别:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION

    IF (EXISTS (SELECT * FROM myTable WHERE key=@key)
        UPDATE myTable SET ...
        WHERE key=@key
    ELSE
        INSERT INTO myTable (key, ...)
        VALUES (@key, ...)

COMMIT TRANSACTION

当当前运行的事务处于打开状态时,此隔离级别将阻止/阻止后续可重复读事务访问同一行(WHERE key=@key)。 另一方面,对另一行的操作不会被阻塞(WHERE key=@key2)。

其他回答

如果您首先尝试更新,然后插入,那么竞态条件真的重要吗? 假设你有两个线程想要为key key设置一个值:

线程1:value = 1 线程2:value = 2

竞态条件场景示例

键没有定义 线程1更新失败 线程2更新失败 线程1或线程2中的一个成功插入。例如线程1 另一个线程插入失败(错误重复键)-线程2。 结果:要插入的两个步骤中的“第一个”决定值。 期望结果:最后一个线程写入数据(更新或插入)应该决定值

但;在多线程环境中,操作系统调度器决定线程执行的顺序——在上面的场景中,我们有这个竞争条件,是操作系统决定执行的顺序。从系统的观点来看,说“线程1”或“线程2”是“第一个”是错误的。

当线程1和线程2的执行时间非常接近时,竞态条件的结果并不重要。唯一的要求应该是其中一个线程应该定义结果值。

对于实现:如果更新后插入导致错误的“重复键”,这应该被视为成功。

此外,我们当然不应该假定数据库中的值与您最后写入的值相同。

如果你使用ADO。NET, DataAdapter处理这个。

如果你想自己处理,方法如下:

确保在键列上有一个主键约束。

然后你:

进行更新 如果更新失败是因为已经存在具有键的记录,则执行插入操作。如果更新没有失败,您就完成了。

你也可以反过来做,即先做插入,然后在插入失败时进行更新。通常第一种方法更好,因为更新比插入更频繁。

你可以使用:

INSERT INTO tableName (...) VALUES (...) 
ON DUPLICATE KEY 
UPDATE ...

使用这个,如果已经有一个特定键的条目,那么它将更新,否则,它将插入。

假设您想要插入/更新单行,最优的方法是使用SQL Server的REPEATABLE READ事务隔离级别:

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION

    IF (EXISTS (SELECT * FROM myTable WHERE key=@key)
        UPDATE myTable SET ...
        WHERE key=@key
    ELSE
        INSERT INTO myTable (key, ...)
        VALUES (@key, ...)

COMMIT TRANSACTION

当当前运行的事务处于打开状态时,此隔离级别将阻止/阻止后续可重复读事务访问同一行(WHERE key=@key)。 另一方面,对另一行的操作不会被阻塞(WHERE key=@key2)。

不要忘记事务。性能很好,但是简单的方法(IF EXISTS..)非常危险。 当多个线程将尝试执行插入或更新时,您可以轻松地 获取主键冲突。

由@Beau Crawford和@Esteban提供的解决方案显示了大致的想法,但容易出错。

为了避免死锁和PK违规,你可以使用如下方法:

begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran

or

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran