MySQL有哪些锁?InnoDB锁体系完整梳理博客

前言

MySQL锁是并发控制的核心,很多死锁、超时、更新阻塞、幻读问题根源都来自锁。很多开发者分不清:表锁、行锁、意向锁、MDL锁、Gap锁、临键锁这些概念。

注意:MyISAM只支持表级锁;现在业务几乎全部使用InnoDB引擎,本文主要围绕 InnoDB 讲解,把所有锁分类、作用、场景、踩坑一次性讲清楚。

一、锁的大分类维度

可以从4个角度划分锁:

  1. 粒度维度:全局锁、表级锁、行级锁(记录锁)
  2. 兼容性维度:共享锁(S)、排他锁(X)
  3. 意向锁:意向共享IS、意向排他IX,表级别辅助锁
  4. InnoDB特殊锁:MDL元数据锁、Gap间隙锁、Next‑Key临键锁、插入意向锁

二、全局锁(Global Read Lock)

作用范围:整个MySQL实例所有库所有表

语法:

sql 复制代码
FLUSH TABLES WITH READ LOCK;
  • 加锁之后,所有表只能读,DML、DDL全部阻塞;
  • 典型场景:全库逻辑备份(mysqldump --single‑transaction 可以避开全局锁)
  • 风险:业务库不要随便执行,一旦加锁,所有写入全部卡住,线上灾难。
  • 释放:UNLOCK TABLES; 或者会话断开自动释放。

生产建议:备份优先使用 --single‑transaction 快照读,避免FTWRL全局锁。

三、表级锁(Table‑level lock)

作用范围:整张数据表,分为两种:表读锁、表写锁

1)表读锁(共享锁)

sql 复制代码
LOCK TABLES t1 READ;
  • 当前会话:可读,不可写;
  • 其他会话:可读,写操作阻塞。

2)表写锁(排他锁)

sql 复制代码
LOCK TABLES t1 WRITE;
  • 当前会话:可读可写;
  • 其他会话:读、写全部阻塞。

MyISAM引擎默认使用表锁;InnoDB尽量不要手动使用LOCK TABLES,InnoDB有行锁,手动表锁会破坏InnoDB事务特性。

3)MDL元数据锁(Metadata Lock,元数据锁,非常容易踩坑)

MDL锁是InnoDB 5.6之后,访问表的时候自动获取,不需要手动加锁,DDL操作会拿MDL排他锁。

  • MDL共享锁:执行select / insert / update / delete DML自动拿MDL‑S;多个会话可以同时持有。
  • MDL排他锁:alter table、drop table、rename等DDL拿MDL‑X;排他锁和所有MDL‑S互斥。
经典线上故障场景
  1. 会话A执行一条慢查询SELECT,持有MDL‑S,SQL执行很慢没有结束;
  2. DBA执行alter table修改表结构,申请MDL‑X,被阻塞;
  3. 后续所有新的DML请求,都需要申请MDL‑S,会被等待中的MDL‑X堵住;
  4. 整个表全部卡死,大量连接堆积,数据库雪崩。

MDL锁在事务提交之后才释放,不是SQL执行完就释放

坑:长事务持有MDL‑S,会直接阻塞DDL,进而堵死整个表业务。

四、意向锁 Intention Lock(IS / IX)

意向锁是InnoDB表级锁,不会阻塞普通DML,是用来协调表锁和行锁之间冲突检测,用户不能手动操作,InnoDB内部自动维护。

  • IS 意向共享锁:事务打算给表中部分行加共享S行锁,先给表加上IS意向锁。
  • IX 意向排他锁:事务打算给表中部分行加排他X行锁,先给表加上IX意向锁。

意向锁兼容性矩阵:

X表锁 S表锁 IX意向锁 IS意向锁
X表锁 冲突 冲突 冲突 冲突
S表锁 冲突 兼容 冲突 兼容
IX意向锁 冲突 冲突 兼容 兼容
IS意向锁 冲突 兼容 兼容 兼容

通俗理解:意向锁作用:当有人想加整张表的表锁的时候,通过意向锁快速判断表里有没有行锁存在。

五、行级锁 Record Lock(记录锁,只锁行)

InnoDB行锁是索引实现!!如果更新条件不走索引,行锁会升级为表锁,重大坑点。

两种基础行锁:

  1. S 共享锁(读锁)
    select ... lock in share mode;
  • 自己可以读;其他事务也可以加S锁读;但是不能加X锁修改该行。
  1. X 排他锁(写锁)
    select ... for update;
  • 获取行排他锁;其他事务不能读也不能写,等待锁释放。

兼容性:

S行锁 X行锁
S行锁 兼容 互斥
X行锁 互斥 互斥

重要提醒:

update / delete 会自动给匹配行加上X排他行锁,不需要手动for update;

where条件没有索引,InnoDB无法定位行,会锁住全表,等价表锁效果。

六、InnoDB RR隔离级别下的三种特殊锁(解决幻读核心)

InnoDB 在 REPEATABLE‑READ(RR)可重复读隔离级别,为了解决幻读,引入 Gap锁、临键锁、插入意向锁。

1. Gap Lock 间隙锁

锁的是索引记录之间的间隙,不是锁行,防止其他事务在间隙插入新数据,以此解决幻读。

只在RR隔离级别生效;RC隔离级别没有间隙锁。

举例:索引值 10,20,30

更新 where id=20;间隙会锁住 (10,20)、(20,30) 这个区间,别的事务不能往这个间隙insert。

间隙锁之间互相兼容:多个事务可以持有同一个间隙锁;间隙锁只阻塞插入,不阻塞修改已有记录。

2. Next‑Key Lock 临键锁(行锁+间隙锁组合)

InnoDB RR下默认行锁算法 = 临键锁

临键锁 = Record记录锁 + Gap间隙锁,左开右闭区间。

  • 命中确切索引记录:临键锁退化成单纯记录锁(只锁那一行)
  • 没有命中任何记录:保留间隙锁,阻止插入,避免幻读。

幻读:同一个事务内,同样select,第二次查询多出了别人新插入的数据。RR靠临键锁解决幻读。

3. Insert Intention Lock 插入意向锁

属于Gap间隙锁的一种,Insert语句插入数据的时候获取。

多个事务向同一个间隙插入不同记录,互相不会阻塞,提升并发插入性能。

七、快照读 & 当前读(锁和MVCC关系)

  • 快照读(普通select):不加锁 ,走MVCC多版本,读取历史快照,不会产生锁冲突。

    SELECT * FROM t where id=1;

  • 当前读:加锁读取,读取最新数据

    select ... for update / select ... lock in share mode / update / delete / insert

    执行当前读,才会触发行锁、临键锁、间隙锁。

很多同学疑惑:为什么普通select不会阻塞?普通select是快照读,不上锁。

八、所有锁汇总一览表

锁名称 锁粒度 手动控制 主要作用
全局锁 整个实例 FLUSH TABLES WITH READ LOCK 全库只读,备份场景
表读锁S 整张表 LOCK TABLES READ 整张表只读
表写锁X 整张表 LOCK TABLES WRITE 整张表独占读写
MDL元数据锁 自动获取 保护表元数据,DDL/DML互斥
IS意向共享锁 自动 辅助检测表锁与行锁冲突
IX意向排他锁 自动 辅助检测表锁与行锁冲突
Record S共享行锁 索引行 LOCK IN SHARE MODE 行共享读锁
Record X排他行锁 索引行 FOR UPDATE、DML 行写锁,修改行
Gap间隙锁 索引间隙 自动(RR) 阻止间隙插入,防幻读
Next‑Key临键锁 行+间隙 自动(RR) RR默认行锁算法,解决幻读
插入意向锁 索引间隙 自动(insert) 优化并发插入

九、线上高频锁踩坑总结

坑1:where条件无索引,行锁退化为全表锁

sql 复制代码
update user set name='xxx' where phone='138xxxx';

phone字段没有索引,InnoDB无法定位行,扫描全表给每一行上X锁,整个表更新全部阻塞。

行锁是索引实现,没有索引就没有行锁。

坑2:长事务导致锁不释放

事务执行完不commit,行锁、MDL锁一直占有,其他会话阻塞,产生大量锁等待、死锁。

规范:业务事务尽量小,不要在事务里面做网络IO、外部http调用。

坑3:RR隔离级别间隙锁带来意外阻塞

RR级别下更新一个不存在的数据,会加Gap间隙锁,导致别的会话insert被卡住。

如果业务可以接受幻读,可考虑RC隔离级别,RC没有间隙锁,并发更高。

坑4:MDL锁雪崩

慢SQL持有MDL‑S,接着执行alter table DDL,会堵死整张表所有DML。

上线DDL避开业务高峰,先确认没有长事务慢查询。

坑5:分不清快照读与当前读

普通select不上锁;for update、update、delete属于当前读,会上行锁。

坑6:意向锁手动无法操作,不要去纠结怎么加意向锁

意向锁是InnoDB内部机制,开发者不需要写SQL操作。

十、如何排查锁问题

  1. show engine innodb status; 查看最近死锁日志
  2. information_schema.innodb_trx 当前运行事务
  3. information_schema.innodb_locks 当前锁信息
  4. performance_schema 监控MDL锁等待
  5. 慢查询日志,找出长事务、慢DML

十一、锁相关开发规范

  1. InnoDB尽量使用行锁,优先保证update/delete where条件命中索引;
  2. 事务尽可能短小,避免大事务、长事务;
  3. DDL变更避开业务高峰,排查是否存在运行中的长事务;
  4. 理解RC和RR锁行为差异:RC没有间隙锁,不会防幻读;RR带间隙锁解决幻读,但会带来额外插入阻塞;
  5. 普通查询优先快照读,不要随便滥用 select ... for update
  6. 尽量少手动使用 LOCK TABLES 表锁,会破坏InnoDB事务特性。

总结

  1. MySQL锁粒度从大到小:全局锁 > 表锁 > 行锁;InnoDB优先使用行锁,行锁依赖索引。
  2. MDL元数据锁,DDL阻塞事故头号元凶,事务结束才释放。
  3. 意向锁IS/IX只是表级别辅助检测,用户不能手动控制。
  4. RR隔离级别才有Gap间隙锁、Next‑Key临键锁,目的解决幻读;RC没有间隙锁,并发更高。
  5. 普通select快照读不加锁;for update、update、delete属于当前读会上锁。
  6. 行锁失效变表锁最常见原因:更新条件不走索引。
  7. 锁不释放大多来自长事务,写业务一定要控制事务大小。

记住:InnoDB行锁锁住的不是物理记录,锁住的是索引。没有索引,行锁不复存在。

相关推荐
wear工程师2 小时前
MySQL 索引条件下推是什么?Using index condition 并不是覆盖索引
sql·mysql
Wang's Blog2 小时前
PostgreSQL笔记38:慢查询定位与优化的系统方法论
数据库·笔记·postgresql
幻风_huanfeng3 小时前
软考:高级软件架构师学习笔记----数据库
数据库·软考·系统架构师
衿心.3 小时前
Django TemplateDoesNotExist
数据库·django·sqlite
方便面不加香菜3 小时前
MySQL 复合查询
数据库·mysql
花花鱼3 小时前
MySQL Illegal mix of collations 全解|字符集与排序规则冲突原理、分层排错、通用解决方案(适配5.7/8.0迁移)
数据库·mysql
Wang's Blog3 小时前
PostgreSQL笔记37:数据库性能瓶颈排查方法论与工具链
数据库·笔记·postgresql
Lyyaoo.12 小时前
Spring Boot Validation 声明式参数校验
spring boot·后端·mysql
oradh13 小时前
Oracle闪回技术操作总结
数据库·oracle·oracle闪回技术操作总结·oracle闪回·flashback技术