【问题标题】:SELECT... FOR UPDATE selecting old data after a commitSELECT... FOR UPDATE 在提交后选择旧数据
【发布时间】:2018-08-05 01:52:30
【问题描述】:

早安,

这是对我在SQL Simultaneous transactions ignore each other's locks??? DEADLOCK [InnoDB, Python] 的旧帖子的更新,因为我后来意识到这个问题与我认为的问题无关。我正在尝试为客户端创建与基于 T-SQL 的脚本等效的 MySQL。

我有两个相同的脚本,AliceBarry 同时运行。他们的目标是

  1. SELECT * FROM job WHERE status = 0 LIMIT 1 FOR UPDATE
  2. UPDATE job SET status = 1 jobID 匹配的地方,做一些其他的事情,然后...
  3. COMMIT 更改,然后继续工作

我遇到的问题是 Alice 制作了锁,读取了作业,UPDATES 将其设置为状态 1,但是一旦她提交更改,Barry占用锁并读取状态为0,原始状态...甚至Alice有时在她COMMIT之后看到状态0@

这是我python脚本的一小部分,但应该足以理解程序:

connection = MySQLdb.connect(host=..., user=..., [...])
cursor = connection.cursor(MySQLdb.cursors.DictCursor)
[...]
execute("START TRANSACTION")
execute("SELECT * FROM job WHERE status = %s LIMIT 1 FOR UPDATE", 0)
job_data = cursor.fetchone()
 # debug("Made lock")
if not job_data:
    connection.commit()
     # debug("No rows")
else:
     # debug("Locked row with status  "+str(job_data['status']))
    execute("SELECT status FROM job")
     # debug("Double checked status is "+str(cursor.fetchone()))
    execute("UPDATE job SET status = %s WHERE jobID = %s", 1, job_data['jobID'])
    time.sleep(5)
    execute("SELECT status FROM job")
     # debug("Status before commit "+str(cursor.fetchone()))
    connection.commit()
     # debug('Committed')
    execute("SELECT status FROM job")
     # debug("Status after commit "+str(cursor.fetchone()))

这个臃肿的原始脚本版本充满了调试方法来尝试了解正在发生的事情,time.sleep 能够跟上正在发生的事情。我已在此摘录中注释掉了调试功能,以便更容易阅读实际发生的情况。
这些是 AliceBarry 的输出:

爱丽丝

41,351161: Made lock
41,351161: Locked row with status  0
41,351161: Double checked status is {'status': 0}
46,352156: Status before commit {'status': 1}
46,370601: Committed
46,370601: Status after commit {'status': 1} (Sometimes Alice sees 0 here)

巴里

46,352682: Made lock
46,353184: No rows
48,365044: Made lock
48,365044: Locked row with status  0
48,365044: Double checked status is {'status': 0}
53,365062: Status before commit {'status': 1}
53,386910: Committed
53,388846: Status after commit {'status': 1}

输出开头的数字是时间戳,如SECONDS,MICROSECONDS

Alice 持有锁五秒钟,一旦她COMMITSBarry 就会拿到锁。但是,Barry 看到状态为 0(测试期间只有一行)。最终双方都认为他们可以处理这项工作。

我不确定为什么 Barry 在提交后读取状态为 0。是回滚了吗?他是否正在读取旧值的缓存,从他调用他的SELECT 开始?不同的隔离级别会有帮助吗(似乎没有)?

当我在封闭环境中执行此代码时,没有程序的其余部分,它可以工作!!! Barry 在他最终获得​​锁后立即报告没有任何行。我不明白它怎么可能不同?我猜这意味着脚本的其他地方有问题。但它可能是什么?这是一个相当孤立的交易。在这段代码没有改变任何东西之前调用COMMIT,我想可能之前的交易没有正确关闭。
AliceBarry 是唯一的进程测试时访问数据库(除了phpmyadmin)。

我正在运行MariaDB 的InnoDB 引擎,使用MySQLdb (mysqlclient) 作为Python 的连接器。据我所知,AUTOCOMMIT已关闭。脚本很长,所以我只发了几行。在接下来的几天里,我将尝试将其拆分,以尝试隔离问题,或者创建一个小示例脚本。我似乎不知道如何让 MariaDB 打印出更改和锁定的日志,但如果我这样做了,我会更新这篇文章。

任何想法、建议或 cmets 都会很棒。

祝你有美好的一天!

【问题讨论】:

  • 如果您“在封闭环境中执行此代码”,它就可以工作,如果您包含更多代码,不是吗?如果您只向我们展示有效的部分,我们很难诊断。理想情况下,您从工作代码开始,然后添加代码直到它停止工作(请参阅mvce)。你经常会以这种方式自己发现问题。我会检查任何可能引发异常的东西;检查您是否有任何create/alter-code;尝试connection.start_transaction() 而不是execute(先做);检查隔离级别设置;检查你是否使用连接池。

标签: python mysql transactions locking commit


【解决方案1】:

在遵循 Solarflare 对程序进行一点一点分割的建议后,我最终发现了导致代码的远程部分的问题,该代码可以正常运行,但在运行后完全不同从 T-SQL 迁移到 SQL。

我一开始以为是 SQL 服务器的问题,但这只是一个讨厌的错误。由于某种原因,我现在遇到了可怕的僵局,但又很高兴研究一下。

感谢您的评论以及您花时间解释可能出现的问题,很抱歉这只是个人愚蠢的问题

祝你有美好的一天!

【讨论】:

  • 如果它对任何人都有帮助:为了让它工作(即避免死锁),我必须将隔离级别设置为(不大于)READ COMMITTED
猜你喜欢
  • 2015-03-26
  • 2011-06-28
  • 2017-04-16
  • 2013-09-23
  • 1970-01-01
  • 1970-01-01
  • 2013-01-04
  • 2013-10-19
  • 2018-11-21
相关资源
最近更新 更多