Sql server 2005 为什么我不能在SQL Server 2005中插入/更新数据而不锁定整个表?

Sql server 2005 为什么我不能在SQL Server 2005中插入/更新数据而不锁定整个表?,sql-server-2005,locking,insert-update,database-deadlocks,Sql Server 2005,Locking,Insert Update,Database Deadlocks,我正在尝试根据SQL Server表是否存在来插入/更新该表中的行。我在多台机器上从多个线程执行SQL,我希望避免出现重复的密钥错误 我在网上找到了很多解决方案,但都造成了交易死锁。这是我一直使用的一般模式: BEGIN TRANSACTION UPDATE TestTable WITH (UPDLOCK, SERIALIZABLE) SET Data = @Data WHERE Key = @Key IF(@@ROWCOUNT = 0) BEGIN INSERT INTO Te

我正在尝试根据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
我试过:

使用XLOCK而不是UPDLOCK 使用UPDLOCK在开始时将事务隔离级别设置为可序列化 将事务隔离级别设置为可序列化且无表提示 我还尝试了以下模式与上述所有组合:

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
我能让它在没有死锁的情况下工作的唯一方法是与TABLOCKX一起使用

我使用的是SQLServer2005,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


我做错了什么?

您的死锁在索引资源上


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

您的死锁在索引资源上


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

首先在表上创建一个连接来检查插入是否存在:

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检查位于同一个原子语句中,而不是两个单独的语句。

首先使用表上的联接执行INSERT以检查它是否存在:

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检查位于同一个原子语句中,而不是两条单独的语句。

今天我再次查看了这条语句,发现我有点傻。我实际上是在跑步:

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

我自己在锁钥匙。哼

今天我又看了一遍,发现自己有点冲动。我实际上是在跑步:

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

我自己在锁钥匙。哼

谢谢你,克里斯。我刚刚尝试了这个,但不幸的是,我仍然在执行死锁:用UPDLOCK更新TestTable,ROWLOCK,Serializable死锁是单次调用造成的还是多线程造成的?@Chris:是多线程造成的。我认为这与服务器设置“最大并行度”有关,该设置在有问题的机器上为0,但在另一台正在工作的机器上为1。谢谢@Chris。我刚刚尝试了这个,但不幸的是,我仍然在执行死锁:用UPDLOCK更新TestTable,ROWLOCK,Serializable死锁是单次调用造成的还是多线程造成的?@Chris:是多线程造成的。我认为这与服务器设置“最大并行度”有关,该设置在有问题的机器上为0,但在另一台正在工作的机器上为1。谢谢@kev,我很感激这是一个延迟响应。我已经检查了查询的where子句,它们都以正确的顺序查询主键,并在执行计划中使用索引。谢谢@kev,我知道这是一个延迟响应。我已经检查了查询的where子句,它们都以正确的顺序查询主键,并在执行计划中使用索引。