Documentation Home
MySQL 8.0 参考手册  / 第 15 章 InnoDB 存储引擎  / 15.7 InnoDB 锁定和事务模型  /  15.7.3 InnoDB中不同SQL语句设置的锁

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

locking read、 an UPDATE或 generally set 记录锁定在 SQL 语句处理中扫描 的DELETE每个索引记录。WHERE语句中是否存在排除该行的条件并不重要 。InnoDB不记得确切的WHERE情况,只知道扫描了哪些索引范围。这些锁通常是 next-key 锁,它也会阻止插入记录之前的间隙” 。但是, 可以显式禁用间隙锁定,这会导致不使用下一键锁定。有关详细信息,请参阅 第 15.7.1 节,“InnoDB 锁定”。事务隔离级别也会影响设置的锁;请参阅 第 15.7.2.1 节,“事务隔离级别”

如果在搜索中使用二级索引并且要设置的索引记录锁是独占的,InnoDB还检索相应的聚簇索引记录并对其设置锁。

如果您没有适合您的语句的索引,并且 MySQL 必须扫描整个表来处理该语句,那么表的每一行都会被锁定,这反过来会阻止其他用户对该表的所有插入。创建良好的索引很重要,这样您的查询就不会扫描不必要的行。

InnoDB如下设置特定类型的锁。

  • SELECT ... FROM是一致读取,读取数据库的快照并且不设置锁,除非事务隔离级别设置为 SERIALIZABLE. 对于 SERIALIZABLE级别,搜索在它遇到的索引记录上设置共享的下一键锁。但是,对于使用唯一索引锁定行以搜索唯一行的语句,只需要索引记录锁。

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

  • 对于锁定读取SELECTwith FOR UPDATEor FOR SHARE)、 UPDATEDELETE语句,所采取的锁定取决于该语句是使用具有唯一搜索条件的唯一索引还是范围类型搜索条件。

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

    • 对于其他搜索条件,以及非唯一索引, InnoDB锁定扫描的索引范围,使用间隙锁下一个键锁 来阻止其他会话插入到范围所覆盖的间隙中。有关间隙锁和下一键锁的信息,请参阅第 15.7.1 节,“InnoDB 锁定”

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

  • UPDATE ... WHERE ...在搜索遇到的每条记录上设置排他的下一键锁。但是,对于使用唯一索引锁定行以搜索唯一行的语句,只需要索引记录锁。

  • 修改聚簇索引记录时UPDATE,会对受影响的二级索引记录进行隐式锁定。UPDATE在插入新的二级索引记录之前执行重复检查扫描时,以及插入新的二级索引记录时, 该 操作还会对受影响的二级索引记录使用共享锁。

  • DELETE FROM ... WHERE ...在搜索遇到的每条记录上设置排他的下一键锁。但是,对于使用唯一索引锁定行以搜索唯一行的语句,只需要索引记录锁。

  • INSERT在插入的行上设置排他锁。这个锁是index-record lock,不是next-key lock(即没有gap lock),不会阻止其他session插入插入行之前的gap。

    在插入行之前,设置一种称为插入意图间隙锁的间隙锁。这个锁表示插入的意图,这样插入到同一个索引间隙中的多个事务如果没有插入到间隙中的相同位置,则不需要相互等待。假设有值为 4 和 7 的索引记录。尝试插入值 5 和 6 的单独事务在获得对插入行的排他锁之前分别使用插入意向锁锁定 4 和 7 之间的间隙,但不相互阻塞,因为行不冲突。

    如果发生重复键错误,则会在重复索引记录上设置共享锁。如果另一个会话已经具有独占锁,那么如果有多个会话试图插入同一行,那么使用共享锁可能会导致死锁。如果另一个会话删除该行,就会发生这种情况。假设一个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与 simple INSERT的不同之处在于,当发生重复键错误时,将排他锁而不是共享锁放在要更新的行上。对重复的主键值采用独占索引记录锁。对重复的唯一键值采用独占下一键锁。

  • REPLACE就像 INSERT在唯一键上没有碰撞一样完成。否则,将排他的下一键锁放置在要替换的行上。

  • INSERT INTO T SELECT ... FROM S WHERE ... 在插入到的每一行上设置排他索引记录锁(没有间隙锁)T。如果事务隔离级别为READ COMMITTEDInnoDB则将搜索S作为一致读取(无锁)进行。否则,InnoDB对来自 的行设置共享的下一键锁SInnoDB在后一种情况下必须设置锁:在使用基于语句的二进制日志进行前滚恢复期间,每个 SQL 语句都必须以与最初相同的方式执行。

    CREATE TABLE ... SELECT ...使用共享的下一键锁或作为一致读取执行 SELECT,至于 INSERT ... SELECT.

    SELECT在结构 REPLACE INTO t SELECT ... FROM s WHERE ... or中使用 a 时UPDATE t ... WHERE col IN (SELECT ... FROM s ...)InnoDB对表中的行设置共享的下一键锁s

  • InnoDBAUTO_INCREMENT在初始化表上先前指定的列时,在与列关联的索引的末尾设置排他锁 AUTO_INCREMENT

    使用 innodb_autoinc_lock_mode=0, InnoDB使用特殊的 AUTO-INC表锁模式,在访问自增计数器时获取锁并保持到当前 SQL 语句的末尾(而不是整个事务的末尾)。AUTO-INC当持有表锁时,其他客户端无法插入到表中。相同的行为发生在批量插入innodb_autoinc_lock_mode=1。表级AUTO-INC锁不与 一起使用 innodb_autoinc_lock_mode=2。有关更多信息,请参阅 第 15.6.1.6 节,“InnoDB 中的 AUTO_INCREMENT 处理”

    InnoDBAUTO_INCREMENT在不设置任何锁 的情况下获取先前初始化的列的值。

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

  • LOCK TABLESInnoDB设置表锁,但它是设置这些锁 的层之上的更高 MySQL 层 。InnoDB如果 innodb_table_locks = 1(默认)和 知道表锁autocommit = 0,并且上面的 MySQL 层InnoDB知道行级锁。

    否则,InnoDB的自动死锁检测无法检测到涉及此类表锁的死锁。此外,因为在这种情况下,较高的 MySQL 层不知道行级锁,所以有可能在另一个会话当前具有行级锁的表上获得表锁。但是,这不会危及事务完整性,如 第 15.7.5.2 节“死锁检测”中所述。

  • LOCK TABLESinnodb_table_locks=1如果(默认),则在每个表上获取两个锁。除了MySQL层的表锁,它还获取了InnoDB表锁。为避免获取InnoDB表锁,请设置 innodb_table_locks=0. 如果没有 InnoDB获取表锁, LOCK TABLES即使表的某些记录被其他事务锁定,也会完成。

    在 MySQL 8.0 中, innodb_table_locks=0对使用 显式锁定的表没有影响 LOCK TABLES ... WRITELOCK TABLES ... WRITE它确实对通过隐式(例如,通过触发器)或通过锁定以供读取或写入的表有影响 LOCK TABLES ... READ

  • InnoDB当事务提交或中止时,事务持有的 所有锁都会被释放。因此,在mode中调用表 没有多大意义, LOCK TABLES因为 获取的表锁将立即释放。 InnoDBautocommit=1InnoDB

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