引言
线上系统突然报出死锁异常,业务数据更新卡住,排查半天却连锁的类型都分不清?行锁、表锁、间隙锁到底有啥区别?S 锁和 X 锁的竞争又是如何引发死锁的?作为后端开发者,数据库锁机制是绕不开的核心知识点,更是保障系统数据一致性和并发性能的关键。本文将从基础锁类型到死锁排查,层层拆解 MySQL 锁机制,带你吃透每个核心要点,轻松应对线上锁相关问题。
一、核心锁类型基础:S 锁与 X 锁
数据库锁的核心目的是解决并发场景下的数据一致性问题,而 Shared Locks(共享锁,简称 S 锁)和 Exclusive Locks(排他锁,简称 X 锁)是所有锁机制的基础,几乎所有数据库都实现了这两种核心锁类型。
1.1 共享锁(S 锁):读锁不互斥
共享锁的核心特质:多个事务可以同时持有同一资源的 S 锁,互不干扰,但会阻塞 X 锁的获取。
- 适用场景:只读操作,比如
SELECT * FROM table WHERE id=1 LOCK IN SHARE MODE;(MySQL 中显式加 S 锁) - 核心规则:
- 事务 A 获取资源 R 的 S 锁后,其他事务可正常获取 R 的 S 锁(读 - 读兼容)
- 事务 A 获取资源 R 的 S 锁后,其他事务请求 R 的 X 锁会被阻塞,直至 A 释放 S 锁(读 - 写互斥)
1.2 排他锁(X 锁):写锁全互斥
排他锁的核心特质:同一资源同一时间只能被一个事务持有 X 锁,既阻塞其他 X 锁,也阻塞 S 锁。
- 适用场景:写操作(插入、更新、删除),MySQL 中默认对写操作加 X 锁,比如
UPDATE table SET name='test' WHERE id=1; - 核心规则:
- 事务 A 获取资源 R 的 X 锁后,其他事务请求 R 的 S 锁或 X 锁都会被阻塞(写 - 读、写 - 写均互斥)
- 事务 A 释放 X 锁后,阻塞的事务才会按优先级依次获取锁资源
二、粒度区分:表锁与行锁
除了 S 锁和 X 锁的类型区分,按锁定资源的粒度,MySQL 锁可分为表锁(Table Lock)和行锁(Record Lock),两者的锁定范围和适用场景差异极大。
2.1 表锁:粗粒度锁,高效低并发
表锁是锁定整个数据表的锁机制,是 MySQL 中最基础、开销最小的锁类型,MyISAM 存储引擎默认支持表锁,InnoDB 也支持但不常用。
核心特性:
- 锁定范围:整个数据表,无论操作涉及多少行数据
- 优缺点:
- ✅ 优点:加锁/解锁速度快,开销小,不会产生死锁
- ❌ 缺点:锁定粒度大,并发性能差,比如事务 A 更新表中 1 行数据,事务 B 更新同一表中另一行数据会被阻塞
加锁方式:
-- 显式加表级 S 锁
LOCK TABLES table_name READ;
-- 显式加表级 X 锁
LOCK TABLES table_name WRITE;
-- 释放表锁
UNLOCK TABLES;
适用场景:
- 读多写少的场景(如日志查询表)
- 批量操作场景(如批量导入数据,加表锁避免频繁行锁竞争)
2.2 行锁:细粒度锁,低效高并发
行锁(Record Lock)是锁定数据表中某一行或多行数据的锁机制,仅 InnoDB 存储引擎支持,也是 MySQL 高并发场景下的核心锁类型。
核心特性:
- 锁定范围:仅涉及的行数据,不影响其他行
- 优缺点:
- ✅ 优点:锁定粒度小,并发性能好,多个事务可同时操作同一表中不同行数据
- ❌ 缺点:加锁/解锁速度慢,开销大,可能产生死锁
加锁方式:无需显式加锁,默认通过索引实现(重点!无索引会升级为表锁)
-- 写操作默认加行级 X 锁
DELETE FROM table_name WHERE id=1;
-- 读操作显式加行级 S 锁
SELECT * FROM table_name WHERE id=1 FOR UPDATE;
关键注意点:
划重点!行锁的实现依赖索引,如果查询条件没有使用索引(比如
WHERE name='test'且 name 无索引),InnoDB 会无法定位到具体行,此时会将行锁升级为表锁,导致并发性能骤降!
三、间隙锁与 Next-Key Lock:解决幻读的核心
行锁只能锁定已存在的行数据,无法解决幻读问题(事务 A 读取范围内数据,事务 B 插入该范围数据,A 再次读取出现新数据)。为此,InnoDB 引入了间隙锁(Gap Lock)和 Next-Key Lock,两者共同构成了解决幻读的方案。
3.1 间隙锁(Gap Lock):锁定区间,阻止插入
- 定义:锁定数据行之间的间隙(包括行前和行后),不锁定行本身,核心目的是阻止其他事务在间隙中插入数据
- 适用场景:仅在 InnoDB 的 Repeatable Read(可重复读)隔离级别下生效(MySQL 默认隔离级别)
- 示例:
假设表中有 id 为 1、3、5 的行数据,事务 A 执行
SELECT * FROM table WHERE id BETWEEN 1 AND 5 FOR UPDATE;,此时会锁定以下间隙:- (负无穷,1)
- (1, 3)
- (3, 5)
- (5, 正无穷) 事务 B 尝试插入 id=2 或 4 的数据时,会被间隙锁阻塞
3.2 Next-Key Lock:行锁 + 间隙锁的组合
- 定义:Next-Key Lock = 行锁(Record Lock) + 间隙锁(Gap Lock),既锁定当前行,也锁定当前行与下一行之间的间隙
- 核心规则:锁定的范围是'左开右闭'区间,比如 id=3 的行,Next-Key Lock 锁定的范围是 (1, 3]
- 作用:彻底解决幻读问题,是 InnoDB Repeatable Read 隔离级别下的默认锁机制
- 示例:
表中 id=1、3、5,事务 A 执行
SELECT * FROM table WHERE id=3 FOR UPDATE;,此时 Next-Key Lock 锁定的范围是 (1, 3],事务 B:- 更新 id=3 的数据:被行锁阻塞
- 插入 id=2 的数据:被间隙锁阻塞
四、死锁(Deadlock):成因、场景与排查
死锁是并发系统中最棘手的问题之一,当两个或多个事务互相持有对方需要的锁资源,且都不主动释放时,就会陷入无限等待的死锁状态。
4.1 死锁产生的 3 大核心原因
- 互斥条件:资源(锁)只能被一个事务持有(X 锁的核心特性)
- 持有并等待:事务 A 持有锁 1,同时等待事务 B 持有的锁 2;事务 B 持有锁 2,同时等待事务 A 持有的锁 1
- 不可剥夺:事务持有锁时,其他事务无法强制剥夺该锁,只能等待其主动释放
- 循环等待:多个事务形成循环等待锁资源的链条(比如 A 等 B,B 等 C,C 等 A)
4.2 典型死锁场景示例
假设有 user 表(id 为主键),两个事务同时执行以下操作:
事务 A:
BEGIN;
-- 持有 id=1 的行级 X 锁
UPDATE user SET name='A' WHERE id=1;
-- 等待 id=2 的行级 X 锁
UPDATE user SET name='A' WHERE id=2;
COMMIT;
事务 B:
BEGIN;
-- 持有 id=2 的行级 X 锁
UPDATE user SET name='B' WHERE id=2;
-- 等待 id=1 的行级 X 锁
UPDATE user SET name='B' WHERE id=1;
COMMIT;
此时两个事务互相等待对方的锁资源,形成死锁,MySQL 会自动检测到死锁,并回滚其中一个事务(代价较小的那个)。
4.3 死锁排查的 4 个核心步骤
步骤 1:开启死锁日志(永久生效需修改配置文件)
-- 临时开启死锁日志(重启 MySQL 失效)
SET GLOBAL innodb_print_all_deadlocks = 1;
-- 查看死锁日志位置
SHOW VARIABLES LIKE 'datadir';
死锁日志会记录在 MySQL 的错误日志中(通常是 hostname.err 文件)。
步骤 2:实时查看锁状态
-- 查看当前事务持有和等待的锁信息
SELECT * FROM information_schema.innodb_locks;
-- 查看当前事务等待锁的情况
SELECT * FROM information_schema.innodb_lock_waits;
-- 查看当前运行的事务
SELECT * FROM information_schema.innodb_trx;
步骤 3:分析死锁日志核心信息
死锁日志会包含以下关键内容,帮助定位问题:
- 死锁事务的 ID(TRX ID)
- 每个事务持有和等待的锁类型(行锁/表锁、S 锁/X 锁)
- 每个事务执行的 SQL 语句
- 死锁检测结果和回滚的事务
步骤 4:优化并规避死锁
根据排查结果,针对性优化,核心规避方案:
- 统一事务操作顺序:所有事务对同一组资源的操作顺序保持一致(比如都按 id 升序操作)
- 缩短事务执行时间:减少事务中不必要的操作,避免长时间持有锁
- 避免批量加锁:尽量拆分批量操作,避免一次性锁定大量行数据
- 合理设置隔离级别:非必要不使用 Repeatable Read,可降低间隙锁带来的死锁概率
- 加锁前预判:对可能产生死锁的场景,可通过显式加锁或事务超时设置规避
五、全文总结
本文从核心锁类型(S 锁/X 锁)出发,详解了表锁与行锁的粒度差异、适用场景,深入剖析了间隙锁与 Next-Key Lock 解决幻读的底层逻辑,最后重点讲解了死锁的成因、典型场景及排查优化方案。
掌握数据库锁机制,不仅能快速定位线上并发问题,更能在系统设计阶段规避潜在风险,保障数据一致性和系统高可用性。建议大家结合实际业务场景多练多总结,真正吃透锁机制的核心要点。


