Python 查询导致SQL Server 2016死锁?
我已经阅读了所有其他死锁问题,但这些问题似乎都是特定于查询的,因此我无法用发布的答案解决我的特定问题 我有一个Python脚本对这个数据库运行多个并发更新,当线程数设置得太高时,我会遇到死锁 下面的查询出现了死锁,我不确定应该使用什么“表提示”组合,或者是否有更好的方法执行此Python 查询导致SQL Server 2016死锁?,python,sql-server,sql-server-2016,Python,Sql Server,Sql Server 2016,我已经阅读了所有其他死锁问题,但这些问题似乎都是特定于查询的,因此我无法用发布的答案解决我的特定问题 我有一个Python脚本对这个数据库运行多个并发更新,当线程数设置得太高时,我会遇到死锁 下面的查询出现了死锁,我不确定应该使用什么“表提示”组合,或者是否有更好的方法执行此UPDATE语句 以下是我的查询(为了简洁起见,修改了名称): 您不希望多个会话为同一键值运行第一个SELECT。这就是导致僵局的原因 这里的正确模式是: BEGIN TRAN IF EXISTS (SELECT BlahI
UPDATE
语句
以下是我的查询(为了简洁起见,修改了名称):
您不希望多个会话为同一键值运行第一个SELECT。这就是导致僵局的原因 这里的正确模式是:
BEGIN TRAN
IF EXISTS (SELECT BlahID FROM MyTable WITH (UPDLOCK,HOLDLOCK) WHERE BlahID = ?)
BEGIN
UPDATE MyTable SET
Foo = ?,
Bar = 1
WHERE BlahID = ?
END
ELSE
BEGIN
INSERT INTO MyTable (Foo, Bar)
VALUES (1, ?,)
END
COMMIT TRAN
如果行存在,SELECT将锁定该行;如果行不存在,SELECT将获取密钥范围上的更新范围锁。在这两种情况下,第二个会话将阻塞存在性检查,直到第一个会话完成插入或更新
如果您不使用锁定提示(在选择、更新、插入或合并中)进行读取,则如果该行不存在,则不会进行锁定,并且多个会话可能会尝试插入。您不需要
If
来检查记录是否已存在。UPDATE语句中的WHERE
子句就是这样做的。在插入新记录之前,您只需确保记录不存在,例如:
UPDATE MyTable
SET
Foo = @foo,
Bar = 1
WHERE BlahID = @id;
INSERT MyTable (Bar,Foo)
values (1,@foo)
where not exists (select BlahID
from MyTable
where BlahID=@id)
如果可能,请使用命名参数,这样您只需要传递2个参数,而不是4个参数,并且可能会混淆顺序
您可以在一个事务中包装这两条语句,但要确保BlahID已被索引。这将允许服务器仅锁定一行进行更新。如果没有索引,服务器将不得不扫描和锁定更多数据以确保一致性
这也避免了插入重复条目。无论使用多少个锁,如果使用if
子句,使用相同的不存在ID的两次并发尝试都将导致两次插入,因为两个查询都会发现缺少行,都将尝试无条件插入
另一种选择是使用MERGE,尽管在这种情况下它的性能不好。从
当简单地基于另一个表的行更新一个表时,可以使用基本的INSERT、UPDATE和DELETE语句来提高性能和可伸缩性。例如:
目前的情况更简单,只涉及一个表:
INSERT MyTable (Bar,Foo)
VALUES (1,@foo)
WHERE NOT EXISTS (SELECT BlahID FROM MyTable WHERE BlahID=@id);
为什么会出现僵局?
服务器必须锁定行以确保事务可重复。选择时,服务器对检索到的或扫描的行使用共享锁。这就是为什么索引会导致更少的锁-服务器可以立即找到它需要的行。这些共享锁将在事务期间保留。如果没有显式事务,根据隔离模式,共享锁可能会在连接期间保留。这就是可重复读取的情况
当您尝试更新行时,服务器将尝试获取更新锁。如果行具有共享锁,则服务器将阻止更新操作。如果事务已在某行上持有共享锁,它将尝试将其升级为升级锁。如果其他人在行上有S锁,则传输将被阻止。为了使读取可以重复,服务器必须锁定它接触的行
如果服务器因为缺少索引而找不到一行,情况会更糟
NOLOCK并不意味着没有锁,它意味着其他人的锁不受尊重。该操作仍将锁定,但会导致脏结果、重影或缺少更新
这就是在这种情况下解除锁定的原因:
IF(SELECT)
并在行S1和S2上获得共享锁UPDATE MyTable SET
Foo = ?
WHERE BlahID = ?
IF @@ROWCOUNT=0
INSERT INTO MyTable (Foo)
VALUES (1)
我学到了很多关于“表格提示”和锁定其他提供的答案的知识,所以如果你是一个任性的谷歌用户,它们值得一读 如果你关心准确性,你应该放弃NOLOCK提示。特别是因为您声明它正在导致死锁,这表明其他进程正在同时处理此数据。只有当所有其他选项都已用尽并且您100%确定知道它们的用途时,才应使用表提示。本质上,你是在说你比sql引擎更清楚你想要什么类型的锁定。我明白了。这是有道理的。然而,删除它后,只是重新测试,死锁仍然会发生。看起来您只是在尝试对一行执行“插入或更新”,对吗?您应该能够在一个MERGE语句中完成此操作。Correct-更新单个记录(Insert或Update)。不是最“懂SQL”的人,所以发布的SQL是我通过使用Google Fu得到的。你有一个合并的例子吗?从这里开始:有人给微软的员工写评论,评论如何编写好的SQL。这是一个糟糕的SQL-使用过多的锁会导致死锁。
IF
statem
INSERT MyTable (Bar,Foo)
VALUES (1,@foo)
WHERE NOT EXISTS (SELECT BlahID FROM MyTable WHERE BlahID=@id);
UPDATE MyTable SET
Foo = ?
WHERE BlahID = ?
IF @@ROWCOUNT=0
INSERT INTO MyTable (Foo)
VALUES (1)