行锁由 InnoDB 实现而非 MySQL 服务层,依赖索引加锁;若无索引或引擎非 InnoDB,则退化为表锁;间隙锁与临键锁用于防止幻读。

行锁不是 MySQL 服务器层实现的,而是 InnoDB 自己干的
MySQL 服务层本身不提供行级锁能力,LOCK IN SHARE MODE 或 FOR UPDATE 这类语句之所以能加行锁,完全依赖 InnoDB 存储引擎在底层对索引项加锁。MyISAM 就做不到——它只支持表锁,哪怕你只改一行,整张表都得停写。
这意味着:如果你把表的存储引擎换成 MyISAM,或者建表时没指定 ENGINE=InnoDB,那所谓“行锁”就根本不存在,所有 SELECT ... FOR UPDATE 都会退化成表锁(甚至可能报错或静默失效)。
- 检查当前表引擎:
SHOW CREATE TABLE table_name,确认有ENGINE=InnoDB - 建表时不显式声明引擎,MySQL 8.0+ 默认是 InnoDB,但低版本或配置修改后可能不是,不能赌默认
-
ALTER TABLE table_name ENGINE=InnoDB可在线转换,但大表会锁表较久,别在高峰期操作
行锁生效的前提:必须走索引,否则锁住整张表
InnoDB 的行锁本质是“锁索引”,不是锁数据行。如果 SQL 查询条件无法命中任何索引(比如 WHERE username = 'xxx' 但 username 没建索引),InnoDB 就不得不扫描全表来找记录——而为了保证并发安全,它会直接对聚簇索引(主键)的所有叶子节点加锁,效果等同于表锁。
常见踩坑场景:
- 用
OR连接多个字段,导致索引失效 → 全表扫描 → 表锁 - 对字段做函数操作:
WHERE DATE(create_time) = '2026-01-28'→ 索引失效 - 隐式类型转换:
WHERE user_id = '123'(user_id 是 INT)→ 索引可能失效 - 没主键、没索引的表,InnoDB 会用隐藏的 6 字节 row_id 做聚簇索引,但依然要求查询条件能定位到该 row_id,否则还是扫全表
为什么需要间隙锁(Gap Lock)和临键锁(Next-Key Lock)?
单纯 Record Lock(只锁某条记录)解决不了“幻读”。比如事务 A 执行 SELECT * FROM orders WHERE status = 'pending' FOR UPDATE,只锁住当前存在的 pending 订单;但如果事务 B 此时插入一条新 pending 订单,A 再查就会多出一行——这就是幻读。
InnoDB 默认在可重复读(REPEATABLE READ)隔离级别下,用 Next-Key Lock(Record Lock + Gap Lock)来封住“值区间”,防止新记录插入。例如锁定 id > 10 AND id 的间隙,别人就不能插 <code>id=15 的行。
- 间隙锁只在
REPEATABLE READ下启用,在READ COMMITTED下会被禁用(但代价是可能读到幻读) - 唯一索引的等值查询(如
WHERE id = 100)只会加 Record Lock,不加 Gap Lock - 非唯一索引的范围查询(如
WHERE name LIKE 'a%')会加 Next-Key Lock,影响极大,务必压测
意向锁(Intention Lock)是隐形但关键的协调者
当你要给某行加 X 锁,InnoDB 会先在表级别加一个 IX(意向排他锁);加 S 锁则加 IS。这不是为了拦住其他事务读写行,而是告诉“想对整张表加表锁的人”:别急,这表里已经有行被锁了。
没有意向锁,每次加表锁前就得遍历所有行锁,性能崩盘。它不阻塞普通 DML,但能避免冲突:
-
LOCK TABLES t WRITE会被正在执行UPDATE t SET x=1 WHERE id=5的事务阻塞,因为 IX 和表 X 锁不兼容 - 两个事务可以同时持有同一张表的
IS和IX,互不干扰 - 你永远不需要手动加意向锁——它是 InnoDB 自动管理的,但理解它能帮你读懂
SHOW ENGINE INNODB STATUS里的死锁日志
真正让 InnoDB 行锁“可用”的,从来不是语法有多漂亮,而是索引设计是否精准、隔离级别是否匹配业务语义、事务是否足够短。一旦出现锁等待超时或死锁,第一反应不该是调大 innodb_lock_wait_timeout,而是立刻看执行计划、查锁类型、确认有没有全表扫描或间隙锁误伤。










