MySQL8.0 InnoDB支持 NOWAIT和SKIP LOCKED选项SELECT ... FOR SHARE以及SELECT ... FOR UPDATE锁定读取语句。 NOWAIT如果请求的行被另一个事务锁定,则会立即返回该语句。SKIP LOCKED从结果集中删除锁定的行。
如果查询数据然后在同一事务中插入或更新相关数据,则常规SELECT 语句不会提供足够的保护。其他事务可以更新或删除您刚查询的相同行。 InnoDB支持两种类型的 锁定读取,提供额外的安全性:
InnoDB 支持执行 SELECT ... FOR SHARE 和 SELECT ... FOR UPDATE 锁定读语句时,带有NOWAIT 和 SKIP LOCKED 选项。如果请求行已经被其他事务锁定, NOWAIT 会立刻返回结果。 SKIP LOCKED 将锁定的行从结果中移除。
官方文档:
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html#innodb-locking-reads-nowait-skip-locked
1、SELECT ... FOR SHARE:
在读取的任何行上设置共享模式锁定。其他会话可以读取行,但在事务提交之前无法修改它们。如果这些行中的任何行已被另一个尚未提交的事务更改,则查询将等待该事务结束,然后使用最新值。
注意:
SELECT ... FOR SHARE是替代SELECT ... LOCK IN SHARE MODE,但 LOCK IN SHARE MODE仍可用于向后兼容。
2、SELECT ... FOR UPDATE:
对于搜索遇到的索引记录,锁定行和任何关联的索引条目,就像您UPDATE为这些行发出 语句一样。阻止其他事务更新这些行,执行SELECT ... FOR SHARE或从某些事务隔离级别读取数据。一致性读取将忽略在读取视图中存在的记录上设置的任何锁定。(旧版本的记录无法锁定;它们是通过在记录的内存副本上应用撤消日志来重建的 。)
注意:
①:提交或回滚事务时,将释放 由设置FOR SHARE和FOR UPDATE查询设置的所有锁。
②:只有在禁用自动提交时(通过使用START TRANSACTION或通过设置 autocommit为0 开始事务处理),才能锁定读取 。
除非在子查询中指定了锁定读取子句,否则外部语句中的锁定读取子句不会锁定嵌套子查询中的表行。例如,以下语句不会锁定表中的行 t2:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2) FOR UPDATE;
要锁定表中的行,t2请在子查询中添加一个锁定读取子句:
SELECT * FROM t1 WHERE c1 = (SELECT c1 FROM t2 FOR UPDATE) FOR UPDATE;
3、使用NOWAIT和SKIP LOCKED锁定读取并发
如果行被事务锁定 ,则请求相同锁定行的事务SELECT ... FOR UPDATE或SELECT ... FOR SHARE事务必须等到阻塞事务释放行锁。此行为可防止事务更新或删除由其他事务查询以进行更新的行。但是,如果您希望在请求的行被锁定时立即返回查询,或者从结果集中排除锁定的行是可接受的,则无需等待释放行锁定。
为了避免等待其他事务释放行锁, NO WAIT并且SKIP LOCKED 选项可以与 read语句一起使用SELECT ... FOR UPDATE或SELECT ... FOR SHARE锁定。
①:NOWAIT: 使用NOWAIT永不等待获取行锁定的锁定读取。查询立即执行,如果请求的行被锁定,则失败并显示错误。
②:SKIP LOCKED: 使用SKIP LOCKED 永不等待获取行锁定的锁定读取。查询立即执行,从结果集中删除锁定的行。
注意:
①:跳过锁定行的查询会返回数据的不一致视图。SKIP LOCKED因此不适合一般交易工作。但是,当多个会话访问同一个类似队列的表时,它可用于避免锁争用。
②:NO WAIT和SKIP LOCKED 仅适用于行级锁。
以下示例演示NOWAIT 和SKIP LOCKED。会话1启动一个事务,该事务对单个记录执行行锁定。会话2尝试使用该NOWAIT选项对同一记录进行锁定读取 。由于请求的行被会话1锁定,因此锁定读取会立即返回错误。在会话3中,锁定读取SKIP LOCKED返回请求的行,但会话1锁定的行除外。
# Session 1:
mysql> CREATE TABLE t (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
mysql> INSERT INTO t (i) VALUES(1),(2),(3);
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE;
+---+
| i |
+---+
| 2 |
+---+
# Session 2:
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 2 FOR UPDATE NOWAIT;
ERROR 3572 (HY000): Do not wait for lock.
# Session 3:
mysql> START TRANSACTION;
mysql> SELECT * FROM t FOR UPDATE SKIP LOCKED;
+---+
| i |
+---+
| 1 |
| 3 |
+---+