mysql行锁仅在innodb引擎且where命中索引时生效;否则近似锁表。判断是否真锁1行需看explain:type为const/ref且key显示使用主键或唯一索引才算可靠。

MySQL 只在 InnoDB 引擎 + WHERE 条件命中索引时,才真正加行锁;其他情况基本等于“锁整张表”。
怎么判断你的 UPDATE 或 SELECT FOR UPDATE 真的只锁了 1 行?
别信语义,看执行计划。行锁不是“写了 WHERE 就有”,而是优化器决定走不走索引:
-
EXPLAIN SELECT * FROM users WHERE id = 1001 FOR UPDATE;→ 如果type是const或ref,且key显示用了主键或唯一索引,才算稳稳锁 1 行 -
EXPLAIN SELECT * FROM users WHERE name = 'Alice' FOR UPDATE;→ 若name没索引,type是ALL,那就不是“没加锁”,而是对**所有扫描到的记录都加 X 锁**,外加间隙锁,效果等同于锁表 - 隐式转换也致命:
UPDATE users SET status=1 WHERE phone = 13800138000;(phone是VARCHAR)→ 触发全表扫描加锁,不是 bug,是规则
为什么明明写了 WHERE 主键,还是被卡住?
因为行锁 ≠ 立刻释放。InnoDB 遵循两阶段锁协议:锁从需要时加上,直到事务 COMMIT 或 ROLLBACK 才释放。
- 事务里先调了外部 API 耗时 3 秒,再执行
SELECT * FROM order WHERE id = 123 FOR UPDATE;→ 这 3 秒内,锁已持有,别人全得等 -
SELECT ... FOR UPDATE后不做任何更新、直接COMMIT?锁照样等到提交那一刻才松手 - 查锁状态用:
SELECT * FROM performance_schema.data_locks;看LOCK_DATA和LOCK_MODE,别靠猜
SELECT 不加锁?那是默认 MVCC 快照读 —— 但一加 FOR UPDATE 就变天
普通 SELECT 在 RR/RC 下完全不加锁,靠 MVCC 返回快照;可一旦显式加锁提示,行为彻底改变:
-
SELECT * FROM stock WHERE sku_id = 'ABC123' FOR UPDATE;→ 加 X 锁,阻塞其他事务的FOR UPDATE和UPDATE -
SELECT * FROM stock WHERE price > 100 FOR UPDATE;→ 范围查询,在 RR 级别下自动升级为 Next-Key Lock(Record Lock + Gap Lock),连INSERT price = 150都可能被堵住 - 想禁用间隙锁?临时切隔离级别:
SET SESSION transaction_isolation = 'READ-COMMITTED';,但得接受幻读风险
最常被忽略的一点:没有主键或唯一索引的 InnoDB 表,InnoDB 会用隐藏的 row_id 构建聚簇索引,此时锁行为极难预测——看着只改 1 行,实际可能锁住一大片。上线前务必确认表有主键,且关键查询字段建了索引。










