logo 范 · 拾光录
网址收集 关于作者 Github Gitee
杂文随笔4
Hexo博客:基础使用Hexo博客:Next主题Hexo博客:Next进阶使用Hexo博客:Next高级配置
前端编程3
前端基础知识16
Vue框架19
UniApp15
Java编程8
Java基础知识14
SpringBoot31
SpringMVC14
MyBatis8
Spring Cloud Netflix8
Spring Cloud Alibaba7
RocketMQ1
MyCat1
数据库4
MySQL12
Redis7
MongoDB10
H21
Java技术栈1
分布式锁的实现方案
软件工具箱14
IDEAGitMavenGradleNginx安装Nginx配置JMeter压测OllamaPicGoRustFSVSCodeDockerObsidianObs录制
Linux知识11
树莓派安装及使用ArchLinux:常用软件ArchLinux:基础系统安装ArchLinux:深度优化ArchLInux:图形化界面安装ArchLinux:Nirifrp内网穿透Jar启动脚本Linux常用命令VirtualBox安装CentOSVirtualBox安装Ubuntu
Python编程6
Python基础知识Python语法yolo目标检测OpenCV的使用及树莓派平台condauv
AI相关9
Claude CodeHermes AgentOpenAI基本使用OpenAI工具调用OpenAI记忆管理OpenAI推理执行OpenAI开发框架Langchainllama.cpp
创意设计2
Blender:入门知识UI设计基础知识

并发下为了保证资源的一致性等问题,而产生的技术手段,是阻塞的

锁的分类

MySQL 锁按不同维度划分:

维度 分类
粒度 全局锁、表级锁、页级锁(BDB)、行级锁
机制 共享锁(S锁)、排他锁(X锁)
思想 悲观锁(SELECT FOR UPDATE)、乐观锁(版本号/CAS)
加锁方式 隐式锁(自动加)、显式锁(手动 SQL)

InnoDB 是最常用的引擎,下面主要围绕 InnoDB 展开。

全局锁

对整个数据库实例加锁,让整个库只读。

FLUSH TABLES WITH READ LOCK;   -- 加全局读锁
UNLOCK TABLES;                  -- 释放

典型场景:全库逻辑备份(mysqldump 加 --single-transaction 可替代,避免锁库)。

注意:加了全局锁后任何 DML/DDL 都会被阻塞,生产慎用。

表级锁

表锁(Table Lock)

LOCK TABLES t1 READ;            -- 共享表锁
LOCK TABLES t1 WRITE;           -- 排他表锁
UNLOCK TABLES;

MyISAM 默认用表锁,开销小但并发差。InnoDB 一般不用显式表锁。

元数据锁(MDL)

MySQL 5.5 引入,自动隐式加锁,用于保证 DDL 和 DML 的冲突:

常见坑:一个慢查询持有 MDL 读锁不释放,后续 ALTER TABLE 尝试加 MDL 写锁被阻塞,然后写锁后面的所有 CRUD 也排队等 MDL 读锁→ 整个表"卡死"。

排查:SELECT * FROM performance_schema.metadata_locks;

意向锁(Intention Lock)

InnoDB 特有,隐式自动加锁,解决表锁和行锁的冲突判断:

意向共享锁(IS) 意向排他锁(IX)
表 S 锁 ✅ 兼容 ❌ 冲突
表 X 锁 ❌ 冲突 ❌ 冲突

记忆:意向锁之间永远兼容(IS/IS、IS/IX、IX/IX 均兼容),它只和表级锁冲突。

AUTO-INC 锁

自增主键插入时使用,保证 AUTO_INCREMENT 值的连续性。MySQL 8.0 默认用轻量级互斥量替代。

行级锁(InnoDB)

记录锁(Record Lock)

锁定单条索引记录

SELECT * FROM t WHERE id = 1 FOR UPDATE;   -- 对 id=1 加 X 锁

关键:行锁是加在索引上的,不是加在数据行上。如果查询条件不走索引,会退化为表锁。

间隙锁(Gap Lock)

锁定索引记录之间的间隙,不包含记录本身。只在 RR 隔离级别下存在。

索引值:  5    10    15    20
间隙:   (-∞,5) (5,10) (10,15) (15,20) (20,+∞)
SELECT * FROM t WHERE id BETWEEN 10 AND 15 FOR UPDATE;
-- 锁定 (5,10)、(10,15)、(15,20) 三个间隙 + 10、15 两条记录

作用:防止幻读 —— 间隙锁阻止其他事务在间隙中 INSERT 新数据。

临键锁(Next-Key Lock)

记录锁 + 间隙锁的组合,锁定"左开右闭"区间。InnoDB RR 级别的默认行锁算法。

锁住的是 (5, 10] 区间:间隙锁 (5,10) + 记录锁 [10]

效果:既防止当前记录被修改,也防止间隙插入,彻底杜绝幻读。

插入意向锁(Insert Intention Lock)

INSERT 前自动加的间隙锁的一种,多个事务对同一间隙的插入意向锁互相兼容(只要不插入相同位置),提升并发插入性能。

加锁规则

两个原则 + 两个优化 + 一个 bug:

原则

  1. 加锁的基本单位是 Next-Key Lock(左开右闭)
  2. 查找中访问到的对象才加锁

优化

  1. 唯一索引等值查询且记录存在 → 退化为 Record Lock
  2. 等值查询向右遍历到第一个不满足条件的值 → 最后一个间隙锁退化为 Gap Lock(右开)

Bug(MySQL 8.0.18 前):唯一索引范围查询会多锁一个 Next-Key Lock

死锁

产生条件(四要素,缺一不可)

  1. 互斥:资源不能被共享
  2. 持有并等待:持有已获得的锁,同时等待其他锁
  3. 不可剥夺:锁只能由持有者主动释放
  4. 循环等待:事务间形成等待环路

典型场景

-- 事务 A
UPDATE t SET v = 1 WHERE id = 1;   -- 获得 id=1 的 X 锁
UPDATE t SET v = 2 WHERE id = 2;   -- 等待 id=2 的锁 → 被 B 持有

-- 事务 B(并发)
UPDATE t SET v = 3 WHERE id = 2;   -- 获得 id=2 的 X 锁
UPDATE t SET v = 4 WHERE id = 1;   -- 等待 id=1 的锁 → 被 A 持有 → 死锁

排查

SHOW ENGINE INNODB STATUS;                          -- 查看最近死锁详情
SELECT * FROM information_schema.INNODB_TRX;        -- 当前事务
SELECT * FROM information_schema.INNODB_LOCKS;      -- 当前锁(8.0 废弃)
SELECT * FROM performance_schema.data_locks;        -- 当前锁(8.0+)

避免策略

  1. 固定加锁顺序(所有事务按相同顺序访问资源)
  2. 拆分大事务,缩短锁持有时间
  3. 合理使用索引(减少锁范围)
  4. 降低隔离级别(RC 去掉间隙锁,减少锁冲突)
  5. 死锁检测(innodb_deadlock_detect=ON)→ 自动回滚代价最小的事务

MVCC与锁

MVCC(多版本并发控制)是 InnoDB 实现读不加锁的核心机制,与锁体系互补:

对比 锁机制 MVCC
读操作 当前读(加锁) 快照读(不加锁)
实现 行锁 + 间隙锁 + 临键锁 undo log + ReadView
隔离级别 所有级别 RC + RR(RU 无,序列化退化为锁)

当前读(加锁):SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODEUPDATEDELETEINSERT

快照读(无锁):普通 SELECT

MVCC 通过 DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针)、DB_ROW_ID(行ID)三个隐藏字段 + undo log 版本链实现。

锁的分类
全局锁
表级锁
表锁(Table Lock)
元数据锁(MDL)
意向锁(Intention Lock)
AUTO-INC 锁
行级锁(InnoDB)
记录锁(Record Lock)
间隙锁(Gap Lock)
临键锁(Next-Key Lock)
插入意向锁(Insert Intention Lock)
加锁规则
死锁
产生条件(四要素,缺一不可)
典型场景
排查
避免策略
MVCC与锁