Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/jsf-2/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 查询导致SQL Server 2016死锁?_Python_Sql Server_Sql Server 2016 - Fatal编程技术网

Python 查询导致SQL Server 2016死锁?

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

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

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

下面的查询出现了死锁,我不确定应该使用什么“表提示”组合,或者是否有更好的方法执行此
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上获得共享锁
  • 连接1尝试将锁升级为升级,但在其上发现S2锁并阻塞等待释放
  • 连接2尝试升级到U,但发现S1和块。无法继续连接,导致死锁
  • 有关锁定、锁定类型、兼容性和作用域的更多信息,请参见

    快照隔离

    您可以使用避免读写器相互阻塞,这与Oracle和PostgreSQL的做法类似。在这种情况下,这不会有帮助,因为您有一个编写器阻止另一个编写器

    我最终在“BlahID”字段中添加了一个“unique约束”,因为它似乎在对我的第一条UPDATE语句执行整个表级锁定。一旦我添加了这个约束,我相信它只正确地执行了一个行级锁,它为我解决了死锁问题

    我还取消了更新时的“IF/ELSE”格式,只需执行以下操作:

    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)