【问题标题】:MySQL InnoDB "SELECT FOR UPDATE" - SKIP LOCKED equivalentMySQL InnoDB "SELECT FOR UPDATE" - SKIP LOCKED 等效项
【发布时间】:2016-01-29 10:34:30
【问题描述】:

当我们在 MySQL 中使用 InnoDB 表进行“SELECT FOR UPDATE”时,有什么方法可以跳过“锁定的行”?

例如:终端 t1

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select id from mytable ORDER BY id ASC limit 5 for update;
+-------+
| id    |
+-------+
|     1 |
|    15 |
| 30217 |
| 30218 |
| 30643 |
+-------+
5 rows in set (0.00 sec)

mysql> 

同时终端t2:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select id from mytable where id>30643 order by id asc limit 2 for update;
+-------+
| id    |
+-------+
| 30939 |
| 31211 |
+-------+
2 rows in set (0.01 sec)

mysql> select id from mytable order by id asc limit 5 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> 

因此,如果我启动一个查询,强制它选择其他行,那很好。

但是有没有办法跳过锁定的行?

我猜这应该是并发过程中的冗余问题,但是我没有找到任何解决方案。


编辑: 实际上,我的不同并发进程正在做一些显然非常简单的事情:

  1. 获取第一行(不包含特定标志 - 例如:“WHERE myflag_inUse!=1”)。

  2. 一旦我得到“选择更新”的结果,我就会更新标志并提交行。

所以我只想选择尚未锁定的行以及 myflag_inUse!=1...


以下链接可以帮助我理解为什么会超时,但不能帮助我理解如何避免它:

MySQL 'select for update' behaviour


mysql> SHOW VARIABLES LIKE "%version%";
+-------------------------+-------------------------+
| Variable_name           | Value                   |
+-------------------------+-------------------------+
| innodb_version          | 5.5.46                  |
| protocol_version        | 10                      |
| slave_type_conversions  |                         |
| version                 | 5.5.46-0ubuntu0.14.04.2 |
| version_comment         | (Ubuntu)                |
| version_compile_machine | x86_64                  |
| version_compile_os      | debian-linux-gnu        |
+-------------------------+-------------------------+
7 rows in set (0.00 sec)

【问题讨论】:

  • 不要使用SELECT FOR UPDATE。长时间使用更新锁是一个坏主意,并且仅在使用游标时才真正有用(也不是一个好主意)。您不能使用它来模拟签入/签出机制
  • @PanagiotisKanavos,我不想模仿这种机制。我只想通过跳过锁定的行来选择更新。
  • 修复另一笔交易,这样它就不会花这么长时间!
  • @RickJames,修复交易是什么意思?提交/回滚?如果是,实际上我有一个相当复杂的 sql 查询正在运行,这已经花费了一些时间......所以所有并发请求总是在等待另一个选择以进行更新查询......
  • 向我们展示另一笔交易;让我们讨论一下可以为此做些什么。也就是说,这个问题是“责备受害者”;让我们去追捕“恶棍”。

标签: mysql sql multithreading concurrency innodb


【解决方案1】:

MySQL 8.0 引入了对SKIP LOCKEDNO WAIT 的支持。

SKIP LOCKED 可用于实现作业队列(也称为批处理队列),以便您可以跳过已被并发事务锁定的锁。

NO WAIT 有助于避免等待并发事务释放我们也有兴趣锁定的锁。

如果没有NO WAIT,我们要么必须等到锁被释放(在当前持有锁的事务提交或释放时),要么锁获取超时。 NO WAIT 充当锁定超时,值为0

有关SKIP LOCKNO WAIT的更多详细信息。

【讨论】:

    【解决方案2】:

    从 8.0.1 开始,这似乎现在存在于 MySQL 中:

    https://mysqlserverteam.com/mysql-8-0-1-using-skip-locked-and-nowait-to-handle-hot-rows/

    从 MySQL 8.0.1 开始,我们引入了 SKIP LOCKED 修饰符 可用于从表中非确定性地读取行 同时跳过锁定的行。这可以由 我们的预订系统可以跳过待处理的订单。例如:

    但是,我认为该版本不一定是生产就绪的。

    【讨论】:

      【解决方案3】:

      使用一些合适的LIMITPRIMARY KEY 的块的形式遍历表格,这样您就不会一次查看“太多”行。

      通过使用 PK,您可以以可预测的方式订购东西;这实际上消除了死锁。

      通过使用LIMIT,您将避免一次占用太多。 LIMIT 应体现为 PK 上的范围。这很清楚两个线程是否即将相互踩踏。

      更多详情(间接)在我的blog on big deletes 中。

      【讨论】:

      • 感谢您的回答。实际上,当您在单进程程序中时,这可能是解决方案......正如我所说,我有不同的并发进程,目标是跨越整个数据库。但是没有办法知道哪个线程在时间 t 选择/锁定了哪些行。
      • 是的。而且我的建议仍然会遇到 lock_waits 甚至死锁。但是,我希望我的建议会大大减少锁定,从而使整个过程可以忍受。
      • 是的,正如我在下面的回答 (stackoverflow.com/a/33515992/5165343) 中所说,我将数百万“无用的行”移到单独的表中以节省一些时间...
      【解决方案4】:

      很遗憾,目前看来没有办法跳过 select for update 中的锁定行。

      如果我们可以使用像 Oracle 'FOR UPDATE SKIP LOCKED' 这样的东西,那就太好了。

      在我的例子中,并行启动的查询完全相同,并且在数百万行中包含一个“where”子句和一个“group by”...因为查询需要 20 到 40 秒运行,这是(正如我已经知道的)问题的很大一部分。

      我看到的唯一 -临时而非最佳- 解决方案是按顺序移动一些(即:数百万)我不会(直接)使用的行减少查询所需的时间

      所以我仍然会有相同的行为,但我会等待更少的时间......

      我期待有一种方法可以不在选择中选择锁定的行。

      我不会将此标记为答案,因此如果添加(或发现)来自 mysql 的新子句,我可以稍后接受...

      【讨论】:

      • 我在 mysql 5.7 上遇到了同样的问题,我正在努力寻找解决方案。我的桌子充当队列。我有多个线程从表中读取数据以一次处理一行。因此我的查询使用'limit 1 for update'。我同意它似乎没有办法跳过锁定的行,除非在 mysql8 之后
      【解决方案5】:

      很抱歉,我认为您从错误的角度处理问题。如果您的用户想要列出满足特定选择条件的表中的记录,那么您的查询应该将它们全部返回,或者返回错误消息并且不提供任何结果集。但是查询不应该只返回结果的一个子集,让用户相信他拥有所有匹配的记录。

      应该通过确保您的应用程序在尽可能短的时间内锁定尽可能少的行来解决该问题。

      【讨论】:

      • 感谢您的回答。但是,就我而言,我有几个进程在 //.他们更新 mytable 上的数据。我只想更新一些数据,以便能够在 // 中启动多个线程,并使全局更新更快。当然我在真正的 where 子句中有额外的条件,但我这里只展示一个简单的例子。
      • 很抱歉,我无法对我不熟悉的设计发表评论。您需要查看您的流程并评估它们锁定记录的方式是否合适,甚至这些流程的设计方式是否合适。
      • 好的。好吧,我不同的并发进程正在做一些显然非常简单的事情:取第一行(不包含特定标志)。一旦我得到“选择更新”的结果,我就会更新标志并提交行。所以我只想选择尚未锁定的行...
      • @Bast “真正简单”的事情实际上是一个复杂的操作——您正在尝试模拟签入/签出操作。如您所见,您无法使用更新锁来模拟这一点。您不仅会遇到超时,您的应用程序还必须在整个操作期间保持连接打开。如果您尝试在另一台机器上执行长时间运行的操作,那是个坏主意
      • @Bast 然后使用标志字段而不是锁定,以及允许您查看未提交更改或更新长期事务之外的标志字段的隔离模式。
      猜你喜欢
      • 2019-04-16
      • 2021-12-05
      • 2018-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-25
      • 2016-08-14
      相关资源
      最近更新 更多