一,介绍
MySQL的锁机制是保证数据一致性和并发控制的核心组件,尤其在多用户环境下至关重要。
按锁粒度划分
-
全局锁(Global Lock)
- 作用:锁定整个数据库实例,确保备份时数据一致性。
-
表级锁(Table-Level Lock)
- 特点:锁定整张表,开销小、加锁快,但并发度低。
-
行级锁(Row-Level Lock)
- 特点:锁定单行或多行,开销大、加锁慢,但并发度高。
二,全局锁
2.1 介绍
全局锁就是对整个数据库实例加锁。
枷锁后整个实例就处于只读状态,后续的增删改操作,事务提交操作,修改表操作等会处于阻塞状态。全局锁一般用在数据库的逻辑备份,对所有表进行锁定,从而获取一致性视图,确保数据的完整性。
2.2 语法
加全局锁
sql
flush tables with read lock;
解锁
sql
unlock tables;
2.3 数据库备份(mysqldump)
bash
mysqldump -u账号 -p密码 -h主机地址 要备份的数据库 > 备份的路径/xxx.sql
mysqldump
是mysql
提供的一个备份工具,直接在命令行执行即可不需要进入mysql bash
,xxx.sql
文件存储的是SQL语句!
三,表级锁
3.1 介绍
- 锁定粒度:锁定整张表,无论操作涉及多少行数据。
- 加锁速度:开销小,加锁快(仅需维护表级别的锁状态)。
- 并发性能:并发度低,同一时间只能有一个事务对表进行写操作。
- 适用存储引擎 :
- MyISAM:默认使用表级锁(仅支持表级锁)。
- InnoDB:支持行级锁,但在某些场景(如无索引的全表扫描)会退化为表级锁。
3.2 表级锁类型
- 表共享读锁(S Lock) :
- 允许其他会话读表,但阻塞所有写操作。
- 显式加锁:
LOCK TABLES table_name READ;
- 表独占写锁(X Lock) :
- 独占访问,阻塞其他会话的读写操作。
- 显式加锁:
LOCK TABLES table_name WRITE;
- 元数据锁(MDL) :
- 隐式锁,保护表结构变更(DDL操作)。
- 读锁(SHARED_READ)与写锁(EXCLUSIVE)的互斥规则严格。
- 意向锁(Intention Lock) :
- 意向共享锁(IS):事务计划对某些行加共享锁(S)。
- 意向排他锁(IX):事务计划对某些行加排他锁(X)。
3.3 表共享读锁\表独占写锁
3.3.1 表共享读锁(Shared Read Lock, S Lock)
- 作用 :允许多个事务同时读取表数据,但阻塞所有写操作。
- 加锁方式 :
- 显式加锁 :
LOCK TABLES table_name READ;
- 隐式加锁 (仅MyISAM引擎):执行
SELECT
时自动加读锁。
- 显式加锁 :
- 兼容性 :
- 允许其他事务加读锁(共享读)。
- 阻塞其他事务加写锁(互斥写)。
- 释放条件 :
- 显式释放:
UNLOCK TABLES;
- 隐式释放(MyISAM):查询结束后自动释放(非事务引擎)。
- 显式释放:
加锁流程示例(MyISAM引擎)
场景:会话A加表共享读锁,会话B尝试读写。
sql
-- 会话A(显式加读锁)
LOCK TABLES users READ;
-- 会话A执行读操作(允许)
SELECT * FROM users WHERE id=1;
-- 会话B尝试读操作(允许)
SELECT * FROM users WHERE id=2; -- 成功执行
-- 会话B尝试写操作(阻塞)
UPDATE users SET name='Bob' WHERE id=1; -- 等待会话A释放锁
-- 会话A释放锁
UNLOCK TABLES;
-- 会话B的UPDATE操作继续执行
结果:
- 会话B的读操作(
SELECT
)不受影响。 - 会话B的写操作(
UPDATE
)被阻塞,直到会话A释放锁。
3.3.2 表独占写锁(Exclusive Write Lock, X Lock)
- 作用:独占整个表,禁止其他事务读写。
- 加锁方式 :
- 显式加锁 :
LOCK TABLES table_name WRITE;
- 隐式加锁 (仅MyISAM引擎):执行
INSERT/UPDATE/DELETE
时自动加写锁。
- 显式加锁 :
- 兼容性 :
- 阻塞其他事务的所有读写操作(完全独占)。
- 释放条件 :
- 显式释放:
UNLOCK TABLES;
- 隐式释放(MyISAM):写操作完成后自动释放。
- 显式释放:
加锁流程示例(MyISAM引擎)
场景:会话A加表独占写锁,会话B尝试读写。
sql
-- 会话A(显式加写锁)
LOCK TABLES users WRITE;
-- 会话A执行写操作(允许)
UPDATE users SET name='Alice' WHERE id=1;
-- 会话B尝试读操作(阻塞)
SELECT * FROM users WHERE id=2; -- 等待会话A释放锁
-- 会话B尝试写操作(阻塞)
INSERT INTO users (id, name) VALUES (3, 'Charlie'); -- 同样被阻塞
-- 会话A释放锁
UNLOCK TABLES;
-- 会话B的SELECT和INSERT操作继续执行
结果:会话B的所有读写操作均被阻塞,直到会话A释放锁。
3.3.3 锁的兼容性与冲突规则
当前锁类型 \ 请求锁类型 | 共享读锁(S) | 独占写锁(X) |
---|---|---|
共享读锁(S) | ✅ 兼容 | ❌ 不兼容 |
独占写锁(X) | ❌ 不兼容 | ❌ 不兼容 |
- 核心规则 :
- 读锁之间共享,写锁完全独占。
- 写锁优先级高于读锁(等待队列中写锁可能优先获取)。
3.3.4 InnoDB中的表级锁退化
InnoDB默认使用行级锁,但在以下场景会退化为表级锁:
-
无索引的更新/删除 :
sql-- 若age字段无索引,退化为表锁 UPDATE users SET status=1 WHERE age > 30;
-
显式锁表 :
sqlLOCK TABLES users WRITE; -- 强制使用表级锁(破坏InnoDB行锁机制)
示例:InnoDB无索引导致表锁
sql
-- 会话A(无索引UPDATE)
BEGIN;
UPDATE users SET status=1 WHERE age > 30; -- age字段无索引,退化为表锁
-- 会话B尝试更新其他行(被阻塞)
UPDATE users SET name='Dave' WHERE id=5; -- 因表锁被阻塞,直到会话A提交
3.3.5 监控锁状态
- 查看当前锁信息
go
```sql
sql
SHOW OPEN TABLES WHERE In_use > 0; -- 查看被锁定的表
SHOW PROCESSLIST; -- 查看阻塞的会话
```
- MyISAM锁监控
sql
```sql
SHOW STATUS LIKE 'Table_locks%';
-- Table_locks_immediate: 立即获取锁的次数
-- Table_locks_waited: 需要等待锁的次数(值高表示锁竞争严重)
```
- InnoDB锁监控(需启用InnoDB监控)
sql
```sql
-- 查询INFORMATION_SCHEMA表
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; -- 当前持有的锁
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; -- 锁等待关系
```
3.3.6 总结
- 表共享读锁(S):允许多读,禁写,适合读多写少场景。
- 表独占写锁(X):完全独占,适合批量写入或表结构变更。
- 注意事项 :
- MyISAM依赖表级锁,InnoDB慎用显式锁表。
- 避免长事务持有锁,减少锁竞争。
- 合理设计索引,防止InnoDB退化为表锁。
3.4 元数据锁
3.4.1 元数据锁(MDL)的核心作用
- 保护表结构 :防止在数据操作(DML)过程中,表结构(Schema)被意外修改(如
ALTER TABLE
、DROP TABLE
),导致数据不一致。 - 隐式自动管理:由MySQL自动加锁,无需用户手动干预。
- 所有存储引擎生效:无论是InnoDB、MyISAM还是其他引擎,均受MDL约束。
3.4.2 MDL锁的类型与行为
锁类型 | 触发场景 | 兼容性规则 |
---|---|---|
MDL读锁(SHARED_READ) | SELECT 、UPDATE 、DELETE 等DML操作 |
允许多个会话同时持有,但阻塞DDL操作。 |
MDL写锁(EXCLUSIVE) | ALTER TABLE 、DROP TABLE 等DDL操作 |
独占锁,阻塞所有其他会话的DML和DDL操作 |
3.4.3 MDL锁的典型问题场景
-
长事务阻塞DDL操作
sql-- 会话A(长事务) BEGIN; SELECT * FROM users; -- 隐式加MDL读锁 -- 不提交事务... -- 会话B(DDL操作) ALTER TABLE users ADD COLUMN age INT; -- 需要MDL写锁,被阻塞
- 结果 :会话B的
ALTER TABLE
会一直等待,直到会话A提交或超时(默认50秒)。
-
DDL操作阻塞后续查询
sql-- 会话A(DDL操作) ALTER TABLE users ADD COLUMN email VARCHAR(255); -- 加MDL写锁 -- 会话B(普通查询) SELECT * FROM users; -- 需要MDL读锁,被阻塞
- 结果 :会话B的
SELECT
在DDL完成前无法执行。
3.4.4 总结
- MDL锁是隐式锁:自动保护表结构,避免DDL与DML并发导致数据不一致。
- 核心问题:长事务和DDL操作的冲突是MDL锁问题的根源。
- 优化关键 :
- 及时提交事务,避免长查询。
- 使用在线DDL工具减少锁冲突。
- 监控锁等待和会话状态。
3.5 意向锁
3.5.1 意向锁的核心作用
意向锁是 表级锁 ,用于协调 行级锁 与 表级锁 的共存,解决以下问题:
- 避免全表扫描检查行锁: 当需要加表级锁时,无需逐行检查是否有行锁冲突,只需检查意向锁即可快速判断。
- 支持多粒度锁共存: 允许行级锁(细粒度)和表级锁(粗粒度)同时存在,提升并发效率。
- 减少锁冲突概率: 通过层级锁机制(意向锁 -> 行锁),降低锁竞争复杂度。
3.5.2 意向锁的类型
锁类型 | 缩写 | 行为 |
---|---|---|
意向共享锁 | IS | 表示事务计划对表中的某些行加 共享锁(S Lock)(读操作)。 |
意向排他锁 | IX | 表示事务计划对表中的某些行加 排他锁(X Lock)(写操作) |
3.5.3 意向锁的兼容性规则
意向锁之间的兼容性决定了多个事务能否同时操作同一张表:
当前锁类型 \ 请求锁类型 | IS | IX | 表级S锁(读锁) | 表级X锁(写锁) |
---|---|---|---|---|
IS | ✅ | ✅ | ✅ | ❌ |
IX | ✅ | ✅ | ❌ | ❌ |
表级S锁(S) | ✅ | ❌ | ✅ | ❌ |
表级X锁(X) | ❌ | ❌ | ❌ | ❌ |
- 关键规则 :
- IS与IS、IX、表级S锁兼容:允许多个事务同时读取表的不同行。
- IX与IS、IX兼容:允许多个事务同时修改不同行。
- 表级S锁/X锁与意向锁互斥:表级锁需要独占访问,必须等待意向锁释放。
3.5.4 加锁流程与示例
1. 加锁规则
- 层级加锁 :事务在加行锁前,必须先对表加对应的意向锁(IS或IX)。
- 加行级S锁前 → 先加IS锁。
- 加行级X锁前 → 先加IX锁。
2. 示例场景
-
场景1:事务A读取某一行(加IS锁)
sql-- 事务A BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 对表加IS锁,对id=1加行级S锁 -- 其他事务: -- 可以继续对表加IS锁(如SELECT其他行),但无法加表级X锁。
-
场景2:事务B修改某一行(加IX锁)
sql-- 事务B BEGIN; UPDATE users SET name='Alice' WHERE id=2; -- 对表加IX锁,对id=2加行级X锁 -- 其他事务: -- 可以加IS锁(读其他行),但无法加表级S锁或X锁。
-
场景3:事务C尝试加表级S锁
sql-- 事务C LOCK TABLES users READ; -- 请求表级S锁 -- 若存在活跃的IX锁(如事务B未提交),事务C会被阻塞,直到所有IX锁释放。
3.5.5 意向锁的意义与优势
- 性能优化: 避免在加表级锁时遍历所有行检查锁状态,减少资源消耗。
- 锁粒度协调 : 允许行级锁和表级锁共存,例如:
- 一个事务持有行级X锁(IX锁),另一个事务可持有表级S锁(只要没有IX冲突)。
- 死锁预防: 层级锁机制(意向锁先行)减少了循环等待的可能性。
3.5.6 意向锁的监控
通过InnoDB系统表查看意向锁状态:
sql
-- 查看当前持有的锁(包括意向锁)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
-- 查看锁等待关系
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
3.5.7 总结
- 意向锁是InnoDB实现多粒度锁的核心机制,平衡了并发性能与数据一致性。
- IS锁:预示事务要读取某些行。
- IX锁:预示事务要修改某些行。
- 核心价值:通过层级锁机制,高效管理行锁与表锁的共存,避免全表扫描检查锁冲突。
四,行级锁
4.1 介绍
行级锁是InnoDB存储引擎的核心特性,用于在 高并发事务 中实现细粒度的数据访问控制,解决以下问题:
- 提升并发性能:仅锁定需要操作的行,而非整张表。
- 避免锁冲突:不同事务可以同时操作不同行,减少阻塞。
- 支持复杂事务 :结合事务隔离级别(如
REPEATABLE READ
),实现ACID特性。
4.2 记录锁
4.2.1 记录锁的定义与作用
- 定义 :记录锁是 行级锁 的一种,锁定索引中的 单条记录。
- 核心作用 :
- 确保事务对单行数据的 独占访问 或 共享访问。
- 防止其他事务并发修改同一行数据,保证数据一致性。
4.2.2 记录锁的触发场景
-
显式加锁
通过
SELECT ... FOR UPDATE
(排他锁)或SELECT ... LOCK IN SHARE MODE
(共享锁)显式加锁。sql-- 排他锁(X Lock) BEGIN; SELECT * FROM users WHERE id=1 FOR UPDATE; -- 对id=1加记录X锁 -- 共享锁(S Lock) BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 对id=1加记录S锁
-
隐式加锁
DML操作 :
UPDATE
、DELETE
语句自动对符合条件的行加 排他锁。sqlBEGIN; UPDATE users SET name='Alice' WHERE id=1; -- 自动对id=1加记录X锁
4.2.3 记录锁的兼容性规则
当前锁类型 \ 请求锁类型 | 共享锁(S) | 排他锁(X) |
---|---|---|
共享锁(S) | ✅ 兼容 | ❌ 不兼容 |
排他锁(X) | ❌ 不兼容 | ❌ 不兼容 |
- 共享锁(S):允许多个事务同时读取同一行。
- 排他锁(X):独占访问,阻塞其他事务的所有操作。
4.2.4 记录锁的加锁流程示例
-
场景1:共享锁(S)与共享锁(S)兼容
sql-- 事务A BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 加S锁 -- 事务B BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 允许加S锁,正常读取
-
场景2:共享锁(S)与排他锁(X)冲突
sql-- 事务A BEGIN; SELECT * FROM users WHERE id=1 LOCK IN SHARE MODE; -- 加S锁 -- 事务B BEGIN; UPDATE users SET name='Bob' WHERE id=1; -- 尝试加X锁,被阻塞,直到事务A提交
-
场景3:排他锁(X)完全独占
sql-- 事务A BEGIN; UPDATE users SET name='Alice' WHERE id=1; -- 自动加X锁 -- 事务B BEGIN; SELECT * FROM users WHERE id=1 FOR UPDATE; -- 尝试加X锁,被阻塞
4.2.5 记录锁的底层实现
- 依赖索引 :记录锁基于索引 实现,若查询条件无索引,InnoDB会退化为 表级锁。
- 锁定索引记录 :
- 若表有主键,直接锁定主键索引。
- 若表无主键但有唯一索引,锁定唯一索引。
- 若表无索引,锁定隐藏的
DB_ROW_ID
(退化为表锁)。
示例:无索引导致表锁
sql
-- 假设age字段无索引
BEGIN;
UPDATE users SET status=1 WHERE age=30; -- 无索引,退化为表级锁
4.2.6 记录锁的监控与诊断
-
查看当前记录锁
sql-- 查看InnoDB锁信息(MySQL 5.7+) SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS WHERE LOCK_TYPE = 'RECORD'; -- 输出示例: -- LOCK_ID: 1234:5:3:2 -- LOCK_TRX_ID: 1234 -- 事务ID -- LOCK_MODE: X,REC_NOT_GAP -- 排他记录锁(非间隙) -- LOCK_DATA: 1 -- 锁定的记录值(如id=1)
-
分析锁等待
sql-- 查看锁等待关系 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; -- 输出示例: -- requesting_trx_id: 5678 -- 请求锁的事务ID -- blocking_trx_id: 1234 -- 持有锁的事务ID
-
死锁日志分析
sqlSHOW ENGINE INNODB STATUS; -- 查看最近死锁信息
在输出日志的LATEST DETECTED DEADLOCK
部分,可找到死锁涉及的记录锁详情。
4.2.7 总结
- 记录锁是行级锁的基石:直接控制单行数据的并发访问。
- 显式与隐式加锁 :通过
FOR UPDATE
或DML操作触发,需注意索引设计。 - 优化核心:索引、事务控制、死锁处理。
4.3 间隙锁
4.3.1 间隙锁的定义与作用
- 定义 :间隙锁是 行级锁 的一种,锁定索引记录之间的 间隙 (即一个范围,不包含记录本身)。
- 核心作用 :
- 防止幻读(Phantom Read):确保事务执行过程中,其他事务无法在锁定的间隙内插入新数据。
- 范围保护:锁定查询条件覆盖的区间,保证事务多次读取的范围结果一致。
- 生效条件 :仅在
REPEATABLE READ
隔离级别下生效(InnoDB默认级别)。

4.3.2 间隙锁的触发场景
-
范围查询
sql-- 锁定id在(5, 10)之间的间隙(不包含5和10) SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
-
非唯一索引的等值查询
sql-- 假设age是非唯一索引 SELECT * FROM users WHERE age = 25 FOR UPDATE; -- 锁定age=25前后的间隙
-
唯一索引的间隙查询
sql-- 若表中没有id=7的记录,则锁定id=5到id=10的间隙 SELECT * FROM users WHERE id=7 FOR UPDATE;
4.3.3 间隙锁的兼容性规则
当前锁类型 \ 请求锁类型 | 共享间隙锁(S) | 排他间隙锁(X) | 插入意向锁 |
---|---|---|---|
共享间隙锁(S) | ✅ 兼容 | ✅ 兼容 | ❌ 不兼容 |
排他间隙锁(X) | ✅ 兼容 | ✅ 兼容 | ❌ 不兼容 |
插入意向锁 | ❌ 不兼容 | ❌ 不兼容 | ✅ 兼容 |
- 关键规则 :
- 间隙锁之间完全兼容,允许多个事务同时锁定同一间隙。
- 间隙锁与插入意向锁互斥,插入操作会被阻塞。
4.3.4 间隙锁的底层实现
- 基于索引 :间隙锁仅作用于索引 ,若表无索引,InnoDB会创建隐藏的聚簇索引 (
DB_ROW_ID
)来实现。 - 锁定范围 :
- 对于唯一索引的等值查询(命中记录),退化为记录锁(无间隙锁)。
- 对于非唯一索引或未命中记录的查询,锁定间隙。
示例1:非唯一索引触发间隙锁
sql
-- 表结构:id(主键), age(非唯一索引)
-- 数据:id=1(age=20), id=3(age=25), id=5(age=30)
-- 事务A
BEGIN;
SELECT * FROM users WHERE age=25 FOR UPDATE;
-- 锁定age=25的间隙:(20,25)和(25,30)
示例2:唯一索引未命中记录触发间隙锁
sql
-- 表数据:id=5, 10, 15(主键)
-- 事务A
BEGIN;
SELECT * FROM users WHERE id=7 FOR UPDATE;
-- 锁定间隙:(5,10)
4.3.5 间隙锁与幻读的解决
-
幻读现象:事务A两次读取同一范围,期间事务B插入新数据,导致事务A第二次读取出现"幻行"。
-
间隙锁的作用 :
sql-- 事务A(REPEATABLE READ隔离级别) BEGIN; SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 锁定age>20的间隙 -- 事务B INSERT INTO users (age) VALUES (25); -- 被阻塞,直到事务A提交
- 事务A的间隙锁阻止了事务B的插入,确保两次查询结果一致。
4.3.6 间隙锁的监控与诊断
-
查看当前间隙锁
sql-- 查看InnoDB锁信息(MySQL 5.7+) SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS WHERE LOCK_MODE LIKE '%GAP%'; -- 输出示例: -- LOCK_ID: 1234:5:3:2 -- LOCK_TYPE: RECORD -- LOCK_MODE: X,GAP -- 排他间隙锁 -- LOCK_DATA: 10 -- 锁定的间隙上限(如id=10)
-
分析锁等待
sql-- 查看被阻塞的插入操作 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-
死锁日志分析
sqlSHOW ENGINE INNODB STATUS;
在日志的
LATEST DETECTED DEADLOCK
部分,可找到间隙锁冲突导致的死锁信息。
4.3.7 总结
- 间隙锁是防止幻读的核心机制:通过锁定索引间隙,确保事务范围内的数据一致性。
- 触发条件:范围查询、非唯一索引查询、唯一索引未命中记录。
- 优化核心:索引设计、事务隔离级别选择、避免长事务。
4.3 临间锁
4.3.1 临键锁的定义与作用
- 定义 :临键锁是 行级锁 的一种,由 记录锁(Record Lock) 和 间隙锁(Gap Lock) 组合而成,锁定索引记录及其 之前的间隙。
- 核心作用 :
- 解决幻读(Phantom Read):确保事务执行过程中,其他事务无法在锁定范围内插入新数据。
- 范围保护:锁定查询条件覆盖的记录及间隙,保证事务多次读取的范围结果一致。
- 默认机制 :InnoDB在
REPEATABLE READ
隔离级别下默认使用临键锁。
4.3.2 临键锁的触发场景
-
范围查询
sql-- 锁定id >= 10的记录及之前的间隙 SELECT * FROM users WHERE id >= 10 FOR UPDATE;
-
非唯一索引的等值查询
sql-- 假设age是非唯一索引,锁定age=25的记录及前后间隙 SELECT * FROM users WHERE age = 25 FOR UPDATE;
-
未命中记录的查询
sql-- 若表中无id=7的记录,锁定(5,10)的间隙及记录(临键锁) SELECT * FROM users WHERE id=7 FOR UPDATE;
4.3.3 临键锁的组成与范围
- 组成 :
- 记录锁:锁定索引中的一条记录(如果存在)。
- 间隙锁:锁定该记录之前的间隙。
- 锁定范围 :
- 若查询条件命中记录:锁定该记录及其之前的间隙。
- 若未命中记录:仅锁定间隙(退化为间隙锁)。
示例:
sql
-- 表数据:id=5, 10, 15(主键)
-- 事务A
BEGIN;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
- 锁定范围 :
- 记录锁:id=10、15(若存在)。
- 间隙锁:(10, 15)、(15, 20)。
- 总锁定区间 :
[10, +∞)
。
4.3.4 临键锁的兼容性规则
当前锁类型 \ 请求锁类型 | 临键锁(X) | 共享锁(S) | 插入意向锁 |
---|---|---|---|
临键锁(X) | ❌ 不兼容 | ❌ 不兼容 | ❌ 不兼容 |
共享锁(S) | ❌ 不兼容 | ✅ 兼容 | ✅ 兼容 |
插入意向锁 | ❌ 不兼容 | ✅ 兼容 | ✅ 兼容 |
- 关键规则 :
- 临键锁(X)与其他锁完全互斥,保证独占性。
- 插入意向锁与临键锁互斥,插入操作需等待。
4.3.5 临键锁与幻读的解决
-
幻读现象:事务A两次读取同一范围,事务B插入新数据,导致事务A第二次读取出现"幻行"。
-
临键锁的作用 :
sql-- 事务A(REPEATABLE READ隔离级别) BEGIN; SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 加临键锁,锁定(10, +∞) -- 事务B INSERT INTO users (id) VALUES (12); -- 被阻塞,直到事务A提交
- 事务A的临键锁阻止了事务B的插入,确保两次查询结果一致。
4.3.6 临键锁的监控与诊断
-
查看当前临键锁
sql-- 查看InnoDB锁信息(MySQL 5.7+) SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS WHERE LOCK_MODE LIKE 'X%'; -- 临键锁模式为X或IX -- 输出示例: -- LOCK_ID: 1234:5:3:2 -- LOCK_TYPE: RECORD -- LOCK_MODE: X -- 临键锁(默认模式) -- LOCK_DATA: 10 -- 锁定的记录值
-
分析锁等待
sql-- 查看被临键锁阻塞的操作 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-
死锁日志分析
sqlSHOW ENGINE INNODB STATUS;
在日志LATEST DETECTED DEADLOCK`部分,可找到临键锁导致的死锁信息。
4.3.7 临键锁的示例场景
-
场景1:范围查询触发临键锁
sql-- 表数据:id=5, 10, 15 -- 事务A BEGIN; SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 锁定(10, 15), (15, +∞) -- 事务B INSERT INTO users (id) VALUES (12); -- 被阻塞 UPDATE users SET name='Bob' WHERE id=15; -- 被阻塞
-
场景2:非唯一索引触发临键锁
sql-- 表结构:id(主键), age(非唯一索引) -- 数据:id=1(age=20), id=3(age=25), id=5(age=30) -- 事务A BEGIN; SELECT * FROM users WHERE age >= 25 FOR UPDATE; -- 锁定age=25的记录及(25,30)的间隙 -- 事务B INSERT INTO users (age) VALUES (28); -- 被阻塞
4.3.8 总结
- 临键锁是InnoDB的默认锁机制 :在
REPEATABLE READ
级别下,通过组合记录锁和间隙锁彻底解决幻读。 - 锁定范围:索引记录及其之前的间隙,确保事务范围内的数据一致性。
- 优化核心:索引设计、查询条件控制、事务拆分。
4.4 插入意向锁
4.4.1 插入意向锁的定义与作用
- 定义 :插入意向锁是 间隙锁(Gap Lock) 的一种特殊类型,在插入新数据前对目标间隙加锁,表示"准备在此间隙插入数据"。
- 核心作用 :
- 优化并发插入:允许多个事务在同一间隙的不同位置插入数据(只要不冲突),提高插入效率。
- 避免锁冲突:与普通间隙锁不同,插入意向锁之间互相兼容,仅在目标位置冲突时阻塞。
4.4.2 插入意向锁的触发场景
-
插入操作前隐式加锁
sql-- 事务A尝试插入数据 BEGIN; INSERT INTO users (id) VALUES (15); -- 对间隙(10,20)加插入意向锁
-
与间隙锁的冲突
sql-- 事务A已锁定间隙(10,20) BEGIN; SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE; -- 加间隙锁 -- 事务B尝试插入数据 BEGIN; INSERT INTO users (id) VALUES (15); -- 需获取插入意向锁,但被事务A的间隙锁阻塞
4.4.3 插入意向锁的兼容性规则
当前锁类型 \ 请求锁类型 | 插入意向锁 | 间隙锁(Gap Lock) | 临键锁(Next-Key Lock) |
---|---|---|---|
插入意向锁 | ✅ 兼容 | ❌ 不兼容 | ❌ 不兼容 |
间隙锁(Gap Lock) | ❌ 不兼容 | ✅ 兼容 | ✅ 兼容 |
临键锁(Next-Key) | ❌ 不兼容 | ✅ 兼容 | ❌ 不兼容 |
- 关键规则 :
- 插入意向锁之间兼容:允许多个事务在同一间隙插入不同数据(如id=12和id=15)。
- 插入意向锁与间隙锁互斥:若间隙已被其他事务加间隙锁,插入操作需等待。
4.4.4 插入意向锁的底层实现
- 锁定目标:插入意向锁仅锁定目标插入点所在的间隙,而非整个范围。
- 冲突检测 :
- 若插入位置已被其他事务的插入意向锁占用(如唯一键冲突),则触发等待。
- 若插入位置未被占用,直接插入并释放锁。
示例:
sql
-- 表数据:id=10, 20(主键)
-- 事务A
BEGIN;
INSERT INTO users (id) VALUES (15); -- 对间隙(10,20)加插入意向锁
-- 事务B
BEGIN;
INSERT INTO users (id) VALUES (18); -- 同样对间隙(10,20)加插入意向锁,允许执行
4.4.5 插入意向锁如何避免死锁
- 场景:多个事务尝试插入同一间隙的不同位置。
- 机制 :
- 插入意向锁之间兼容,事务不会互相阻塞。
- 若插入位置相同(如唯一键冲突),后插入的事务会等待。
示例:
sql
-- 事务A和事务B同时插入不同位置
-- 事务A
BEGIN;
INSERT INTO users (id) VALUES (15); -- 成功
-- 事务B
BEGIN;
INSERT INTO users (id) VALUES (18); -- 成功
-- 结果:无死锁,两者均插入成功。
4.4.6 插入意向锁的监控与诊断
-
查看当前插入意向锁
sql-- 查看InnoDB锁信息(MySQL 5.7+) SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS WHERE LOCK_MODE = 'X,INSERT_INTENTION'; -- 插入意向锁模式 -- 输出示例: -- LOCK_ID: 1234:5:3:2 -- LOCK_TYPE: RECORD -- LOCK_MODE: X,INSERT_INTENTION -- LOCK_DATA: 15 -- 插入目标值
-
分析插入意向锁冲突
sql-- 查看被阻塞的插入操作 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
-
死锁日志分析
sqlSHOW ENGINE INNODB STATUS;
在日志的
LATEST DETECTED DEADLOCK
部分,可找到插入意向锁导致的死锁信息。
4.4.7 插入意向锁的示例场景
-
场景1:插入意向锁与间隙锁冲突
sql-- 事务A BEGIN; SELECT * FROM users WHERE id > 10 FOR UPDATE; -- 加临键锁(锁定(10, +∞)) -- 事务B BEGIN; INSERT INTO users (id) VALUES (15); -- 需要插入意向锁,被事务A阻塞
-
场景2:插入意向锁之间的兼容
sql-- 事务A BEGIN; INSERT INTO users (id) VALUES (15); -- 加插入意向锁(间隙(10,20)) -- 事务B BEGIN; INSERT INTO users (id) VALUES (18); -- 加插入意向锁(同一间隙,兼容)
4.4.8 总结
- 插入意向锁是间隙锁的优化:允许并发插入同一间隙的不同位置,提升写入性能。
- 冲突机制:与普通间隙锁互斥,但插入意向锁之间兼容。
- 优化核心:分散插入热点、减少事务时间、合理设计索引。
五,总结
5.1 全局锁(Global Lock)
- 锁定粒度:整个数据库实例。
- 核心作用:确保备份时数据一致性。
- 实现方式 :
FLUSH TABLES WITH READ LOCK
(FTWRL):阻塞所有写操作。- InnoDB引擎可使用
mysqldump --single-transaction
实现无锁热备份。
- 适用场景 :
- 全库逻辑备份(非事务引擎表需手动加锁)。
- 优缺点 :
- 优点:数据一致性高。
- 缺点:阻塞所有写操作,影响业务可用性。
5.2 表级锁(Table-Level Lock)
- 锁定粒度:整张表。
- 核心作用:简单高效地控制表级并发。
- 类型 :
- 表共享读锁(S Lock):允许多个读操作,阻塞所有写操作。
- 表独占写锁(X Lock):完全独占,阻塞所有读写操作。
- 元数据锁(MDL):隐式保护表结构变更(DDL操作)。
- 意向锁(IS/IX):协调行锁与表锁的共存。
- 适用场景 :
- MyISAM引擎默认使用(读多写少场景)。
- InnoDB在无索引的全表操作时退化为表锁。
- 优缺点 :
- 优点:开销小,加锁快,适合低并发场景。
- 缺点:并发性能差,锁冲突概率高。
5.3 行级锁(Row-Level Lock)
- 锁定粒度:单行或多行数据。
- 核心作用:实现高并发下的细粒度控制。
- 类型 :
- 记录锁(Record Lock):锁定单行。
- 间隙锁(Gap Lock):锁定索引记录间的间隙。
- 临键锁(Next-Key Lock):记录锁 + 间隙锁(InnoDB默认)。
- 插入意向锁(Insert Intention Lock):优化并发插入。
- 适用场景 :
- InnoDB引擎默认支持,适合高并发写入。
- 需精确控制数据访问的事务场景。
- 优缺点 :
- 优点:并发度高,锁冲突少。
- 缺点:开销大,可能引发死锁。