python - SQL Server 2016查询导致死锁?

标签 python sql-server sql-server-2016

已阅读所有其他死锁问题,但似乎这些问题通常是特定于查询的,因此无法通过发布的答案解决我的特定问题。

我有一个针对该数据库运行多个并发更新的 Python 脚本,当线程数设置得太高时,我会遇到死锁。

下面的查询发生了死锁,我不确定应该使用什么“表提示”组合,或者是否有更好的方法来执行此 UPDATE 语句。

这是我的查询(为简洁起见,修改了名称):

BEGIN TRAN
IF EXISTS (SELECT BlahID FROM MyTable WITH (NOLOCK) WHERE BlahID = ?)
    BEGIN
        UPDATE MyTable SET
            Foo = ?,
            Bar = 1
        WHERE BlahID = ?
    END
ELSE
    BEGIN
        INSERT INTO MyTable (Foo, Bar)
        VALUES (1, ?,)
    END
COMMIT TRAN

最佳答案

您不需要 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,尽管在这种情况下它的性能不佳。来自 MERGE documentation

When simply updating one table based on the rows of another table, improved performance and scalability can be achieved with basic INSERT, UPDATE, and DELETE statements. For example:

INSERT tbl_A (col, col2)  
SELECT col, col2   
FROM tbl_B   
WHERE NOT EXISTS (SELECT col FROM tbl_A A2 WHERE A2.col = tbl_B.col);  

当前的情况更简单,只涉及一张表:

INSERT MyTable (Bar,Foo)
VALUES (1,@foo)
WHERE NOT EXISTS (SELECT BlahID FROM MyTable WHERE BlahID=@id);

为什么会出现僵局?

服务器必须锁定行以确保事务可重复。选择时,服务器对检索或扫描的行采用 SHARED (S) 锁。这就是为什么拥有索引会导致更少的锁定 - 服务器可以立即找到它需要的行。这些共享锁将在事务期间保留。如果没有显式事务,则根据隔离模式,共享锁可能会在连接期间保留。这就是可重复读取所发生的情况。

当您尝试更新一行时,服务器将尝试获取 UPDATE 锁。如果某行具有共享锁,则服务器的更新操作将被阻止。如果事务已经在某行上持有 SHARED 锁,它将尝试将其升级为 UPGRADE 锁。如果其他人对该行有 S 锁,则事务将被阻止。为了使读取可重复,服务器必须锁定它触及的行。

如果服务器由于缺少索引而无法定位单行,情况会更糟。

NOLOCK并不意味着不获取锁,而是意味着不尊重其他人的锁。该操作仍然会获取锁,但会导致脏结果、重影或丢失更新。

这就是在这种情况下导致解除锁定的原因:

  1. 两个连接执行 IF(SELECT) 并获取行 S1 和 S2 上的共享锁。
  2. 连接 1 尝试将锁升级为 UPGRADE,但发现其上有 S2 锁,并阻塞等待其释放。
  3. 连接 2 尝试升级到 U,但发现 S1 并阻止。无法继续连接,导致死锁。

您可以在Locking in the Database Engine中找到有关锁定、锁定类型、兼容性和范围的更多信息。 SQL Server Transaction Locking and Row Versioning Guide 部分

快照隔离

您可以使用snapshot isolation level避免读取器和写入器互相阻塞,类似于 Oracle 和 PostgreSQL 的做法。这在这种情况下没有帮助,因为你有一个作家阻止了另一个作家。

关于python - SQL Server 2016查询导致死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53088112/

相关文章:

python - 创建一个新列作为 Pandas DataFrame 的计数

python - 如何永远运行异步 websocket 客户端?

sql - 使用 IF NOT EXISTS 生成插入脚本

python - Pyodbc - 读取存储过程的输出参数(SQL Server)

python - 在 ndarray 中查找 float

python - python中类 block 和函数 block 的区别

c# - SQL Server 到 Entity Framework 数据类型的映射

StorageTable 中的 Azure PolyBase 外部表

SQL 服务器 : "Could not find the Database Engine startup handle"

sql-server-2016 - SQL Server 从 JSON 中提取第一个数组元素