MySQL的InnoDB引擎 在不同 SQL 语句中设置的锁

  • 执行锁定读操作(如 SELECT ... FOR UPDATE/SHARE)、UPDATE 或 DELETE 通常会对处理 SQL 语句时扫描到的每个索引记录设置记录锁,即使 WHERE 条件可能排除了某些行也一样。这些锁通常是"下一个键锁"(next-key locks),它们还会阻止在记录之前的“间隙”内进行插入。但可以显式禁用间隙锁定,从而不使用下一个键锁。事务隔离级别也会影响设置的锁类型。

  • 如果搜索中使用了二级索引,并且需要设置的索引记录锁是排他性的,InnoDB 还会检索相应的聚簇索引记录并对其设置锁。

  • 如果没有适合语句的索引,并且 MySQL 必须扫描整个表来处理语句,则整个表的每一行都将被锁定,从而阻塞其他用户对该表的所有插入操作。创建良好的索引非常重要,以便您的查询不扫描比必要更多的行。

  • 对于锁定读取、UPDATE 和 DELETE 语句,根据语句是否使用唯一索引和唯一搜索条件或范围类型搜索条件而采取不同的锁。

    • 对于有唯一搜索条件的唯一索引,只锁定找到的索引记录,不锁定其前面的间隙。

    • 对于其他搜索条件和非唯一索引,会锁定被扫描的索引范围,并使用间隙锁或下一个键锁阻止其他会话插入到范围覆盖的间隙中。

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

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

  • 当 UPDATE 修改聚簇索引记录时,在受影响的二级索引记录上也会隐式地设置锁。在插入新的二级索引记录之前,执行重复检查扫描时以及插入新的二级索引记录时,操作也会在受影响的二级索引记录上设置共享锁。

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

  • INSERT 在插入的行上设置排他锁。这个锁是索引记录锁,而不是下一个键锁(没有间隙锁),不会阻止其他会话在插入行之前的间隙中插入。

  • 在插入行之前,会设置一种称为插入意向间隙锁的间隙锁。这种锁表示插入的意图,多个事务在相同索引间隙中插入时无需等待彼此,只要它们不是在间隙中的相同位置插入。

  • 如果发生重复键错误,将在重复的索引记录上设置共享锁。如果尝试插入相同行的多个会话与已经拥有排他锁的另一个会话冲突,可能会导致死锁。

例如,假设有 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);

会话 3:

START TRANSACTION; INSERT INTO t1 VALUES(1);

会话 1:

ROLLBACK;

会话 1 的第一个操作获得了行的排他锁。会话 2 和 3 的操作都导致重复键错误,并且它们都请求行的共享锁。当会话 1 回滚时,它释放了对行的排他锁,同时授予了会话 2 和 3 的共享锁请求。此时,会话 2 和 3 发生死锁:由于彼此持有共享锁,两者均不能获取行的排他锁。

  • INSERT ... ON DUPLICATE KEY UPDATE 与普通的 INSERT 不同,当发生重复键错误时,将对待更新的行放置排他锁而不是共享锁。对于重复的主键值,会获得排他的索引记录锁;对于重复的唯一键值,会获得排他的下一个键锁。

  • REPLACE 在没有唯一键冲突时像 INSERT 一样执行。否则,将在要替换的行上放置一个排他的下一个键锁。

  • INSERT INTO T SELECT ... FROM S WHERE ... 在插入到 T 的每一行上设置排他索引记录锁(没有间隙锁)。如果事务隔离级别是 READ COMMITTED,InnoDB 会对 S 进行一致性读取(无锁)。否则,InnoDB 会在 S 的行上设置共享的下一个键锁。

  • CREATE TABLE ... SELECT ... 使用共享的下一个键锁或作为一致读取来执行 SELECT,就像 INSERT ... SELECT。

  • 当使用 SELECT 在 REPLACE INTO t SELECT ... FROM s WHERE ... 或 UPDATE t ... WHERE col IN (SELECT ... FROM s ...) 结构中时,InnoDB 会在表 s 的行上设置共享的下一个键锁。

  • InnoDB 在初始化之前指定的 AUTO_INCREMENT 列的索引末端设置排他锁。

具有 innodb_autoinc_lock_mode=0 时,InnoDB 使用特殊的 AUTO-INC 表锁模式,在访问自增计数器时获取锁并保持至当前 SQL 语句结束。其他客户端不能在持有 AUTO-INC 表锁时向表中插入数据。innodb_autoinc_lock_mode=2 不使用表级 AUTO-INC 锁。

  • 具有外键约束的表上的任何插入、更新或删除操作,如果需要检查该约束条件,则会在它查看的记录上设置共享级别的记录锁。即使约束失败,InnoDB 也会设置这些锁。

  • LOCK TABLES 设置表锁,这是在 MySQL 上层而不是 InnoDB 层完成的。如果 innodb_table_locks = 1(默认设置)且 autocommit = 0,则 InnoDB 会意识到表锁,并且 MySQL 上层也会知道行锁。

  • LOCK TABLES 如果 innodb_table_locks=1(默认值),会在每个表上获取两个锁。除了 MySQL 层的表锁外,它还会获取一个 InnoDB 表锁。要避免获取 InnoDB 表锁,可以设置 innodb_table_locks=0。如果没有获得 InnoDB 表锁,那么即使一些表记录正在被其他事务锁定,LOCK TABLES 也可以完成。

  • 在事务被提交或中止时,所有由事务持有的 InnoDB 锁都会被释放。因此,在 autocommit=1 模式下对 InnoDB 表使用 LOCK TABLES 没有太大意义,因为获得的 InnoDB 表锁将立即被释放。

  • 事务进行过程中不能锁定额外的表,

你可能感兴趣的:(mysql,mysql,sql,数据库)