Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


17.7.2.4 锁定读取

如果您查询数据,然后在同一事务中插入或更新相关数据,常规SELECT语句不足以提供保护。其他事务可以更新或删除您刚刚查询的行。InnoDB支持两个类型的锁定读取,提供额外的安全性:

  • SELECT ... FOR SHARE

    在读取任何行时设置共享模式锁。其他会话可以读取这些行,但直到您的事务提交它们不能修改。如果这些行被另一个尚未提交的事务所更改,您的查询将等待该事务结束,然后使用最新值。

    Note

    SELECT ... FOR SHARESELECT ... LOCK IN SHARE MODE的替代品,但LOCK IN SHARE MODE仍然可用于向后兼容性。两个语句是等效的。但是,FOR SHARE支持OF table_nameNOWAITSKIP LOCKED选项。请参阅Locking Read Concurrency with NOWAIT and SKIP LOCKED

    SELECT ... FOR SHARE需要SELECT权限。

    SELECT ... FOR SHARE 语句不在 MySQL 授权表上获取读锁。更多信息,请见授权表并发性

  • SELECT ... FOR UPDATE

    对于索引记录,搜索将锁定行和相关的索引条目,就像对这些行执行了一个UPDATE语句一样。其他事务将被阻塞,以便不能更新这些行,也不能执行SELECT ... FOR SHARE语句,或者在某些事务隔离级别下读取数据。consistent reads 将忽略对读视图中记录的锁定。(老版本的记录无法锁定;它们将通过在内存副本上应用undo日志来重建。)

    SELECT ... FOR UPDATE 需要SELECT 权限,以及至少一个DELETELOCK TABLESUPDATE 权限。

这些子句主要有助于处理树结构或图结构数据,可能分布在单个表中或跨多个表。您可以从一个地方到另一个地方遍历边缘或树枝,同时保留更改这些指针值的权利。

所有由FOR SHAREFOR UPDATE查询设置的锁定都将在事务提交或回滚时释放。

Note

只有在自动提交被禁用(通过开始事务或设置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;

假设您想将新行插入到表child中,并确保该子行在表parent中存在。您的应用程序代码可以确保引用完整性整个操作序列。

首先,使用一致读查询表PARENT并验证父行是否存在。您可以安全地将子行插入到表CHILD中吗?不,因为其他会话可能在您的SELECTINSERT之间删除父行,而您不知道。

为了避免这个潜在问题,使用FOR SHARE来执行SELECT:

SELECT * FROM parent WHERE NAME = 'Jones' FOR SHARE;

在执行了FOR SHARE查询后,您可以安全地将子记录添加到CHILD表中,并提交事务。任何尝试在PARENT表中的适用行上获取排他锁的事务都将等待您完成,即直到所有表中的数据处于一致状态。

例如,考虑一个用于为每个子记录分配唯一标识符的整数计数字段在表CHILD_ CODES中。不要使用一致读或共享模式读来读取计数器的当前值,因为两个数据库用户可能看到相同的计数器值,如果两个事务尝试将具有相同标识符的行添加到CHILD表中,将出现重复键错误。

在这里,FOR SHARE并不是一个好的解决方案,因为如果两个用户同时读取计数器至少其中一个将在尝试更新计数器时陷入死锁。

要实现读取和递增计数器,请首先使用FOR UPDATE对计数器进行锁定读,然后递增计数器。例如:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

一个SELECT ... FOR UPDATE读取最新可用的数据,设置每个行上的排他锁。因此,它设置了与搜索SQLUPDATE将设置的相同锁。

以下描述仅是SELECT ... FOR UPDATE 的一个示例。在 MySQL 中,生成唯一标识符的特定任务实际上可以使用单个访问表来完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT 语句仅检索当前连接的标识信息(具体到当前连接)。它不访问任何表。

如果事务锁定了一行,如果一个请求相同锁定的行的SELECT ... FOR UPDATESELECT ... FOR SHARE事务必须等待阻塞事务释放行锁。这行为防止了事务更新或删除被其他事务查询更新的行。然而,如果你想要查询在请求行锁时立即返回,或者排除锁定的行从结果集中,则不需要等待行锁释放。

避免等待其他事务释放行锁,可以使用NOWAITSKIP LOCKED选项与SELECT ... FOR UPDATESELECT ... FOR SHARE锁定读语句。

  • NOWAIT

    使用NOWAIT的锁定读从不等待获取行锁。查询立即执行,如果请求行被锁定,则失败并返回错误。

  • SKIP LOCKED

    使用SKIP LOCKED的锁定读操作从不等待获取行锁。查询立即执行,删除结果集中被锁定的行。

    Note

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

NOWAITSKIP LOCKED仅适用于行级锁定。

使用NOWAITSKIP LOCKED的语句不安全用于语句基于的复制。

以下示例演示了NOWAITSKIP LOCKED。会话1开始一个事务,锁定单个记录。会话2尝试使用NOWAIT选项对同一记录进行锁定读操作,因为请求的行被会话1锁定,锁定读操作立即返回错误。在会话3中,使用SKIP LOCKED的锁定读操作返回请求的行 except for the row that is locked by Session 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 |
+---+