MySQL 各种锁机制详解

MySQL 各种锁机制详解

重点放在 InnoDB,引擎不同锁语义也不同。

目标:弄清楚"有哪些锁、锁什么粒度、什么时候会被加上"。


一、为什么需要锁?

数据库是"多人同时操作同一份数据"的系统:

  • 多个事务并发更新同一行;
  • 一个事务在扫一段范围时,另一个插入新数据;
  • 结构变更(DDL)和读写(DML)并发执行;

如果不加锁:

  • 数据会被"乱改";
  • 读到一半被改、更新丢失、约束被破坏。

所以需要各种锁:

  • 控制并发写
  • 配合 MVCC 控制并发读写
  • 控制 元数据变更(DDL)

MySQL 的锁大致可以从几类角度划分:

  1. 对象粒度:全局锁、库锁、表锁、行锁;
  2. 功能/用途:读锁(S)、写锁(X)、意向锁、元数据锁、间隙锁;
  3. 引擎:Server 层锁、InnoDB 锁、MyISAM 锁等。

下面重点围绕 InnoDB 来讲。


二、从大到小看:锁的粒度分类

2.1 全局锁(Global Lock)

  • 作用范围:整个实例(所有数据库、所有表)。
  • 常见命令:
sql 复制代码
FLUSH TABLES WITH READ LOCK;

作用:

  • 把整个实例"锁成只读",常用于全库逻辑备份
  • 期间所有 DML、DDL 都会被阻塞(写不了)。

不常用,慎用,线上大量业务时基本不会直接这么干。


2.2 表级锁(Table Lock)

2.2.1 MySQL Server 层的显式表锁

语法:

sql 复制代码
LOCK TABLES t1 READ, t2 WRITE;
UNLOCK TABLES;

特点:

  • 不区分引擎,属于 Server 层机制;
  • READ:其他会话可读不能写;
  • WRITE:其他会话不能读也不能写;
  • 粒度粗,并发度差,实际业务很少手工用。
2.2.2 MyISAM 的表锁

MyISAM 没有行锁,只有表锁:

  • 读锁(READ LOCK):多读互不阻塞;
  • 写锁(WRITE LOCK):写独占,阻塞其它读写。

这也是 MyISAM 不适合高并发写场景的重要原因。


2.3 元数据锁(Metadata Lock,MDL)

  • 作用对象:表结构(元数据)层面;
  • 自动加,不能手动控制。

场景:

  • 对一张表执行 SELECT/INSERT/UPDATE/DELETE 时:
    • 获得一个 MDL 读锁
    • 阻止别人对该表执行结构变更(DDL);
  • 对表执行 ALTER TABLE / DROP TABLE 等 DDL 时:
    • 需要获取 MDL 写锁
    • 会等待所有 MDL 读锁释放。

作用:

  • 保证 DDL 和 DML 不会"打架";
  • 比如:你在跑查询时,不会有人把表给删了。

注意:

  • 长事务会长期持有 MDL 读锁;
  • 这会导致后续的 DDL 一直阻塞在"Waiting for table metadata lock";
  • 线上经常见到这种场景。

2.4 行级锁(Row Lock)------InnoDB 主角

InnoDB 提供的行级锁包括:

  1. 记录锁(Record Lock)
  2. 间隙锁(Gap Lock)
  3. Next-Key Lock(记录锁 + 间隙锁)
  4. 插入意向锁(Insert Intention Lock)

以及上层逻辑上的:

  • 共享锁(S 锁,读锁)
  • 排他锁(X 锁,写锁)
  • 意向锁(意向共享 / 意向排他)

行级锁的特点:

  • 粒度细,并发度高;
  • 成本高于表锁;
  • 默认是行锁 + MVCC 组合模式。

下面逐个拆。


三、InnoDB 行锁的类型

3.1 共享锁(S)和排他锁(X)

逻辑层面最常见的两种:

  • 共享锁(Shared Lock, S 锁)
    • 多个事务可以同时持有 S 锁;
    • 通常用于读,互相不冲突;
  • 排他锁(Exclusive Lock, X 锁)
    • 只允许一个事务持有;
    • 通常用于写,和其它 S/X 锁都不兼容(除自己)。

兼容矩阵(简化):

当前持有 \ 请求 S 请求 X 请求
S 兼容 冲突
X 冲突 冲突

常见 SQL:

sql 复制代码
-- 共享锁
SELECT * FROM t WHERE id = 1 LOCK IN SHARE MODE;  -- 8.0 之后不推荐,用 FOR SHARE

SELECT * FROM t WHERE id = 1 FOR SHARE;

-- 排他锁
SELECT * FROM t WHERE id = 1 FOR UPDATE;

3.2 意向锁(Intention Lock)

作用对象:表级别,但用来配合行锁使用。

  • 意向共享锁(IS):事务打算在某些行上加共享锁;
  • 意向排他锁(IX):事务打算在某些行上加排他锁。

为什么需要意向锁?

  • 主要是为了配合表锁:
    • 如果要给整张表加表级 S/X 锁,需要知道表中是否存在行锁冲突;
    • 不可能一行行扫描,所以设计了"意向锁"。

行为:

  • 当事务要在某一行上加 X 锁时,会先在表上加 IX 锁;
  • 当事务要在某一行上加 S 锁时,会先在表上加 IS 锁。

表级锁与意向锁的兼容大致如下(记个直观印象即可):

IS IX S 表锁 X 表锁
IS ×
IX × ×
S 表锁 × ×
X 表锁 × × × ×

总结:

  • 意向锁本身不会阻塞普通行锁;
  • 它的主要任务是:加速"表锁与行锁之间的冲突判断"

3.3 记录锁(Record Lock)

  • 锁定"索引上的某一条记录";
  • 本质是:锁定某个索引键值。

例子:

sql 复制代码
SELECT * FROM user WHERE id = 10 FOR UPDATE;

假设 id 有索引:

  • 就会对 id = 10 这一条索引记录加记录锁(X 锁)
  • 其他事务不能修改或删除这条记录。

注意:

InnoDB 的行锁是基于 索引 实现的,没有索引就可能退化成表锁或锁一大片。


3.4 间隙锁(Gap Lock)

  • 锁定"索引之间的间隙",不包括记录本身。

比如:索引里现有值:10, 20, 30, 40

则间隙为:(-∞,10)、(10,20)、(20,30)、(30,40)、(40,+∞)

间隙锁用来:

  • 阻止其他事务在某些范围"插入新记录";
  • 本质目的是防止"幻读(Phantom Read)"

例子:

sql 复制代码
SELECT * FROM t WHERE age BETWEEN 20 AND 30 FOR UPDATE;

InnoDB(在某些隔离级别)可能会:

  • 不仅锁定现有的满足条件的记录;
  • 还会锁定 20~30 之间的"间隙",阻止插入新的 age 在这个范围内的数据;
  • 从而在后续同一事务再次查询时,不会出现"多出一条新的行"的幻读。

3.5 Next-Key Lock(记录锁 + 间隙锁)

  • 是"记录锁 + 间隙锁"的组合;
  • 锁定一个"左开右闭"的区间:(前一个索引值, 当前索引值]

在 InnoDB 默认的 REPEATABLE READ 下:

  • 对索引范围查询(带 for update / for share)会采用 Next-Key Lock;
  • 它既锁定记录本身,也锁定附近的间隙;
  • 减少幻读发生的可能。

简单理解:

Next-Key Lock = 将记录锁扩展到一个范围,以防止新记录插进来造成幻读。

3.6 插入意向锁(Insert Intention Lock)

一种特殊的间隙锁,用于插入时:

  • 当事务要在某个间隙插入记录时,会先声明一个"插入意向锁";
  • 多个事务在不同位置插入,彼此不会互相阻塞;
  • 只有插入位置冲突时,才会等待。

四、InnoDB 锁是如何被加上的?

4.1 普通 SELECT(不加锁)

sql 复制代码
SELECT * FROM t WHERE id = 10;
  • 默认是一致性读(Consistent Read)
  • 利用 MVCC 读取数据版本;
  • 一般不加行锁(除非特殊情况如手工 hint 或特定隔离级别)。

4.2 锁定读:SELECT ... FOR UPDATE / FOR SHARE

sql 复制代码
-- 排他锁
SELECT * FROM t WHERE id = 10 FOR UPDATE;

-- 共享锁
SELECT * FROM t WHERE id = 10 FOR SHARE;

特点:

  • 在可重复读 / 读已提交下,都会对符合条件的记录加行锁(记录锁/Next-Key Lock);
  • 会参与锁冲突判定;
  • 用于做"先查后改"的场景防止并发脏写。

4.3 UPDATE / DELETE 自动加锁

sql 复制代码
UPDATE t SET balance = balance - 100 WHERE id = 1;
DELETE FROM t WHERE id = 2;
  • InnoDB 自动对匹配的记录加排他锁(行锁)
  • 其他事务不能修改这些行,直到事务提交/回滚。

五、锁与索引的关系

一个非常重要的点:行锁是基于索引的。

  • 如果 WHERE 条件能用到索引:只锁命中的那几行;
  • 如果没用上索引:可能退化为锁住更多记录甚至全表

例子:

sql 复制代码
-- id 上有索引
SELECT * FROM user WHERE id = 10 FOR UPDATE;
-- ✅ 只锁 id = 10 对应的那条索引记录

-- name 上无索引
SELECT * FROM user WHERE name = 'Tom' FOR UPDATE;
-- ❌ 可能会锁更大范围(扫描整个表,行锁挨个加,甚至接近表锁效果)

优化建议:

  • 对经常锁定某个字段的场景,要确保该字段有索引;
  • 避免在没有合适索引的条件上使用 FOR UPDATE / FOR SHARE。

六、MySQL 锁相关的典型问题

6.1 死锁(Deadlock)

典型死锁场景:

text 复制代码
事务 A:锁记录 1 -> 再锁记录 2
事务 B:锁记录 2 -> 再锁记录 1

两边互相等对方释放锁,形成死锁。

InnoDB 会:

  • 自动检测死锁;
  • 回滚其中一个事务,报错:Deadlock found when trying to get lock

避免方案:

  • 访问多行时,尽量按照固定顺序加锁;
  • 控制事务粒度和时间,避免大事务;
  • 理解你的索引和锁范围。

6.2 锁等待

  • 如果锁冲突但不是死锁,就会出现等待;
  • 超过 innodb_lock_wait_timeout(默认 50s)后报错。

排查手段:

sql 复制代码
SHOW ENGINE INNODB STATUS \G;

或在新版本里用 performance_schema 里的锁视图。


七、锁与隔离级别的关系(简要)

在不同隔离级别下:

  • 读未提交:大量读直接读最新版本,几乎不加锁,但允许脏读;
  • 读已提交:读时只看到已提交事务的最新版本,通常不加间隙锁;
  • 可重复读(默认)
    • 使用 MVCC 保证同一事务内多次读取一致;
    • 加上 Next-Key Lock / 间隙锁 避免/减少幻读;
  • 串行化:很多读都会退化为锁定读,强制串行执行,锁竞争最激烈。

简化理解:

隔离级别越高,加的锁越多或锁得越久,并发性能越差,但数据越"安全"。


八、常见锁类型速查表

锁类型 粒度 作用对象 主要用途
全局锁 实例 所有库表 全库备份,阻止写入
表锁 整张表 简单并发控制(MyISAM、多数不用)
元数据锁 MDL 表结构 / 元数据 DDL 与 DML 并发安全
行锁(记录锁) 单条记录(索引项) 控制并发更新单行
间隙锁 索引间隙 防止插入,避免幻读
Next-Key Lock 记录 + 间隙 InnoDB 默认可重复读的主要锁
插入意向锁 插入位置 多事务在不同位置插入时协调
S 锁 行/表 共享读 允许多读,不允许写
X 锁 行/表 排他写 写时独占,阻塞其它读写
意向锁 IS/IX 声明将要在行上加 S/X 锁 加速表锁与行锁冲突检测

九、小结

  1. MySQL 锁分层很多:全局锁、表锁、MDL、行锁等等。
  2. InnoDB 的并发控制核心是:行锁 + 间隙锁 + Next-Key Lock + MVCC
  3. 行锁基于索引实现,没索引容易锁更多数据甚至锁全表。
  4. 锁的细粒度在带来高并发能力的同时,也带来了死锁、锁等待 等问题,需要通过:
    • 合理设计索引;
    • 控制事务范围和顺序;
    • 使用 EXPLAINSHOW ENGINE INNODB STATUS 等工具来排查。

真正摸清楚锁的行为,一般要结合:隔离级别 + 索引结构 + 实际 SQL 反复实验和看执行计划。

这篇可以当成"概念地图",后面你如果有具体 SQL,我可以帮你一起分析"加了哪些锁、锁到了哪一段"。

相关推荐
Swift社区3 小时前
数据库连接池监控最佳实践:用 Prometheus + Grafana 打造可视化监控体系
数据库·grafana·prometheus
牛奶咖啡133 小时前
达梦数据库在国产系统的生产环境下安装部署实践教程(下)
数据库·达梦数据库·国产达梦数据库的安装部署·达梦数据库的目录结构介绍·使用命令行登录达梦数据库·达梦数据库的常用查询命令·使用图形化工具管理达梦数据库
小满、3 小时前
Redis:高级数据结构与进阶特性(Bitmaps、HyperLogLog、GEO、Pub/Sub、Stream、Lua、Module)
java·数据结构·数据库·redis·redis 高级特性
xiangzhihong83 小时前
Windows环境下安装使用Redis
数据库·windows·redis
islandzzzz3 小时前
从0开始的SQL表DDL学习(基础语法结构、索引/约束关键字)
数据库·sql·学习
qq_381454993 小时前
数据脱敏全流程解析
java·网络·数据库
qq_348231854 小时前
MySQL 与 PostgreSQL对比
数据库·mysql·postgresql
梁bk4 小时前
苍穹外卖项目总结(一)[MyBatis-Plus,文件上传,Redis]
数据库·redis·mybatis
CNRio4 小时前
Redis:内存中的数据引擎,架构解析与设计指南
数据库·redis·架构