2

我正在尝试在 SQL Server 表中插入/更新行(取决于它是否存在)。我正在多台机器上从多个线程执行 SQL,我想避免出现重复的键错误。

我在网上找到了很多解决方案,但它们都导致事务死锁。这是我一直在使用的一般模式:

BEGIN TRANSACTION

UPDATE TestTable WITH (UPDLOCK, SERIALIZABLE)
SET Data = @Data 
WHERE Key = @Key

IF(@@ROWCOUNT = 0)
BEGIN
     INSERT INTO TestTable (Key, Data)
     VALUES (@Key, @Data)
END

COMMIT TRANSACTION

我试过了:

  • WITH XLOCK代替UPDLOCK
  • SET TRANSACTION ISOLATION LEVEL SERIALIZABLE一开始是UPDLOCK
  • SET TRANSACTION ISOLATION LEVEL SERIALIZABLE并且没有表格提示

我还尝试了以下所有组合的模式:

BEGIN TRANSACTION

IF EXISTS (SELECT 1 FROM TestTable WITH (UPDLOCK, SERIALIZABLE) WHERE Key=@Key) 
BEGIN
    UPDATE TestTable
    SET Data = @Data 
    WHERE Key = @Key
END
ELSE
BEGIN
    INSERT INTO TestTable (Key, Data)
    VALUES (@Key, @Data)
END

COMMIT TRANSACTION

我可以让它在没有死锁的情况下工作的唯一方法是使用WITH (TABLOCKX).

我使用的是 SQL Server 2005,SQL 是在运行时生成的,因此它不在存储过程中,并且一些表使用复合键而不是主键,但我可以在具有整数主键的表上重现它。

服务器日志如下所示:

waiter id=processe35978 mode=RangeS-U requestType=wait
waiter-list
owner id=process2ae346b8 mode=RangeS-U
owner-list
keylock hobtid=72057594039566336 dbid=28 objectname=TestDb.dbo.TestTable indexname=PK_TestTable id=lock4f4fb980 mode=RangeS-U associatedObjectId=72057594039566336
waiter id=process2ae346b8 mode=RangeS-U requestType=wait
waiter-list
owner id=processe35978 mode=RangeS-U
owner-list
keylock hobtid=72057594039566336 dbid=28 objectname=TestDb.dbo.TestTable indexname=PK_TestTable id=lock2e8cbc00 mode=RangeS-U associatedObjectId=72057594039566336

模式明显不同,具体取决于使用的表提示(但进程总是在等待它们已经拥有的模式)。我见过 RangeS-U、RangeX-X 和 U。

我究竟做错了什么?

4

3 回答 3

2

如何先在表上进行插入以检查它是否存在:

BEGIN TRANSACTION

WITH ToInsert AS(
     SELECT @Key AS Key, @Data AS Data
)
INSERT INTO TestTable (Key, Data)
SELECT ti.Key, ti.Data
FROM ToInsert ti
LEFT OUTER JOIN TestTable t
     ON t.Key = ti.Key
WHERE t.Key IS NULL

IF(@@ROWCOUNT = 0)
BEGIN
     UPDATE TestTable WITH (UPDLOCK, SERIALIZABLE)
     SET Data = @Data 
     WHERE Key = @Key
END

COMMIT TRANSACTION

这样可以确保您的 UPDATE 语句始终存在一条记录,并且您的 INSERT 和 INSERT-check 在同一个原子语句中,而不是两个单独的语句。

于 2012-09-26T09:34:00.180 回答
2

今天又看了一遍,发现自己有点不耐烦了。我实际上正在运行:

BEGIN TRANSACTION

IF EXISTS (SELECT 1 FROM TestTable WITH (UPDLOCK, SERIALIZABLE) WHERE Key=@Key) 
BEGIN
    UPDATE TestTable
    SET Data = @Data, Key = @Key -- This is the problem
    WHERE Key = @Key
END
ELSE
BEGIN
    INSERT INTO TestTable (Key, Data)
    VALUES (@Key, @Data)
END

COMMIT TRANSACTION

我自己锁了钥匙。呸!

于 2014-04-14T15:40:39.350 回答
0

您的死锁在索引资源上。

在执行计划中查找书签/键查找并创建一个覆盖这些字段的非聚集索引 - 这样 UPDATE 数据的“读取”不会与 INSERT 的“写入”发生冲突。

于 2012-05-24T13:43:34.040 回答