【问题标题】:SQL Simultaneous transactions ignore each other's locks??? DEADLOCK [InnoDB, Python]SQL并发事务忽略彼此的锁???死锁 [InnoDB, Python]
【发布时间】:2018-08-04 17:56:20
【问题描述】:

美好的一天!

我遇到了麻烦。我的客户要求我重新利用 python 程序来使用 MySQL 而不是 Microsoft 的 SQL Server。我在 SQL 中找不到等效的解决方案。

我似乎无法在一行上创建正确的更新锁。当两个相同的事务同时执行时,尽管在序列化隔离级别打开事务,并且使用 SELECT ... FOR UPDATE,但它们都会读取该行。

也许我的代码会更好地解释它:

execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
execute("START TRANSACTION")
execute("SELECT * FROM job WHERE status = %s LIMIT 1 FOR UPDATE", jobStatus.imported)
job_data = cursor.fetchone()
if not job_data:
    connection.rollback()
else:
    execute("UPDATE job SET status = %s WHERE jobID = %s", jobStatus.ingesting, job_data['jobID']) # Update the job data

    if job_data['jobUUID'] == None:
        job_data['jobUUID'] = new_unused_uuid().bytes
        execute("UPDATE job SET jobUUID = %s WHERE jobID = %s LIMIT 1", job_data['jobUUID'], job_data['jobID'])
    if job_data['dateAdded'] == None:
        job_data['dateAdded'] = datetime.datetime.now()
        execute("UPDATE job SET dateAdded = %s WHERE jobID = %s LIMIT 1", job_data['dateAdded'], job_data['jobID'])

    execute("INSERT INTO ingestJob (fk_jobUUID, fk_nodeUUID, status) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE fk_nodeUUID = %s, status = %s", job_data['jobUUID'], unique_id.bytes, smallJobStatus.running, unique_id.bytes, smallJobStatus.running)

    connection.commit()

流程如下:

  1. 使用 FOR UPDATE 选择一项可能的工作
  2. 如果没有作业,则回滚(释放锁),或者...
  3. ...更新行使其无法重新选择,进行一些不相关的更改
  4. 提交

他们都做自己的事,忽略彼此的锁和事务

让我害怕的是它是随机的。它大约每隔一次运行就会发生一次。在隔离环境中尝试相同的查询时,有足够的延迟,我得到了我想要的确切结果。
只要 Alice 调用了 SELECT ... FOR UPDATE,Barry 就无法读取该行,并挂起直到 Alice 提交或回滚。我的现象需要在同一程序的两个实例之间精确同时执行。

我尝试在第 4 行打印获取的行,它们返回 exact 同一行...我在 Ubuntu Server 上使用带有 InnoDB 引擎的 MariaDB 10.1.30,以及 Python 和 MySQLdb (mysqlclient) 通信模块。是玛丽亚吗?我认为与 MySQL 相比,它可能是更好的选择。

一个引发异常,因为它正在与另一个争夺资源(菜鸟太慢了!)

事务和锁正在生成

为了显示一个 FOR UPDATE 锁和一个正确的事务,我做了以下测试。我同时运行了这个小 poke 脚本,同时在主脚本提交之前添加了 time.sleep(10),以保持锁定至少 10 秒。

while True:
    cursor.execute("SELECT * from job FOR UPDATE")
    print('Selected')
    time.sleep(1)
    connection.rollback()
    print('Released')
    time.sleep(1)

一旦主脚本获得锁,小戳脚本就会挂起,无法选择行。十秒后,poke 脚本获得了锁,但是两个节点都执行了,再次!!!。如您所见,顶部的那个抱怨死锁,因为底部的已经在事务的其他地方插入了一行。

我愿意接受其他更正确的 SQL 解决方案。也许我做错了。在 T-SQL 中,可以使用 OUTPUT 子句更新一行并返回修改后的行,就像在 UPDATE 之后运行了一条 SELECT 语句一样。我唯一的解决方案是使用 FOR UPDATE 选择一行,然后运行 ​​UPDATE。我还没有真正考虑过使用过程,将其从 Python 中取出并在 MariaDB 上原生运行会更好吗?

我非常感谢任何提示或建议。我对 SQL 没有那么丰富的经验,但离开 SQL Server 的过程尤其令人痛苦。由于我的客户希望使用 docker,我担心这可能不仅是一种不太可能的情况,而且是一种可能性,因为在产生极端负载时可能会同时创建 docker。

谢谢,祝你有美好的一天!

【问题讨论】:

    标签: python sql transactions mariadb database-deadlocks


    【解决方案1】:

    (这可能无法解决您的问题,但这是一个不适合评论的建议。)

    一次完成所有更新:

    UPDATE job SET
            status = %s,
            jobUUID   = IFNULL(jobUUID, UUID()),
            dateAdded = IFNULL(dateAdded, NOW())
        WHERE jobID = %s
        LIMIT 1
    

    您可以通过使用LAST_INSERT_ID(jobID) 获得jobID,从而避免使用SELECT

    【讨论】:

    • 这正是我一直在寻找的替代方案,用于从 T-SQL 替换我的 OUTPUT。谢谢!
    【解决方案2】:

    SELECT... FOR UPDATE 根据您的配置在不同级别隔离事务。你可以在这里找到更多信息https://dev.mysql.com/doc/refman/5.5/en/innodb-transaction-isolation-levels.html#isolevel_repeatable-read

    但您的代码中最重要的是您必须为不同的事务使用不同的会话。

    如此处所述https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html。如果您在事务完成之前在同一会话中运行另一个事务,它将被隐式提交,这就是导致代码中随机结果的原因。

    您现在在代码中所做的与在一个终端中运行两个事务相同,这与仅在非事务中运行所有内容没有太大区别。

    您需要通过池等方法在不同的连接中创建事务,以模拟不同的会话。

    【讨论】:

    • 嗨,据我了解,这两个程序都在各自的会话中运行,由一个批处理文件启动,两次调用 start py node.py。该图显示了并行运行的程序的两个实例。它们作为单独的进程运行,并拥有与数据库的唯一连接(使用相同的凭据)。
    【解决方案3】:

    在事务的不同部分添加了一些time.sleep 语句后,我意识到问题与AliceBarry 同时执行或忽略它们无关别人的锁。

    没有睡眠语句,太快了,看不出发生了什么。真正的问题是 Barry 在他的 SELECT... FOR UPDATE 中读取旧数据,即使在 AliceCOMMIT 更新了工作状态之后,让他在 Alice 释放锁后立即从事相同的工作.

    由于这是一个完全不同的问题,我在此处以不同的解释和更相关的代码示例重新发布了该问题:SELECT... FOR UPDATE selecting old data after a commit

    很抱歉,这对您没有帮助。我自己还没有发现问题。

    【讨论】:

      猜你喜欢
      • 2020-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多