【问题标题】:Ignoring locked row in a MySQL query忽略 MySQL 查询中的锁定行
【发布时间】:2011-05-05 19:06:59
【问题描述】:

我有一张表同时被不同的线程读取。

每个线程必须选择 100 行,在每行上执行一些任务(与数据库无关),然后他们必须从表中删除选定的行。

使用此查询选择行:

SELECT id FROM table_name FOR UPDATE;

我的问题是:如何忽略(或跳过)以前在 MySQL 中使用 select 语句锁定的行?

【问题讨论】:

    标签: mysql locking race-condition


    【解决方案1】:

    我通常创建一个默认为 NULL 的 process_id 列,然后让每个线程使用唯一标识符来执行以下操作:

    UPDATE table_name SET process_id = #{process.id} WHERE process_id IS NULL LIMIT 100;
    
    SELECT id FROM table_name WHERE process_id = #{process.id} FOR UPDATE;
    

    这确保每个线程从表中选择一组唯一的行。

    希望这会有所帮助。

    【讨论】:

    • 在这个例子中,如果脚本在第一次更新后突然中止,你将如何从旧进程中“清理”数据库?否则这 100 行将永远不会被处理。
    • 好问题。我通常设置相同的列或“状态”列以指示成功完成(作为事务的一部分)。然后,如果一个进程死亡,您可以将一个不存在的进程的任何 process_id 设置回 NULL 以供另一个进程继续使用。我更喜欢 cron 作业进行清理,但您也可以将其作为启动新进程的一部分。
    • 另一种方法是将locked_at 设置为时间,好吧,您已将记录锁定在。然后,您将修改UPDATE 语句以更新process_idNULLlocked_at 早于某个合理间隔的记录。
    • @KrisRobison 你如何创建 processId 到 processId 是唯一的?时间()+随机字符串()? !
    • 抛出一个主机名/id,即命名空间,但是是的,这适用于一个完全分布式的锁 id。
    【解决方案2】:

    尽管这不是最好的解决方案,因为我知道无法忽略锁定的行,但我选择了一个随机行并尝试获取锁。

    START TRANSACTION;
    SET @v1 =(SELECT myId FROM tests.table WHERE status is NULL LIMIT 1);
    SELECT * FROM tests.table WHERE myId=@v1 FOR UPDATE; #<- lock
    

    为事务设置一个小的超时,如果该行被锁定,事务被中止,我尝试另一个。如果我获得了锁,我会处理它。如果(运气不好)该行被锁定,它被处理并在我的超时之前释放锁定,然后我选择一个已经被“处理”的行!但是,我检查了我的流程设置的字段(例如状态):如果其他流程事务结束正常,则该字段告诉我工作已经完成并且我不再处理该行。

    没有事务的所有其他可能的解决方案(例如,如果行没有状态,则设置另一个字段等等)很容易提供竞争条件和错过进程(例如,一个线程突然死亡,分配的数据仍然被标记,而交易过期;参考评论here

    希望对你有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-08
      • 1970-01-01
      • 2012-12-11
      相关资源
      最近更新 更多