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

时间:2018-02-25 14:14:56

标签: python mysql transactions locking commit

美好的一天,

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

我有两个相同的脚本, Alice Barry 并发运行。他们的目标是

  1. SELECT * FROM job WHERE status = 0 LIMIT 1 FOR UPDATE
  2. UPDATE job SET status = 1其中jobID匹配,做其他一些事情,然后......
  3. COMMIT更改,然后继续工作
  4. 我遇到的问题是 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能够跟上正在发生的事情。我已经在这个摘录中注释掉了调试功能,以便更容易阅读实际发生的事情 这些是来自 Alice Barry 的输出:

    爱丽丝

    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 持有锁定五秒钟,只要她COMMITS Barry 就会锁定。但是,Barry看到状态为0(测试期间只有一行)。最终他们都认为他们可以处理这项工作。

    我不确定为什么Barry在提交后读取状态为0。它回滚了吗?从他打电话给他SELECT时,他是否正在阅读旧价值的缓存?不同的隔离级别是否有帮助(似乎没有)?

    当我在一个封闭的环境中执行此代码时,没有程序的其余部分,它就可以了! Barry 一旦获得锁定就会报告没有行。我不明白它会有什么不同?我猜这意味着脚本中的其他地方出了问题。但它可能是什么?这是一个相当孤立的交易。在此代码之前调用COMMIT并没有改变任何内容,我想也许以前的交易没有正确关闭。
    Alice Barry 是测试时访问数据库的唯一进程(除了phpmyadmin)。

    我正在运行MariaDB的InnoDB引擎,MySQLdb(mysqlclient)作为Python的连接器。据我所知,AUTOCOMMIT已关闭。脚本很长,这就是为什么我只发了几行。我将在接下来的几天内尝试将其分开以尝试隔离问题,或者创建一个小的示例脚本。我似乎无法找到如何让MariaDB打印出更改和锁定日志,但如果我这样做,我将更新此帖子。

    任何想法,建议或评论都会令人惊叹。

    祝你有美好的一天!

1 个答案:

答案 0 :(得分:0)

在遵循 Solarflare 关于逐位切割程序的建议之后,我最终发现问题导致代码的远程部分,其运行没有错误,但完全没有从T-SQL迁移到SQL后不同。

我起初认为SQL服务器的问题,但它只是一个讨厌的bug。我出于某种原因现在遇到了可怕的僵局,还有另一种乐趣。

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

祝你有美好的一天!