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

[](<>)[](<>)

一种[锁定读取](glossary.html#glos_locking_read)， 一个[`更新`](update.html), 或[`删除`](delete.html)通常对在 SQL 语句处理过程中扫描到的每条索引记录设置记录锁。有没有没关系`在哪里`语句中将排除该行的条件。`InnoDB`不记得确切的`在哪里`条件，但只知道扫描了哪些索引范围。锁是正常的[下一键锁](glossary.html#glos_next_key_lock)这也会阻止插入记录之前的“间隙”。然而，[间隙锁定](glossary.html#glos_gap_lock)可以显式禁用，这会导致不使用下一个键锁定。有关详细信息，请参阅[第 15.7.1 节，“InnoDB 锁定”](innodb-locking.html).事务隔离级别也会影响设置哪些锁；看[第 15.7.2.1 节，“事务隔离级别”](innodb-transaction-isolation-levels.html).

如果在搜索中使用二级索引并且要设置的索引记录锁是排他的，`InnoDB`还检索相应的聚集索引记录并在它们上设置锁。

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

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

-   [`选择...从`](select.html)是一致性读取，读取数据库的快照并且不设置锁，除非事务隔离级别设置为[`可序列化`](innodb-transaction-isolation-levels.html#isolevel_serializable).为了[`可序列化`](innodb-transaction-isolation-levels.html#isolevel_serializable)级别，搜索在它遇到的索引记录上设置共享的下一个键锁。但是，使用唯一索引锁定行以搜索唯一行的语句只需要索引记录锁。

-   [`选择 ... 更新`](select.html)和[`选择...分享`](select.html)使用唯一索引的语句为扫描的行获取锁，并为不符合包含在结果集中的行释放锁（例如，如果它们不符合`在哪里`条款）。但是，在某些情况下，行可能不会立即解锁，因为结果行与其原始源之间的关系在查询执行期间丢失。例如，在一个[`联盟`](union.html)，在评估它们是否符合结果集的条件之前，可能会将表中的已扫描（和锁定）行插入到临时表中。在这种情况下，临时表中的行与原始表中的行之间的关系丢失，并且后面的行直到查询执行结束才解锁。

-   为了[锁定读取](glossary.html#glos_locking_read)([`选择`](select.html)和`更新`要么`分享`),[`更新`](update.html)， 和[`删除`](delete.html)语句，所采用的锁取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型的搜索条件。

    -   对于具有唯一搜索条件的唯一索引，`InnoDB`只锁定找到的索引记录，而不是[差距](glossary.html#glos_gap)在它之前。

    -   对于其他搜索条件和非唯一索引，`InnoDB`锁定扫描的索引范围，使用[间隙锁](glossary.html#glos_gap_lock)要么[下一键锁](glossary.html#glos_next_key_lock)阻止其他会话插入范围覆盖的间隙。有关间隙锁和下一键锁的信息，请参阅[第 15.7.1 节，“InnoDB 锁定”](innodb-locking.html).

-   对于搜索遇到的索引记录，[`选择 ... 更新`](select.html)阻止其他会话执行[`选择...分享`](select.html)或通过读取某些事务隔离级别。一致读取忽略读取视图中存在的记录上设置的任何锁定。

-   [`更新...在哪里...`](update.html)在搜索遇到的每条记录上设置一个排他的 next-key 锁。但是，使用唯一索引锁定行以搜索唯一行的语句只需要索引记录锁。

-   什么时候[`更新`](update.html)修改聚集索引记录，对受影响的二级索引记录进行隐式锁定。这[`更新`](update.html)在插入新的二级索引记录之前执行重复检查扫描以及插入新的二级索引记录时，操作也会对受影响的二级索引记录进行共享锁。

-   [`从...删除...在哪里...`](delete.html)在搜索遇到的每条记录上设置一个排他的 next-key 锁。但是，使用唯一索引锁定行以搜索唯一行的语句只需要索引记录锁。

-   [`插入`](insert.html)在插入的行上设置排他锁。此锁是索引记录锁，而不是 next-key 锁（即没有间隙锁），并且不会阻止其他会话在插入行之前插入间隙中。

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

    如果发生重复键错误，则会在重复索引记录上设置共享锁。如果另一个会话已经拥有排他锁，那么如果有多个会话尝试插入同一行，则使用共享锁可能会导致死锁。如果另一个会话删除了该行，就会发生这种情况。假设一个`InnoDB`桌子`t1`具有以下结构：

    ```
    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);
    ```

    第三节：

    ```
    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);
    ```

    第三节：

    ```
    START TRANSACTION;
    INSERT INTO t1 VALUES(1);
    ```

    第 1 节：

    ```
    COMMIT;
    ```

    会话 1 的第一个操作获取行的排他锁。会话 2 和 3 的操作都导致重复键错误，并且它们都请求该行的共享锁。当会话 1 提交时，它会释放其对该行的排他锁，并授予会话 2 和 3 的排队共享锁请求。此时，会话 2 和会话 3 死锁：由于对方持有共享锁，两者都无法获得该行的排他锁。

-   [`插入 ... 在重复密钥更新时`](insert-on-duplicate.html)不同于简单的[`插入`](insert.html)当发生重复键错误时，将在要更新的行上放置排他锁而不是共享锁。对重复的主键值采用排他索引记录锁。对重复的唯一键值采用独占的下一个键锁。

-   [`代替`](replace.html)就像一个[`插入`](insert.html)如果唯一键没有冲突。否则，将在要替换的行上放置一个独占的 next-key 锁。

-   `插入 T SELECT ... 从 S WHERE ...`在插入的每一行上设置一个排他索引记录锁（没有间隙锁）`吨`.如果事务隔离级别是[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed),`InnoDB`搜索`小号`作为一致读取（无锁）。否则，`InnoDB`在来自的行上设置共享的下一个键锁`小号`.`InnoDB`在后一种情况下必须设置锁：在使用基于语句的二进制日志进行前滚恢复期间，每个 SQL 语句必须以与最初执行完全相同的方式执行。

    [`创建表...选择...`](create-table.html)执行[`选择`](select.html)使用共享的下一个键锁或作为一致的读取，如[`插入...选择`](insert-select.html).

    当一个`选择`在构造中使用`REPLACE INTO t SELECT ... FROM s WHERE ...`要么`更新 t ... WHERE col IN (SELECT ... FROM s ...)`,`InnoDB`对表中的行设置共享的下一个键锁`s`.

-   `InnoDB`在与`自动递增`在初始化先前指定的列时`自动递增`桌子上的列。

    和[`innodb_autoinc_lock_mode=0`](innodb-parameters.html#sysvar_innodb_autoinc_lock_mode),`InnoDB`使用特殊的`汽车公司`表锁模式，在访问自增计数器时获取锁并保持到当前 SQL 语句的末尾（而不是整个事务的末尾）。其他客户端不能插入到表中，而`汽车公司`持有表锁。“批量插入”也会发生相同的行为[`innodb_autoinc_lock_mode=1`](innodb-parameters.html#sysvar_innodb_autoinc_lock_mode).表级`汽车公司`锁不与[`innodb_autoinc_lock_mode=2`](innodb-parameters.html#sysvar_innodb_autoinc_lock_mode).有关详细信息，请参阅[第 15.6.1.6 节，“自动\_InnoDB 中的增量处理”](innodb-auto-increment-handling.html).

    `InnoDB`获取先前初始化的值`自动递增`列而不设置任何锁。

-   如果一个`外键`约束是在表上定义的，任何需要检查约束条件的插入、更新或删除都会在它查看的记录上设置共享记录级锁以检查约束。`InnoDB`在约束失败的情况下也设置这些锁。

-   [`锁定表`](lock-tables.html)设置表锁，但它是高于 MySQL 的更高层`InnoDB`设置这些锁的层。`InnoDB`知道表锁，如果`innodb_table_locks = 1`（默认）和[`自动提交 = 0`](server-system-variables.html#sysvar_autocommit)，以及上面的 MySQL 层`InnoDB`了解行级锁。

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

-   [`锁定表`](lock-tables.html)如果在每个表上获取两个锁`innodb_table_locks=1`（默认）。除了 MySQL 层的表锁之外，它还获取了一个`InnoDB`表锁。为了避免获得`InnoDB`表锁，设置`innodb_table_locks=0`.如果不`InnoDB`获得表锁，[`锁定表`](lock-tables.html)即使表的某些记录被其他事务锁定，也会完成。

    在 MySQL 8.0 中，[`innodb_table_locks=0`](innodb-parameters.html#sysvar_innodb_table_locks)对显式锁定的表无效[`锁定表...写入`](lock-tables.html).它确实对锁定以供读取或写入的表有影响[`锁定表...写入`](lock-tables.html)隐式（例如，通过触发器）或通过[`锁定表...阅读`](lock-tables.html).

-   全部`InnoDB`当事务提交或中止时，事务持有的锁被释放。因此，调用它没有多大意义[`锁定表`](lock-tables.html)在`InnoDB`中的表[`自动提交=1`](server-system-variables.html#sysvar_autocommit)模式，因为获得`InnoDB`表锁将立即释放。

-   您不能在事务中间锁定其他表，因为[`锁定表`](lock-tables.html)执行隐式[`犯罪`](commit.html)和[`解锁桌子`](lock-tables.html).
