首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >MySQL锁机制超详细入门指南(表锁、行锁、间隙锁、元数据锁,含实操案例)

MySQL锁机制超详细入门指南(表锁、行锁、间隙锁、元数据锁,含实操案例)

作者头像
俊才
发布2026-06-02 13:33:21
发布2026-06-02 13:33:21
1270
举报
文章被收录于专栏:数据库干货铺数据库干货铺

开发同学在工作中经常遇到这些问题:数据库明明没报错,为什么接口突然卡顿?为什么简单的更新语句一直未运行完毕?为什么数据会莫名其妙多出或丢失?

这些问题的核心根源,90%都和MySQL锁机制相关。锁是MySQL实现事务隔离、保证数据安全、处理并发操作的核心机制。

本文就一起探讨一下表锁、行锁、间隙锁、元数据锁(MDL)用途及避坑点等(本文基于RR隔离级别进行举例)。

一、锁的核心分类与基础规则

MySQL锁按照锁定粒度(范围大小),从大到小分为三类,再加上特殊的元数据锁,构成全部核心锁体系:

  • 表锁:锁住整张表,粒度最大,并发最差,开销最小
  • 行锁:锁住表中某一行数据,粒度最小,并发最好,开销最大
  • 间隙锁:锁住数据之间的“空白区间”,专门解决幻读问题
  • 元数据锁(MDL):锁住表结构,防止读写和改表结构冲突

同时所有锁都遵循两个基础规则,新手一定要牢记:

  • 读锁共享:多个事务可以同时读数据,互不阻塞
  • 写锁排他:只要有一个事务在写数据,其他所有事务的读写都会被阻塞

补充核心知识点:行锁、间隙锁是InnoDB引擎专属,MyISAM引擎只有表锁,这也是InnoDB能支持事务、并发更强的核心原因。

二、表锁:最简单、最粗暴的全表锁定

1. 通俗概念

表锁就是一旦加锁,锁住整张数据表。不管你只改一行数据,还是查一行数据,整张表都会被锁定,其他事务必须排队等待。

2. 核心特点

  • 粒度最大:锁定整张表
  • 开销小:无需遍历索引,加锁解锁速度快
  • 并发极差:同一时间只能一个事务写数据,读可以共享
  • MyISAM默认使用表锁,InnoDB一般不用,仅特殊场景触发

3. 表锁的两种类型

表锁分为读锁(共享锁)和写锁(排他锁):

  • 表读锁:所有人都能读,所有人都不能写
  • 表写锁:只有加锁的事务能读写,其他人全部阻塞

4. 案例演示

我们新建一张测试表,用于所有案例演示:

代码语言:javascript
复制
-- 创建测试表
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20),
    age INT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO users(name,age) VALUES ('张三',18),('李四',20),('王五',22);

案例1:手动加表读锁

代码语言:javascript
复制
-- 事务1:给users表加读锁
LOCK TABLES users READ;

执行后:本事务和其他事务都可以正常查询数据,但无法修改、插入、删除数据。

代码语言:javascript
复制
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> LOCK TABLES users READ;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from users;
+----+--------+------+
| id | name   | age  |
+----+--------+------+
|  1 | 张三   |   18 |
|  2 | 李四   |   20 |
|  3 | 王五   |   22 |
+----+--------+------+
3 rows in set (0.00 sec)

mysql> update users  set age=19 where id =1;
ERROR 1099 (HY000): Table 'users' was locked with a READ lock and can't be updated

本事务:

其他事务:

案例2:手动加表写锁

加表级写锁前,所有的其他事务必须结束(提交或回滚),也不能有加读锁的操作,否则加不少表级写锁。

代码语言:javascript
复制
 -- 解锁命令
 UNLOCK TABLES;
-- 事务1:给users表加写锁
LOCK TABLES users WRITE;

执行后:只有事务1能读写表数据,其他所有事务的查询、修改、新增全部阻塞,直到锁释放。

5. 避坑点

在MySQL默认的事务隔离级别可重复读(Repeatable-Read)级别下的InnoDB引擎的表,如果SQL语句没有走索引,行锁会自动升级为表锁!这是新手开发最常踩的坑。建议生产环境在能接受的情况下事务隔离级别使用读已提交级别(READ-COMMITTED)。

举例:update users set age=19 where name='张三'(name无索引),看似只改一行,实际锁住全表,导致整个表的写阻塞,其他事务无法更新其他记录的数据。

6. 适用场景

数据量极小、并发极低、整表操作场景,比如全表备份、批量初始化数据,日常业务开发极少使用。

三、行锁:InnoDB高并发的核心(重点)

1. 通俗概念

行锁就是只锁住需要操作的某一行数据,其他行的数据完全不受影响,其他事务可以正常操作表中其他数据,并发能力极强。

2. 核心前提

行锁必须依赖索引!!!如果操作语句没有命中索引,InnoDB无法定位具体行,行锁直接升级为表锁,并发直接失效。

3. 行锁的两种类型

  • 共享锁(S锁):又称读锁,多个事务可同时加锁读数据,互不阻塞,阻塞写操作
  • 排他锁(X锁):又称写锁,增删改默认加排他锁,只有当前事务可读写,阻塞所有其他事务

4. 实操案例

以下所有行锁案例,默认开启事务(MySQL默认手动事务需开启),事务不提交,锁不会释放。

案例1:排他锁(增删改默认触发)

事务1执行(开启事务,不提交):

代码语言:javascript
复制
begin; -- 或者用begin;
UPDATE users SET age=19 WHERE id=1; -- id是主键索引,仅锁id=1这一行

此时事务2执行:

代码语言:javascript
复制
begin;
UPDATE users SET age=25 WHERE id=1; -- 阻塞,被行锁锁住
UPDATE users SET age=25 WHERE id=2; -- 正常执行,不受影响

结论:行锁只锁当前操作行,不影响其他行,并发性能优异。

案例2:手动加共享锁

事务1:

代码语言:javascript
复制
begin;
SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 手动

加读锁

事务2:

代码语言:javascript
复制
begin;
SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 正常执行,读锁共享
UPDATE users SET age=20 WHERE id=1; -- 阻塞,读锁阻塞写操作

提交事务释放锁:

代码语言:javascript
复制
COMMIT;

5. 高频坑点

  • 索引失效(隐式转换、like模糊查询、无索引),行锁变表锁,导致全表阻塞
  • 事务执行时间过长,锁迟迟不释放,造成接口超时、死锁
  • 查询不加锁,增删改默认加排他锁

6. 适用场景

日常99%的业务并发场景,比如用户修改资料、订单状态更新、库存扣减等,是InnoDB默认的锁机制。

四、间隙锁:解决幻读的特殊锁

1. 先搞懂:什么是幻读?

幻读就是同一个事务内,两次相同的查询,结果行数不一样,莫名多出新数据。

举例:事务1查询age在18-22的数据,查出3条;事务2偷偷插入一条新数据并提交;事务1再次查询,多出1条数据,就像“幻觉”一样。

2. 间隙锁通俗概念

行锁是“锁已有数据行”,间隙锁是“锁数据之间的空白区间”,不锁已有数据,只禁止其他事务在这个区间插入新数据,专门用来彻底解决幻读问题。

核心前提:仅InnoDB、RR(可重复读)隔离级别生效(MySQL默认隔离级别),RC隔离级别没有间隙锁。

3. 配套知识点:临键锁

InnoDB RR级别默认使用临键锁,临键锁 = 行锁 + 间隙锁,既锁当前行,又锁前后间隙,是范围查询的默认锁机制。

4. 实操案例

实操案例可以参考历史文章

为什么我只改一行,MySQL却锁了这么多?

5. 常见踩坑点

范围查询(>、<、between、like)会触发间隙锁/临键锁,锁定范围远大于查询数据,容易造成莫名阻塞,这是很多线上卡顿的核心原因。

6. 作用总结

牺牲微小的并发性能,彻底解决RR隔离级别的幻读问题,保证事务查询结果一致性。

五、元数据锁(MDL):表结构的隐形守护者

1. 通俗概念

很多人只知道行锁、表锁,却忽略了MDL元数据锁。

MDL锁是MySQL自动加的表级锁,不需要手动操作,只要你对表做读写操作,就会自动加MDL锁,用来防止“一边读写数据,一边修改表结构”导致的数据错乱。

2. 核心规则

  • 读写MDL锁(共享):查询、增删改数据时加,多个事务可同时持有,互不阻塞
  • 改表MDL锁(排他):执行ALTER TABLE改表结构时加,排他锁,阻塞所有读写操作

3. 经典实操坑点案例

场景:事务1开启后,查询users表,但是迟迟不提交事务

代码语言:javascript
复制
begin;
SELECT * FROM users; -- 加MDL读锁,不释放

此时DBA执行改表语句:

代码语言:javascript
复制
ALTER TABLE users ADD COLUMN phone VARCHAR(11);

结果:改表语句永久阻塞,后续所有查询、更新users表的接口全部卡死!

原因:长事务持有MDL读锁不释放,MDL写锁(改表)无法获取,形成死阻塞。

4. 避坑点

  • 绝对不要写长事务,事务尽量短小,执行完立即提交
  • 线上改表结构,一定要避开业务高峰期,提前排查长事务
  • MDL锁阻塞是线上数据库卡顿、连接打满的常见元凶

MySQL必懂:为什么需要MDL锁?90%的人都栽过它的坑

六、 总结

1. 常见问题总结

Q1. 为什么有行锁还需要间隙锁?

A:行锁只能锁住已存在的数据,无法阻止其他事务插入新数据,间隙锁专门填补这个空白,彻底解决幻读。

Q2. 为什么没索引会变成表锁?

A: 行锁依赖索引定位具体行,无索引时MySQL不知道锁哪一行,就回锁定全表,保证数据安全。

Q3. MDL锁可以手动关闭吗?

A: 不可以,MDL是MySQL底层强制机制,目的是保护表结构安全,需要通过优化事务时长避免阻塞;大表做变更时(且不能online DDL时)建议使用pt-osc等工具操作

2. 避坑总结

  • 所有更新语句必须走索引(最好是主键),杜绝行锁升级表锁,避免全表阻塞
  • 事务尽量短平快,杜绝长事务,防止MDL锁、行锁长期不释放
  • 少用范围查询加锁,避免触发间隙锁,导致莫名的插入阻塞
  • 线上改表避开高峰,提前排查活跃长事务,防止服务卡死
  • 默认RR隔离级别无需修改,平衡并发与数据一致性
  • 如果可以调整隔离级别,可以调整为RC级别
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据库干货铺 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档