MySQL 默认锁粒度取决于存储引擎:InnoDB 默认行锁但需命中索引,否则退化为全表索引项锁定;MyISAM 只支持读写互斥的表锁。

MySQL 默认用行锁还是表锁?
InnoDB 引擎默认使用行级锁(RECORD LOCK),但**是否真加行锁,取决于是否命中索引**。没走索引的 UPDATE 或 DELETE 会退化为表锁(更准确说是“锁全表所有索引项”,即间隙+记录的组合锁),实际效果接近表级阻塞。
MyISAM 只支持表锁,且是读写互斥的,高并发写场景下极易卡死。
- 查执行计划用
EXPLAIN确认是否走了索引; - 主键、唯一索引上的等值查询(
WHERE id = ?)基本能保证只锁单行; - 范围查询(
WHERE age > 25)会加间隙锁(GAP LOCK)或临键锁(NEXT-KEY LOCK),可能锁住不存在的“空档”,导致幻读或意外阻塞; -
SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE在可重复读(RR)隔离级别下也受索引影响,不走索引照样锁全表。
如何显式控制锁粒度:LOCK TABLES vs SELECT FOR UPDATE
LOCK TABLES 是粗暴的表级强制锁定,会阻塞其他线程对表的所有读写操作(包括 SELECT),仅适用于维护类场景(如备份前冻结表)。它和事务无关,不能被回滚,且会隐式提交当前事务。
SELECT ... FOR UPDATE 是行级锁的常用入口,但它的粒度完全由 WHERE 条件和索引决定——不是“想锁几行就锁几行”,而是“优化器决定锁哪些索引记录+间隙”。
- 避免在事务里用
LOCK TABLES做业务逻辑,它和 InnoDB 的 MVCC 设计冲突; - 若必须缩小锁范围,优先确保 WHERE 字段有高效索引,必要时加
FORCE INDEX; - 批量更新时,分页 + 主键范围比
LIMIT更可控(例如WHERE id BETWEEN ? AND ?); - 读多写少场景,考虑用
SELECT ... LOCK IN SHARE MODE配合应用层重试,比独占锁冲突概率低。
并发写冲突时,锁等待与死锁怎么排查?
锁等待超时默认是 50 秒(innodb_lock_wait_timeout),报错信息是:Lock wait timeout exceeded; try restarting transaction。这不是死锁,只是等太久了。
死锁则由 InnoDB 自动检测并回滚其中一个事务,报错:Deadlock found when trying to get lock; try restarting transaction。此时要看 SHOW ENGINE INNODB STATUS 输出里的 LATEST DETECTED DEADLOCK 段落。
- 死锁常见于多语句交叉更新(如事务 A 更新 user 表再更新 order 表,事务 B 反过来);
- 减少事务长度:把非关键操作(如日志记录、HTTP 调用)移出事务块;
- 固定 DML 顺序:所有业务模块按相同表顺序更新(如总是先 user 后 order);
- 监控
Innodb_row_lock_waits和Innodb_row_lock_time_avg状态变量,持续偏高说明锁竞争严重。
真正影响并发性能的,往往不是锁本身
很多“锁问题”其实是索引缺失、查询未走索引、事务过长或大字段拖慢刷脏页导致的。InnoDB 的行锁在理想条件下效率很高,但一旦出现锁升级(如锁升级为意向锁冲突)、缓冲池压力大(innodb_buffer_pool_wait_free 上升)、或大量 UNDO LOG 积压,表现就会像“锁很重”。
- 检查
information_schema.INNODB_TRX表,看长事务是否堆积; - 用
pt-deadlock-logger持续捕获死锁事件,别只靠偶发报错; - 大字段(
TEXT/BLOB)尽量拆到附表,避免锁住整行物理记录; - 高并发写入场景,
innodb_autoinc_lock_mode = 2(交错模式)比默认的 1 更适合,但需确保应用不依赖连续自增 ID。
锁粒度控制的本质,是让索引设计、SQL 写法和事务边界三者对齐。不理解执行路径就调参数,大概率越调越堵。










