在 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在某些情况下可能无法精确判断事务需 要访问哪些行,从而倾向于使用表锁而不是行锁来确保数据的一致性。