行锁和表锁不是“选哪个”,而是由引擎+语句+索引自动决定的
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 隔离级别下触发了间隙锁。锁机制本身很稳,出问题的永远是人对它的假设。
