SQL 并发事务忽略彼此的锁???死锁 [InnoDB,Python]

SQL Simultaneous transactions ignore each other's locks??? DEADLOCK [InnoDB, Python]

美好的一天!

我已经 运行 头晕目眩了。我的客户要求我重新调整 python 程序的用途以使用 MySQL 而不是 Microsoft 的 SQL 服务器。我在 SQL.

中找不到等效的解决方案

我似乎无法在某行上创建正确的更新锁。当两个相同的 t运行saction 同时执行时,尽管在序列化隔离级别打开了 t运行saction,但它们都读取该行,并且 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. Select 一份可能的工作,FOR UPDATE
  2. 如果没有作业,则回滚(释放锁),或者...
  3. ...更新该行使其无法重新select编辑,进行一些不相关的更改
  4. 提交

他们都各干各的,无视对方的锁和t运行saction

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

我尝试在第 4 行打印获取的行,它们 return 完全 同一行...我正在使用 MariaDB 10.1.30 和 InnoDB 引擎在 Ubuntu 服务器上,使用 Python 和 MySQLdb (mysqlclient) 模块进行通信。是玛丽亚吗?我认为与 MySQL.

相比,它可能是更好的选择

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

运行交易和锁定正在制作中

为了显示一个 FOR UPDATE 锁和一个正确的 t运行saction,我做了以下测试。我同时 运行 这个小 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 脚本就会挂起,无法 select 该行。十秒后,poke 脚本获取了锁,但是 两个节点再次执行!!!。如您所见,顶部的那个抱怨死锁,因为底部的那个已经在 t运行saction.

的其他地方插入了一行

我愿意接受其他更正确的 SQL 解决方案。也许我做错了。在 T-SQL 中,可以使用 OUTPUT 子句更新行和 return 修改后的行,就好像 SELECT 语句在 UPDATE 之后是 运行 一样。我唯一的解决办法是 SELECT 一行 FOR UPDATE,然后 运行 UPDATE。我还没有真正考虑过使用程序,将它从 MariaDB 上的 Python 和 运行 中拿走会更好吗?

如果有任何提示或建议,我将不胜感激。我对 SQL 没有那么多经验,但是离开 SQL 服务器的过程特别痛苦。由于我的客户希望使用 docker,我担心这可能不仅是一种不太可能的情况,而且是一种可能性,因为当产生极端负载时可能会同时创建 docker。

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

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。如果您 运行 在事务完成之前同一会话中的另一个事务,它将被隐式提交,这就是导致代码中出现随机结果的原因。

您现在在代码中所做的与 运行在一个终端中进行两笔交易是一样的,这与 运行在 non-transaction 中进行所有操作并没有太大区别。

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

在交易的不同部分添加了一些time.sleep语句后,我意识到问题与AliceBarry无关同时执行或忽略彼此的锁。

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

由于这是一个完全不同的问题,我在此处重新发布了带有不同解释和更多相关代码示例的问题:

很抱歉这无法帮助您。我自己还没找到问题。

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

一次完成所有更新:

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

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