Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  Locks Set by Different SQL Statements in InnoDB

17.7.3 InnoDB 中的不同 SQL 语句设置的锁

锁定读取、UPDATEDELETE 通常在处理 SQL 语句时设置每个索引记录的锁。无论语句中是否有 WHERE 条件,它们都不会被排除。InnoDB 不记忆确切的 WHERE 条件,只知道哪些索引范围被扫描。锁通常是 next-key 锁,它们也阻止了紧挨着记录的 间隙 中的插入。然而,间隙锁 可以被明确禁用,从而不使用 next-key 锁。有关更多信息,请参阅 第 17.7.1 节,“InnoDB 锁定”。事务隔离级别也可以影响设置哪些锁;请参阅 第 17.7.2.1 节,“事务隔离级别”

如果在搜索中使用了辅助索引,并且要设置的索引记录锁是独占的,InnoDB 也会检索相应的聚簇索引记录,并设置锁。

如果您没有合适的索引来满足语句,MySQL 必须扫描整个表来处理语句,这将锁定表中的每一行,从而阻止其他用户对表的插入。因此,创建良好的索引非常重要,以便您的查询不扫描不必要的行。

InnoDB 按照以下方式设置特定的锁类型。

  • SELECT ... FROM 是一致的读取,读取数据库的快照,不设置锁,除非事务隔离级别设置为 SERIALIZABLE。对于 SERIALIZABLE 级别,搜索设置共享的 next-key 锁在它遇到的索引记录上。然而,只需要一个索引记录锁来锁定使用唯一索引搜索唯一行的行。

  • SELECT ... FOR UPDATESELECT ... FOR SHARE 语句使用唯一索引锁定扫描的行,并释放不符合结果集的行的锁(例如,如果它们不满足 WHERE 子句中的条件)。然而,在某些情况下,行可能不会立即解锁,因为结果行与其原始来源之间的关系在查询执行期间丢失了。例如,在 UNION 中,扫描(和锁定)表中的行可能会被插入到临时表中,以评估它们是否符合结果集。在这种情况下,临时表中的行与原始表中的行之间的关系丢失了,直到查询执行结束时原始表中的行才被解锁。

  • 对于 锁定读取SELECTFOR UPDATEFOR SHARE)、UPDATEDELETE 语句,锁的设置取决于语句是否使用唯一索引和唯一搜索条件或范围搜索条件。

    • 对于唯一索引和唯一搜索条件,InnoDB 只锁定找到的索引记录,而不是锁定它之前的 间隙

    • 对于其他搜索条件和非唯一索引,InnoDB 锁定扫描的索引范围,使用 间隙锁next-key 锁 来阻止其他会话中的插入到扫描范围内的间隙中。有关间隙锁和 next-key 锁的信息,请参阅 第 17.7.1 节,“InnoDB 锁定”

  • 对于索引记录,搜索遇到的记录, SELECT ... FOR UPDATE 阻止其他会话执行 SELECT ... FOR SHARE 或在某些事务隔离级别下读取。一致读取忽略读视图中存在的记录锁。

  • UPDATE ... WHERE ... 在搜索遇到的每个记录上设置独占的 next-key 锁。然而,只需要索引记录锁来锁定使用唯一索引搜索唯一行的行。

  • UPDATE 修改聚簇索引记录时,隐式锁定受影响的辅助索引记录。 UPDATE 操作还在执行重复检查扫描之前锁定受影响的辅助索引记录,并在插入新辅助索引记录时锁定。

  • DELETE FROM ... WHERE ... 在搜索遇到的每个记录上设置独占的 next-key 锁。然而,只需要索引记录锁来锁定使用唯一索引搜索唯一行的行。

  • INSERT 在插入的行上设置独占锁。这锁是一个索引记录锁,而不是 next-key 锁(即,不是间隙锁),并且不阻止其他会话在插入行之前的间隙中插入。

    在插入行之前,一个类型的间隙锁称为插入意图间隙锁被设置。这锁信号意图插入,以便多个事务插入同一个索引间隙不需要等待对方,如果它们不是在同一位置插入间隙。假设有索引记录的值为 4 和 7。单独的事务尝试插入值 5 和 6,每个锁定索引间隙的插入意图锁之前获得插入行的独占锁,但它们不阻塞对方,因为行不是冲突的。

    如果发生重复键错误,设置共享锁在重复索引记录上。这使用共享锁可能导致死锁,如果有多个会话尝试插入同一行,而另一个会话已经拥有独占锁。这可能发生在另一个会话删除该行时。假设 InnoDBt1 具有以下结构:

    CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;

    现在假设三个会话按顺序执行以下操作:

    会话 1:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    会话 2:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    会话 3:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    会话 1:

    ROLLBACK;

    会话 1 的第一个操作获取该行的独占锁。会话 2 和 3 的操作都导致重复键错误,并且它们都请求该行的共享锁。当会话 1 回滚时,它释放该行的独占锁,并且会话 2 和 3 的共享锁请求被授予。此时,会话 2 和 3 死锁:它们都不能获取该行的独占锁,因为对方持有共享锁。

    类似情况也会发生,如果表已经包含键值为 1 的行,并且三个会话按顺序执行以下操作:

    会话 1:

    START TRANSACTION;
    DELETE FROM t1 WHERE i = 1;

    会话 2:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    会话 3:

    START TRANSACTION;
    INSERT INTO t1 VALUES(1);

    会话 1:

    COMMIT;

    会话 1 的第一个操作获取该行的独占锁。会话 2 和 3 的操作都导致重复键错误,并且它们都请求该行的共享锁。当会话 1 提交时,它释放该行的独占锁,并且会话 2 和 3 的共享锁请求被授予。此时,会话 2 和 3 死锁:它们都不能获取该行的独占锁,因为对方持有共享锁。

  • INSERT ... ON DUPLICATE KEY UPDATE 与简单的 INSERT 不同的是,当重复键错误发生时,独占锁而不是共享锁被设置在要更新的行上。当重复主键值时,独占索引记录锁被设置;当重复唯一键值时,独占 next-key 锁被设置。

  • REPLACE 如果没有唯一键碰撞,则类似于 INSERT。否则,对要替换的行设置独占 next-key 锁。

  • INSERT INTO T SELECT ... FROM S WHERE ... 在插入到 T 时设置独占索引记录锁(不包括间隙锁)在每一行上。如果事务隔离级别是 READ COMMITTEDInnoDB 将在 S 上执行一致读取(不锁定)。否则,InnoDB 将在 S 上设置共享 next-key 锁。在回滚恢复使用基于语句的二进制日志时,每个 SQL 语句都必须以完全相同的方式执行。

    CREATE TABLE ... SELECT ... 执行 SELECT,使用共享 next-key 锁或一致读取,类似于 INSERT ... SELECT

    SELECT 用于构造 REPLACE INTO t SELECT ... FROM s WHERE ...UPDATE t ... WHERE col IN (SELECT ... FROM s ...) 时,InnoDB 将在表 s 上设置共享 next-key 锁。

  • InnoDB 在初始化之前指定的 AUTO_INCREMENT 列时,设置关联索引的独占锁。

    使用 innodb_autoinc_lock_mode=0InnoDB 使用特殊的 AUTO-INC 表锁模式,其中锁被获取并持有到当前 SQL 语句的结尾(而不是整个事务的结尾),同时访问自动递增计数器。其他客户端不能在 AUTO-INC 表锁被持有时插入表中。同样的行为也出现在 “批量插入” 中,使用 innodb_autoinc_lock_mode=1。表级 AUTO-INC 锁不用于 innodb_autoinc_lock_mode=2。更多信息,请参阅 第 17.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”

    InnoDB 在不设置锁的情况下获取之前初始化的 AUTO_INCREMENT 列的值。

  • 如果在表上定义了 FOREIGN KEY 约束,那么任何插入、更新或删除操作都需要检查约束条件,InnoDB 将在检查约束时设置共享记录级锁。在约束失败的情况下,InnoDB 也设置这些锁。

  • LOCK TABLES 设置表锁,但这是 MySQL 层以上的 InnoDB 层设置这些锁。InnoDB 如果 innodb_table_locks = 1(默认)和 autocommit = 0,则知道表锁。MySQL 层以上的 InnoDB 知道行级锁。

    否则,InnoDB 的自动死锁检测无法检测涉及表锁的死锁。此外,因为在这种情况下 MySQL 层不知道行级锁,因此可能在另一个会话当前拥有行级锁的表上获取表锁。但这并不危及事务完整性,如 第 17.7.5.2 节,“死锁检测” 中所讨论的那样。

  • LOCK TABLES 如果 innodb_table_locks=1(默认),则在每个表上获取两个锁。在 MySQL 层上的表锁之外,还获取 InnoDB 表锁。要避免获取 InnoDB 表锁,请设置 innodb_table_locks=0。如果不获取 InnoDB 表锁,LOCK TABLES 将完成,即使某些记录被其他事务锁定。

    在 MySQL 8.3 中,innodb_table_locks=0 对于使用 LOCK TABLES ... WRITE 显式锁定的表没有效果。但是,它对通过 LOCK TABLES ... WRITE 隐式锁定以供读取或写入的表(例如,通过触发器)或通过 LOCK TABLES ... READ 锁定的表有影响。

  • 所有由事务持有的 InnoDB 锁在事务提交或回滚时释放。因此,在 autocommit=1 模式下对 InnoDB 表调用 LOCK TABLES 没有太多意义,因为获取的 InnoDB 表锁将立即释放。

  • 您不能在事务中间锁定其他表,因为 LOCK TABLES 执行隐式 COMMITUNLOCK TABLES