我们可以防止从 db 表中读取排他锁定的行吗?

问题描述

我的 MysqL (innoDB) 数据库中有一个表 EMPLOYEE,其中包含以下列,

  • internal_employee_id(自动递增 PK)
  • external_employee_id
  • 姓名
  • 性别
  • 导出(布尔字段)

在分布式系统中,我想确保集群中的多个进程每次从表中读取前 100 条不同的行,这些行的导出列设置为 false。进程读取的行在计算过程中应该保持锁定,这样如果进程 1 读取行 1-100,进程 2 应该无法看到 1-100 的行,然后应该选择下一个可用的 100 行。

为此,我尝试使用 pessimistic_write 锁,但它们似乎没有达到目的。它们确实会阻止多个进程同时更新,但多个进程可以读取相同的锁定行。

我尝试使用以下java代码

    Query query = entityManager.createNativeQuery("select * from employee " +
        "where exported = 0 limit 100 for update");
    List<Employee> employeeListLocked = query.getResultList();
  

编辑:终于找到答案

我需要的是使用“跳过锁定”功能。所以我更新后的代码变成了:

  Query query = entityManager.createNativeQuery("select * from employee " +
        "where exported = 0 limit 100 for update skip locked");

在“跳过锁定”的帮助下,数据库引擎在运行选择时忽略/跳过所有处于锁定状态的行。希望对大家有帮助。

解决方法

您可以在表格中添加一个新列 例如,名为“已处理”的列(布尔字段)并使用假值更新所有记录

update EMPLOYEE set processed = 0;

当一个进程开始时,在同一个事务中,您可以选择更新,然后将这100行中处理的列更新为1。

    Query query = entityManager.createNativeQuery("select * from employee " +
            "where exported = 0 and processed = 0
    order by internal_employee_id desc  limit 100 for update");
        List<Employee> employeeListLocked = query.getResultList();

更新这 100 行

UPDATE EMPLOYEE eUpdate INNER JOIN (select internal_employee_id
       from EMPLOYEE where exported = 0 and processed = 0
       order by internal_employee_id desc limit 100) e
     ON eUpdate.internal_employee_id = e.internal_employee_id
       SET eUpdate.processed = 1 ;

那么,下一个进程就不会处理同一个列表

,

有几种方法可以阻止读取:

首先要更新表的会话:

LOCK TABLES employee WRITE;

这会获取表上的独占元数据锁。然后其他会话被阻止,即使它们只是尝试读取该表。他们必须等待元数据锁定。有关这方面的更多信息,请参阅 https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html

表锁的缺点是它们锁定整个表。无法使用它来锁定单个行或行集。

另一种解决方案是您必须编写所有读取代码以要求共享锁:

SELECT ... FROM employee WHERE ... LOCK IN SHARE MODE;

MySQL 8.0 更改了语法,但工作方式相同:

SELECT ... FROM employee WHERE ... FOR SHARE;

这些不是元数据锁,它们是行锁。因此,您可以锁定单个行或行集。

对某些行的共享锁的请求不会与这些行上的其他共享锁冲突,但如果行上有排他锁,则 SELECT FOR SHARE 会等待。反之亦然——如果未提交的行上有任何 SELECT FOR SHARE,则排他锁请求将等待。

这种方法的缺点是它只有在读取该表的所有查询都具有 FOR SHARE 选项时才有效。

说了这么多,我发这个只是为了直接回答你的问题。我确实认为 answer from Perkilis 中描述的系统很好。我最近实施了一个这样的系统,并且有效。

有时你想到的实现并不是最好的解决方案,你需要考虑另一种方法来解决问题。

,
-- In a transaction by itself:
UPDATE t
    SET who_has = $me   -- some indicate of the process locking the rows
    WHERE who_has IS NULL
    LIMIT 100;

-- Grab some or all rows that you have and process them.
-- You should not need to lock them further (at least not for queue management)
SELECT ... WHERE who_has = $me ...

-- Eventually,release them,either one at a time,or all at once.
-- Here's the bulk release:
UPDATE t SET who_has = NULL
    WHERE who_has = $me
-- Again,this UPDATE is in its own transaction.

请注意,这种通用机制对“处理”项目所需的时间没有限制。

此外,如果在没有释放项目的情况下发生崩溃,使用额外的 who_has 列可以帮助您。它应该增加物品被抓取时的时间戳。 Cron 作业(或同等工作)应该四处寻找任何已锁定“太长时间”的未处理项目。

,

找到答案:

我需要的是使用“跳过锁定”功能。所以我更新后的代码变成了:

  Query query = entityManager.createNativeQuery("select * from employee " +
        "where exported = 0 limit 100 for update skip locked");
    List<Employee> employeeListLocked = query.getResultList();

在“跳过锁定”的帮助下,数据库引擎在运行选择时忽略/跳过所有处于锁定状态的行。希望对大家有帮助。