Skip to content

在 MySQL 中,FOR UPDATE 通常是对行加锁,但也存在一些特殊情况会导致锁升级为表锁,以下是具体介绍:

行锁情况

  • 基于唯一索引查询 :当查询条件命中唯一索引,包括主键索引时,FOR UPDATE 会使用记录锁来锁定符合条件的行。例如,SELECT * FROM users WHERE id = 100 FOR UPDATE; 命令中,id 是主键,INNODB 会自动为主键加上聚簇索引,此时只会锁定 id 为 100 的那一行数据,其他事务可以对其他行进行正常的读写操作。
  • 基于普通索引查询且查询条件满足一定要求 :如果查询条件使用的不是唯一索引,但也能够通过普通索引准确定位到需要锁定的行,那么也会使用行锁。比如 SELECT * FROM users WHERE indexed_column = 'value' FOR UPDATE;,其中 indexed_column 是一个普通索引列,如果该查询条件能够通过普通索引快速找到对应的行,则会对这些行加行锁。

表锁情况

  • 无索引或索引失效 :当查询条件没有命中任何索引,或者索引失效时,MySQL 无法精确地定位到具体的行,此时就会使用表级锁。例如,在 users 表中,name 字段没有索引,执行 SELECT * FROM users WHERE name = 'John' FOR UPDATE; 命令时,MySQL 就会锁定整个表。
  • 范围查询且索引不连续 :在进行范围查询时,如果索引不连续,MySQL 可能会使用间隙锁和临键锁,这可能导致锁范围扩大,甚至出现锁表的情况。比如在一个包含 id(主键)字段的 orders 表中,执行 SELECT * FROM orders WHERE id BETWEEN 10 AND 20 FOR UPDATE; 命令时,如果 id 字段的索引在 10 到 20 之间存在不连续的情况,MySQL 会使用间隙锁和临键锁来锁定这个范围内的间隙和记录,防止其他事务在这个范围内插入新的数据,在极端情况下,可能会导致整个表被锁定。
  • innodb_locks_unsafe_for_bin_log 参数设置为 1 时 :如果该参数设置为 1,并且查询条件中的索引列涉及多列,此时行锁会升级为表锁。
  • MySQL 版本较旧时 :旧版本的 MySQL 在某些情况下可能无法精确判断事务需 要访问哪些行,从而倾向于使用表锁而不是行锁来确保数据的一致性。

Last updated: