SELECT... FOR UPDATE 在提交后选择旧数据

SELECT... FOR UPDATE selecting old data after a commit

美好的一天,

这是对我在 的旧 post 的更新,因为我后来意识到这个问题与我认为的问题无关。我正在尝试为客户端创建等同于基于 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 打印出更改和锁定的日志,但如果我这样做,我会更新这个 post.

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

祝你有美好的一天!

在按照 Solarflare 的建议一点一点地分割程序后,我最终发现了导致代码远程部分的问题,该部分运行正常,但在从 T-SQL 移动到 SQL.

后完全不同

起初我以为是 SQL 服务器的问题,但这只是一个严重的错误。由于某种原因,我现在陷入了可怕的僵局,但又很高兴研究一下。

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

祝你有愉快的一天!