【问题标题】:Select only unlocked rows mysql只选择未锁定的行mysql
【发布时间】:2013-11-20 09:07:11
【问题描述】:

我通过以下查询在一个事务中锁定了一行

START TRANSACTION;

SELECT id FROM children WHERE id=100 FOR UPDATE;

在另一笔交易中,我有如下查询

START TRANSACTION;

SELECT id FROM children WHERE id IN (98,99,100) FOR UPDATE;

它给出了错误锁等待超时超时。

这里100已经被锁定(在第一个事务中)但是ids 98,99没有被锁定。如果在上面的查询中只有100行被锁定,是否有可能返回98,99的记录。所以结果应该如下

身份证

===

98

99

===

Id 100 应该被忽略,因为 100 被事务锁定。

【问题讨论】:

  • 你是否使用 InnoDB 进行行表锁定?

标签: mysql transactions


【解决方案1】:

看起来像以前答案中提到的SKIP LOCKED 选项现在在 MySQL 中可用。它不会等待获取行锁,并允许您使用当前未锁定的行。

来自MySQL 8.0.0 Release Notes/Changes in MySQL 8.0.1:

InnoDB 现在支持 NOWAITSKIP LOCKED 选项以及 SELECT ... FOR SHARESELECT ... FOR UPDATE 锁定读取语句。 NOWAIT 使语句在请求的行被另一个事务锁定时立即返回。 SKIP LOCKED 从结果集中删除锁定的行。 See Locking Read Concurrency with NOWAIT and SKIP LOCKED.

使用示例(带有输出的完整示例可以在上面的链接中找到):

START TRANSACTION;
SELECT * FROM tableName FOR UPDATE SKIP LOCKED;

此外,在此处的参考手册中也包含警告可能会很好:

跳过锁定行的查询会返回不一致的数据视图。 SKIP LOCKED 因此不适合一般事务性工作。但是,当多个会话访问同一个类似队列的表时,它可以用来避免锁争用。

【讨论】:

【解决方案2】:

MySQL 无法忽略 SELECT 中的锁定行。您必须找到一种不同的方法将一行设置为“已处理”。

最简单的方法是在第一个查询中短暂锁定该行以将其标记为“已处理”,然后将其解锁并再次锁定以进行其余处理 - 第二个查询将等待短“标记” " 查询完成,您可以添加显式 WHERE 条件以忽略已标记的行。如果您不想依赖第一个操作能够成功完成,您可能需要添加一些更复杂的时间戳,以便在那些失败的操作之后进行清理。

【讨论】:

  • 一个问题是,在将其标记为“已处理”和解锁然后锁定以进行实际处理之间,该过程可能会失败,我们现在有一行标记为“已处理”,但什么也没有实际处理过。
  • @CMCDragonkai 是的,当考虑到各种故障模式时,这会变得非常复杂。为了解决您提到的特定问题,该行最初可以使用时间戳标记为“处理中”,然后可能会出现另一个进程并取消标记任何已标记为“处理中”时间过长的行。
【解决方案3】:

MySQL 没有这个特性。对于一般搜索此主题的任何人来说,一些 RDBMS 比其他 RDBMS 具有更好/更智能的锁定功能。

对于受限于 MySQL 的开发人员,最好的方法是添加一个可以设置为“已锁定”或“进行中”或类似的列(或使用现有的列,例如状态列),执行 SELECT ID, * ... WHERE IN_PROGRESS != 1 FOR UPDATE; 到获取要锁定的行ID,发出UPDATE .. SET IN_PROGRESS = 1 WHERE ID = XX 解锁记录。

使用LOCK IN SHARE MODE 几乎不是解决方案,因为虽然它可以让您读取旧值,但旧值正在更新过程中,所以除非您执行非原子任务,否则没有意义甚至看那张唱片。

Better* RDBMS 可以识别这种模式(选择要处理的一行并将其锁定、处理、解锁)并提供一种更智能的方法,让您只搜索未锁定的记录。例如,PostgreSQL 9.5+ 提供SELECT ... SKIP LOCKED,它只从与查询匹配的未锁定行子集中进行选择。这使您可以在一行上获得排他锁,服务该记录以完成,然后更新和解锁有问题的记录,而无需阻止其他线程/消费者能够独立工作。

*这里的“更好”是指从原子更新、多消费者架构等角度来看,不一定是“设计得更好”或“整体更好”。不想在这里挑起一场激战。

【讨论】:

    【解决方案4】:

    根据http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

    解决方案是使用LOCK IN SHARE MODE在锁定模式下执行SELECT

    SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;
    

    【讨论】:

    • 在我的情况下,共享模式下的锁定返回所有 3 行(请参阅我的第二个查询),但我只需要 2 行未锁定。锁定的行不应返回;
    • 您是否考虑过根据您在第一次通话中检索到的 ID 过滤结果?您可以在bugs.mysql.com/bug.php?id=49763 找到更多信息
    猜你喜欢
    • 2017-04-13
    • 1970-01-01
    • 2012-12-16
    • 2014-11-28
    • 1970-01-01
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多