MySQL 锁机制详解

以 InnoDB、MySQL 8.0 为基准。核心结论:InnoDB 行锁加在索引记录上;RR 下用 Next-Key Lock 防幻读;RC 下基本只用记录锁;无索引查询会锁大量记录和间隙,效果接近表锁。

一、锁分类

按粒度:

  • 全局锁:锁定整个数据库实例。
  • 表级锁:锁定整张表,包括表锁、元数据锁、意向锁、AUTO-INC 锁。
  • 行级锁:锁定索引记录,包括记录锁、间隙锁、临键锁、插入意向锁。

按模式:

  • 共享锁(S):读锁,SELECT ... FOR SHARE。
  • 排他锁(X):写锁,SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT。

按算法:

  • Record Lock:记录锁。
  • Gap Lock:间隙锁。
  • Next-Key Lock:记录锁 + 间隙锁。
  • Insert Intention Lock:插入意向锁。
  • AUTO-INC Lock:自增锁。

InnoDB 支持行级锁,MyISAM 只支持表级锁。

二、全局锁

2.1 加锁与释放

sql 复制代码
FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;

加锁后整个实例处于只读状态:

  • 所有 DML 被阻塞。
  • 所有 DDL 被阻塞。
  • 更新事务无法提交。
  • 普通读可以执行。

典型用途:全库逻辑备份。

2.2 一致性数据备份

MyISAM 不支持事务,备份通常需要全局锁:

bash 复制代码
mysqldump --lock-all-tables -uroot -p db > db.sql

InnoDB 可以使用事务快照,无需长时间全局锁:

bash 复制代码
mysqldump --single-transaction --source-data=2 -uroot -p db > db.sql

--single-transaction 利用 MVCC 一致性快照,只对 InnoDB 有效。备份期间不能有 DDL,否则快照可能不一致。

如果必须使用全局锁备份,建议在从库执行,避免阻塞主库写入。流程:

  1. 主库或从库执行 FLUSH TABLES WITH READ LOCK;
  2. 记录 binlog 位点。
  3. 执行 mysqldump。
  4. 执行 UNLOCK TABLES;

三、表级锁

3.1 表锁

手动加锁:

sql 复制代码
LOCK TABLES t READ;
LOCK TABLES t WRITE;
UNLOCK TABLES;

兼容性:

锁类型 本会话 其他会话
读锁 可读,不可写 可读,写阻塞
写锁 可读,可写 读、写均阻塞

InnoDB 通常不手动加表锁,MyISAM 常用表锁。

3.2 元数据锁(MDL)

系统自动加锁,无需手动。

  • DML:自动加 MDL 读锁,事务提交后释放。
  • DDL:自动加 MDL 写锁,语句结束后释放。
  • MDL 读锁之间兼容。
  • MDL 读锁与写锁互斥。
  • MDL 写锁与写锁互斥。

作用:保证表结构在事务执行期间不被修改。

问题:

  • 长事务持有 MDL 读锁,会阻塞 DDL。
  • DDL 等待 MDL 写锁时,后续 DML 的 MDL 读锁也会排队,导致连接堆积。

查看 MDL 等待:

sql 复制代码
SELECT * FROM performance_schema.metadata_locks;
SELECT * FROM sys.schema_table_lock_waits;

处理:找到长事务并 kill。

sql 复制代码
SELECT * FROM information_schema.innodb_trx;

3.3 意向锁

InnoDB 自动维护的表级锁,表示事务准备在表中某些行上加 S 或 X 锁。

  • 意向共享锁(IS):准备加行 S 锁。
  • 意向排他锁(IX):准备加行 X 锁。

作用:加表锁时无需逐行检查行锁,只需检查表上的意向锁。

兼容矩阵:

当前 \ 请求 IS IX S X
IS 兼容 兼容 兼容 冲突
IX 兼容 兼容 冲突 冲突
S 兼容 冲突 兼容 冲突
X 冲突 冲突 冲突 冲突

3.4 意向锁测试

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE id = 1 FOR UPDATE;

此时事务 A 对 id=1 加行 X 锁,同时自动加表级 IX 锁。

事务 B:

sql 复制代码
LOCK TABLES t WRITE;

阻塞,因为表写锁与 IX 冲突。

事务 B:

sql 复制代码
LOCK TABLES t READ;

也阻塞,因为表读锁与 IX 冲突。

如果事务 A 使用:

sql 复制代码
SELECT * FROM t WHERE id = 1 FOR SHARE;

则加表级 IS 锁。此时事务 B 加表读锁兼容,加表写锁冲突。

四、行级锁

4.1 行级锁介绍

InnoDB 行锁基于索引项实现,不是直接锁物理行。

如果查询未命中索引,InnoDB 会扫描聚簇索引并锁大量记录和间隙,效果接近表锁。

普通 SELECT 是快照读,不加锁。

当前读加锁:

  • SELECT ... FOR SHARE:加 S 锁。
  • SELECT ... FOR UPDATE:加 X 锁。
  • UPDATE、DELETE、INSERT:加 X 锁。

4.2 行锁(记录锁)

示例表:

sql 复制代码
CREATE TABLE t (
  id INT PRIMARY KEY,
  a INT,
  b INT,
  KEY idx_a (a)
) ENGINE=InnoDB;

唯一索引等值命中:

sql 复制代码
SELECT * FROM t WHERE id = 5 FOR UPDATE;

加 Record Lock,仅锁 id=5 这条记录。

测试:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE id = 5 FOR UPDATE;

事务 B:

sql 复制代码
UPDATE t SET a = 10 WHERE id = 5;

阻塞。

事务 B:

sql 复制代码
UPDATE t SET a = 10 WHERE id = 6;

不阻塞。

非唯一索引等值:

sql 复制代码
SELECT * FROM t WHERE a = 5 FOR UPDATE;

对匹配记录加 Next-Key Lock,并在下一个间隙加 Gap Lock。

无索引查询:

sql 复制代码
SELECT * FROM t WHERE b = 5 FOR UPDATE;

b 无索引,全表扫描,锁所有记录和间隙,插入任何行都可能阻塞。

4.3 间隙锁与临键锁

  • Gap Lock:锁定索引记录之间的间隙,不锁记录本身,防止其他事务插入。
  • Next-Key Lock:Record Lock + Gap Lock,左开右闭区间 (prev, current]。
  • RR 隔离级别下,Next-Key Lock 是默认加锁单位,用于防止幻读。
  • RC 隔离级别下,间隙锁基本关闭,仅外键约束和唯一键冲突检查可能使用。

4.4 RR 下加锁规则

  1. 唯一索引等值命中:Next-Key Lock 退化为 Record Lock。
  2. 唯一索引等值未命中:加 Gap Lock。
  3. 非唯一索引等值:对匹配记录加 Next-Key Lock,并在下一个间隙加 Gap Lock。
  4. 范围查询:对扫描到的记录加 Next-Key Lock,直到不满足条件的第一个记录。
  5. 无索引查询:锁全表记录和间隙。

4.5 间隙锁与临键锁测试

数据:

sql 复制代码
INSERT INTO t (id, a) VALUES (1,1),(3,3),(5,5),(7,7),(9,9);

唯一索引等值命中:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE id = 5 FOR UPDATE;

事务 B:

sql 复制代码
INSERT INTO t (id, a) VALUES (4, 4);

不阻塞,因为只加 Record Lock,不锁间隙。

唯一索引等值未命中:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE id = 6 FOR UPDATE;

id=6 不存在,加 Gap Lock,锁住 (5,7) 间隙。

事务 B:

sql 复制代码
INSERT INTO t (id, a) VALUES (6, 6);

阻塞。

非唯一索引等值:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE a = 5 FOR UPDATE;

对 a=5 加 Next-Key Lock,锁 (3,5],并在下一个间隙加 Gap Lock,锁 (5,7)。

事务 B:

sql 复制代码
INSERT INTO t (a) VALUES (4);

阻塞,因为 4 在 (3,5) 间隙内。

事务 B:

sql 复制代码
INSERT INTO t (a) VALUES (6);

阻塞,因为 6 在 (5,7) 间隙内。

事务 B:

sql 复制代码
INSERT INTO t (a) VALUES (8);

不阻塞。

范围查询:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE a >= 5 FOR UPDATE;

锁范围包括:

  • (3,5]
  • (5,7]
  • (7,9]
  • (9,+∞)

插入 a=4、a=6、a=10 都可能阻塞。

无索引:

事务 A:

sql 复制代码
BEGIN;
SELECT * FROM t WHERE b = 5 FOR UPDATE;

全表扫描,所有记录和间隙被锁,插入任何行都阻塞。

五、死锁与排查

死锁:两个或多个事务互相持有对方需要的锁并等待。

InnoDB 行为:

  • 自动死锁检测,innodb_deadlock_detect 默认 ON。
  • 检测到死锁后,回滚代价较小的事务。
  • 锁等待超时由 innodb_lock_wait_timeout 控制,默认 50 秒。

查看死锁:

sql 复制代码
SHOW ENGINE INNODB STATUS\G

开启全量死锁日志:

sql 复制代码
SET GLOBAL innodb_print_all_deadlocks = ON;

查看当前锁:

sql 复制代码
SELECT * FROM performance_schema.data_locks;
SELECT * FROM performance_schema.data_lock_waits;
SELECT * FROM sys.innodb_lock_waits;

查看事务:

sql 复制代码
SELECT * FROM information_schema.innodb_trx;

减少死锁:

  • 事务尽量短。
  • 多表或多行访问保持一致的加锁顺序。
  • UPDATE、DELETE 的 WHERE 必须命中索引。
  • 业务允许时使用 READ COMMITTED。
  • 应用层对死锁回滚事务做重试。

六、总结

锁类型 粒度 加锁方式 主要作用
全局锁 实例 手动 全库备份
表锁 表 手动 简单并发控制
MDL 表 自动 保护表结构
意向锁 表 自动 辅助行锁与表锁冲突检测
记录锁 行 自动/手动 锁单条记录
间隙锁 间隙 自动 防插入,防幻读
临键锁 记录+间隙 自动 RR 默认加锁单位

核心要点:

  • InnoDB 行锁基于索引,无索引会锁全表。
  • RR 用 Next-Key Lock 防幻读,RC 仅记录锁。
  • 长事务、不一致访问顺序、缺失索引是锁冲突和死锁的主要来源。
  • 事务尽量短,UPDATE/DELETE 的 WHERE 必须命中索引。
  • 监控锁等待:performance_schema.data_locks、data_lock_waits、sys.innodb_lock_waits。
相关推荐
Nturmoils2 小时前
SQL 编辑器不只是写查询:我用 KStudio 排查一次订单异常
数据库
IvorySQL2 小时前
PostgreSQL 日报|逻辑解码 DDL 子事务缺陷修复(10 月 12 日)
数据库·postgresql
这个DBA有点耶2 小时前
研发规模/投入最大的国产数据库厂商是谁?拆解五个衡量维度
数据库·程序员·产品
葡萄城技术团队3 小时前
数据库里的数据明明是对的,为什么 BI 报表还是错?
数据库
野生数据人3 小时前
日志平台每日巡检我压成了一条命令链:从 SHOW BACKENDS 到扩缩容的完整步骤
大数据·数据库
bmxy小明同学3 小时前
2026-10-09-token与成本
java·前端·数据库
路远的数据库笔记3 小时前
Oracle报ORA-00020连接打满?从专用进程模型到静态参数,一次生产排查全记录
数据库·oracle·国产数据库·ora-00020·dba实战·sharedserver
瀚高PG实验室3 小时前
瀚高数据库序列介绍、使用及值不连续、重复写入等现象处理
数据库·postgresql·瀚高数据库
这个DBA有点耶3 小时前
PostgreSQL HOT更新:频繁更新表的索引膨胀根因与fillfactor调优
数据库·sql·postgresql