行锁和表锁由存储引擎、SQL类型及索引命中情况自动决定:InnoDB默认行锁但无索引时退化为表锁,MyISAM全程表锁;可通过EXPLAIN、SHOW ENGINE INNODB STATUS等验证实际锁行为。

行锁和表锁不是“选哪个”,而是由引擎+语句+索引自动决定的
MySQL 本身不直接提供“手动切换行锁/表锁”的开关。锁类型由存储引擎、SQL 类型、是否命中索引共同触发:InnoDB 默认走行锁,但 UPDATE 或 DELETE 没走索引时会退化为表锁;MyISAM 则全程只用表锁,连 SELECT 都会自动加读锁。
- 查当前是否用了行锁?看执行计划:
EXPLAIN SELECT ... FOR UPDATE中key字段非NULL且rows较小,大概率是行锁;若type是ALL或key为NULL,极可能升级为表锁 - InnoDB 下,即使写了
WHERE id = ?,如果id列没建索引,照样全表扫描 → 表锁 - MyISAM 不支持事务,也不支持
SELECT ... FOR UPDATE,强行写会报错:ERROR 1036 (HY000): Table 'xxx' is read only
什么时候会意外拿到表锁?重点盯这三类操作
很多线上慢查询或阻塞,其实源于本想锁几行,结果锁了整张表——尤其在迁移 MyISAM 表到 InnoDB 后忽略索引设计时。
-
UPDATE/DELETE语句中WHERE条件未命中任何索引(包括隐式类型转换,如WHERE phone = 13800138000而phone是VARCHAR) - 对大字段(如
TEXT、BLOB)做ORDER BY或GROUP BY,触发 filesort + 临时表,InnoDB 可能放弃行锁优化 - 显式使用
LOCK TABLES t1 WRITE—— 这是强制表锁,哪怕你用的是 InnoDB,也会绕过 MVCC 直接上表级排他锁
如何验证当前锁行为?别只看文档,用真实命令看
光靠理论容易误判。InnoDB 提供了几个关键视图,比“应该加什么锁”更可信的是“实际加了什么锁”。
- 查当前锁等待:运行
SHOW ENGINE INNODB STATUS\G,重点关注TRANSACTIONS和LOCK WAIT部分,能看到谁在等哪一行、被谁堵住 - 查最近死锁日志:同样在
SHOW ENGINE INNODB STATUS\G输出末尾的LATEST DETECTED DEADLOCK区域,包含完整 SQL 和锁模式(X表示排他锁,S表示共享锁) - 监控表锁争用:执行
SHOW GLOBAL STATUS LIKE 'table_locks%';,若table_locks_waited持续增长,说明存在 MyISAM 表或 InnoDB 被迫升表锁的高频场景
行锁不是万能解药:它带来并发,也埋下死锁风险
行锁提升并发能力,但代价是锁管理更复杂。InnoDB 的行锁基于索引记录,而两个事务若以不同顺序访问相同几行,就极易触发死锁。
- 典型死锁链:
T1先更新id=1再更新id=2;T2反过来先更新id=2再更新id=1→ 双方互相等待 - 避免方式:所有业务逻辑中,对多行更新务必按**主键/索引顺序**统一访问(比如总是
ORDER BY id ASC),让加锁顺序一致 - 别依赖
SELECT ... FOR UPDATE做“乐观重试”:它本质是悲观锁,一旦加锁失败(超时或死锁回滚),必须重试整个事务,而不是仅重试某条语句
UPDATE、那个忘记加 COMMIT 的事务、或是开发时以为“只是读一下”的 SELECT 却在 RR 隔离级别下触发了间隙锁。锁机制本身很稳,出问题的永远是人对它的假设。










