mysql存储引擎如何实现行锁_mysql锁机制说明

来源:这里教程网 时间:2026-02-28 20:46:56 作者:

InnoDB 的行锁到底锁的是什么

InnoDB 实现行锁,**不是直接锁住某一行数据本身**,而是通过在索引记录上加锁来实现的。这意味着:如果没有合适的索引,

UPDATE
DELETE
语句可能退化为表锁(或间隙锁组合),而不是你期望的“只锁一行”。

常见错误现象:

SELECT ... FOR UPDATE
在无索引字段上执行后,其他事务更新同表任意行都被阻塞——这说明实际加了表级意向锁+大量记录锁,而非行锁。

主键或唯一索引等值查询(如
WHERE id = 100
)→ 加 **记录锁(Record Lock)**
普通索引等值查询 → 同样加记录锁,但会同时在主键上加锁(聚簇索引锁定) 范围查询(如
WHERE age > 25
)→ 加 **间隙锁(Gap Lock) + 记录锁**,防止幻读
无索引字段查询 → 优化器可能走全表扫描,对所有扫描到的记录加锁,效果接近表锁

为什么 SELECT FOR UPDATE 有时不生效

行锁只在事务中、且隔离级别 ≥

READ COMMITTED
时才起作用;但如果语句没走索引,或者被 MySQL 优化器判定为不可加锁(比如查询条件含函数、隐式类型转换),
SELECT ... FOR UPDATE
可能不加任何行级锁,仅加意向锁。

典型场景:执行

SELECT * FROM user WHERE name = '张三' FOR UPDATE
,但
name
字段没建索引 → InnoDB 无法精确定位记录,只能对聚集索引的每条记录尝试加锁,最终可能因锁冲突或死锁检测提前释放,看起来“没锁住”。

检查执行计划:
EXPLAIN
确认是否用到索引,
type
字段不能是
ALL
避免隐式转换:比如
WHERE mobile = 13800138000
(mobile 是 VARCHAR),会导致索引失效
注意隔离级别:
READ UNCOMMITTED
READ COMMITTED
下不使用间隙锁,但幻读风险上升

REPEATABLE READ 下的间隙锁与死锁风险

MySQL 默认隔离级别

REPEATABLE READ
会启用间隙锁(Gap Lock),它锁住的是索引记录之间的“空隙”,用于阻止其他事务插入新记录,从而解决幻读。但这也带来更高死锁概率。

例如两个事务并发执行:

INSERT INTO t VALUES (5)
SELECT * FROM t WHERE id BETWEEN 3 AND 7 FOR UPDATE
,后者会锁住 (3,7) 这个间隙,前者尝试插入 5 就会被阻塞;若另一个事务也类似操作,极易触发死锁。

关闭间隙锁?不行 ——
innodb_locks_unsafe_for_binlog
已废弃,且关闭会导致主从不一致
降低风险:尽量用等值查询代替范围;控制事务粒度,避免长事务持有间隙锁 监控死锁:
SHOW ENGINE INNODB STATUS
查看最近死锁详情,重点关注
TRANSACTION
LOCK WAIT
部分

MyISAM 完全不支持行锁,别误配

如果建表时没显式指定引擎,或配置中默认引擎是

MyISAM
,那所有 DML 操作都只有表锁。哪怕写
SELECT ... FOR UPDATE
,MySQL 也不会报错,但实际不会加任何行级锁,只会加表级读锁(
LOCK TABLES ... READ
)。

验证方式很简单:

SHOW CREATE TABLE tbl_name
ENGINE=InnoDB
是否存在;或者查
information_schema.TABLES
表的
ENGINE
列。

线上环境务必确认存储引擎:DDL 脚本里显式写
ENGINE=InnoDB
迁移老表时注意:
ALTER TABLE t ENGINE=InnoDB
会重建表,期间锁表,需评估影响
不要依赖客户端提示或文档描述,默认不是 InnoDB 就没有行锁能力

行锁的“行”字很误导人——它本质是索引项锁,不是数据行锁;真正难调的从来不是语法怎么写,而是索引设计是否匹配锁需求,以及事务边界是否清晰。一个没走索引的

FOR UPDATE
,和一条
SELECT 1
没区别。

相关推荐