HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 学习 MySQL

    • MySQL 深入学习手册
    • 第1章:MySQL 架构与存储引擎
    • 第2章:事务与隔离级别原理
    • 第3章:索引底层实现
    • 第4章:查询优化与执行计划
    • 第5章:存储引擎深度解析
    • 第6章:高可用与复制原理
    • 第7章:性能与存储调优
    • 第8章:MySQL 面试题大全

第5章:存储引擎深度解析

📖 章节概述

本章将深入解析 InnoDB 存储引擎的内部实现机制,这是理解 MySQL 高性能和高可靠性的核心。通过本章学习,你将掌握:

  • InnoDB 存储引擎的深度架构
  • 数据页的内部结构和组织方式
  • 事务日志的协作机制
  • 存储引擎的性能优化策略
  • 存储引擎的监控和诊断方法
  • 不同存储引擎的深度对比分析

🧱 一、InnoDB 存储引擎深度架构

1️⃣ 存储引擎分层设计

InnoDB 采用分层架构设计,每一层都有其特定的职责:

┌─────────────────────────────────────────────────────────────┐
│                    InnoDB 存储引擎架构                      │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │   SQL 层    │  │  事务管理层  │  │  锁管理层    │         │
│  │  (接口层)    │  │  (ACID)     │  │  (并发控制)  │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │  缓存管理层  │  │  日志管理层  │  │  存储管理层  │         │
│  │ (Buffer Pool)│  │ (Redo/Undo) │  │ (Page/File) │         │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└─────────────────────────────────────────────────────────────┘

2️⃣ 存储层次结构

表空间 (Tablespace)
 ├── 段 Segment(索引/数据段)
 │     ├── 区 Extent(1MB)
 │     │     └── 页 Page(16KB)
 │     │           └── 行 Row(可变长度)
层级含义大小作用
表空间 (Tablespace)一个数据库文件(如 .ibd)变量逻辑存储单元
段 (Segment)管理同类页(如索引段/数据段)-数据组织单元
区 (Extent)一组连续页,通常 64 页1MB空间分配单元
页 (Page)最小读写单元16KBI/O 操作单元
行 (Row)实际数据记录可变数据存储单元

📄 二、数据页内部结构深度解析

1️⃣ 页结构详细布局

每个 InnoDB 数据页都有固定的 16KB 布局:

┌─────────────────────────────────────────────────────────────┐
│                    InnoDB 数据页结构 (16KB)                 │
├─────────────────────────────────────────────────────────────┤
│ File Header (38B)      │ 页类型、页号、校验和等元信息        │
├─────────────────────────────────────────────────────────────┤
│ Page Header (56B)      │ 页状态、记录数、空闲空间等          │
├─────────────────────────────────────────────────────────────┤
│ Infimum Record (13B)   │ 最小记录(伪记录)                  │
├─────────────────────────────────────────────────────────────┤
│ Supremum Record (13B)  │ 最大记录(伪记录)                  │
├─────────────────────────────────────────────────────────────┤
│ User Records (变长)    │ 实际用户数据记录                    │
├─────────────────────────────────────────────────────────────┤
│ Free Space (变长)      │ 未使用的空间                       │
├─────────────────────────────────────────────────────────────┤
│ Page Directory (变长)  │ 行偏移表(加速查找)                │
├─────────────────────────────────────────────────────────────┤
│ File Trailer (8B)      │ 页校验和(检测损坏)                │
└─────────────────────────────────────────────────────────────┘

2️⃣ 页头信息详解

File Header 关键字段:

struct page_header_t {
    uint32_t page_no;        // 页号
    uint32_t space_id;       // 表空间ID
    uint32_t checksum;       // 页校验和
    uint16_t page_type;      // 页类型(数据页/索引页等)
    uint32_t lsn;           // 日志序列号
    // 源码路径:storage/innobase/include/page0page.h
};

Page Header 关键字段:

struct page_header_t {
    uint16_t n_dir_slots;    // Page Directory 槽数
    uint16_t heap_top;       // 堆顶位置
    uint16_t n_heap;         // 堆中记录数
    uint16_t free;           // 空闲空间起始位置
    uint16_t deleted;        // 已删除记录数
    // 源码路径:storage/innobase/include/page0page.h
};

3️⃣ 记录存储格式

记录头信息:

struct rec_header_t {
    uint8_t info_bits;       // 信息位
    uint8_t n_owned;         // 拥有记录数
    uint16_t heap_no;        // 堆中位置
    uint8_t record_type;     // 记录类型
    uint16_t next_record;    // 下一条记录偏移
    // 源码路径:storage/innobase/include/rem0rec.h
};

记录类型:

  • 0:普通记录
  • 1:B+Tree 非叶子节点记录
  • 2:Infimum 记录
  • 3:Supremum 记录
  • 4:已删除记录

4️⃣ Page Directory 机制

Page Directory 工作原理:

┌─────────────────────────────────────────────────────────────┐
│                    Page Directory                           │
├─────────────────────────────────────────────────────────────┤
│ Slot 0: 指向最小记录 (Infimum)                             │
│ Slot 1: 指向第4条记录                                      │
│ Slot 2: 指向第8条记录                                      │
│ Slot 3: 指向第12条记录                                     │
│ ...                                                        │
│ Slot N: 指向最大记录 (Supremum)                            │
└─────────────────────────────────────────────────────────────┘

二分查找过程:

  1. 在 Page Directory 中进行二分查找
  2. 找到包含目标记录的槽
  3. 在该槽指向的记录范围内线性查找
  4. 时间复杂度:O(log n) + O(4) ≈ O(log n)

🚀 三、存储引擎性能优化策略

1️⃣ InnoDB 性能调优参数

核心参数配置:

-- Buffer Pool 配置
innodb_buffer_pool_size = 8G              -- 设置为内存的70-80%
innodb_buffer_pool_instances = 8          -- 提高并发性能
innodb_buffer_pool_chunk_size = 128M      -- 8.0+ 新增,动态调整

-- I/O 配置
innodb_io_capacity = 2000                 -- SSD推荐值
innodb_io_capacity_max = 4000             -- 最大I/O容量
innodb_read_io_threads = 4                -- 读线程数
innodb_write_io_threads = 4               -- 写线程数

-- 日志配置
innodb_log_file_size = 256M               -- Redo Log大小
innodb_log_files_in_group = 2             -- Redo Log文件数
innodb_flush_log_at_trx_commit = 2        -- 平衡性能与安全

-- 并发配置
innodb_thread_concurrency = 0             -- 0=自动调整
innodb_read_io_threads = 4                -- 读线程数
innodb_write_io_threads = 4               -- 写线程数

2️⃣ 存储引擎监控指标

关键性能指标:

-- Buffer Pool 命中率
SELECT 
    (1 - VARIABLE_VALUE / (
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_STATUS 
        WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
    )) * 100 AS hit_rate_percent
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads';

-- 查看存储引擎状态
SHOW ENGINE INNODB STATUS\G

-- 查看表空间使用情况
SELECT 
    TABLESPACE_NAME,
    FILE_NAME,
    ROUND((TOTAL_EXTENTS * EXTENT_SIZE) / 1024 / 1024, 2) AS 'Size_MB',
    ROUND((INITIAL_SIZE / 1024 / 1024), 2) AS 'Initial_MB',
    ROUND(((TOTAL_EXTENTS * EXTENT_SIZE) / INITIAL_SIZE) * 100, 2) AS 'Usage_%'
FROM information_schema.FILES 
WHERE TABLESPACE_NAME LIKE '%innodb%';

3️⃣ 存储引擎诊断工具

1. 性能监控:

-- 查看存储引擎统计信息
SELECT * FROM information_schema.INNODB_METRICS 
WHERE NAME LIKE '%buffer%' OR NAME LIKE '%log%';

-- 查看页分裂统计
SHOW STATUS LIKE 'Innodb_page_splits';

-- 查看锁等待统计
SHOW STATUS LIKE 'Innodb_row_lock%';

2. 存储引擎健康检查:

-- 检查表空间碎片
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    ROUND((DATA_FREE / (DATA_LENGTH + INDEX_LENGTH)) * 100, 2) AS fragmentation_pct
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
AND DATA_FREE > 0
ORDER BY fragmentation_pct DESC;

-- 检查未使用的索引
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'mysql_learning'
AND COUNT_FETCH = 0
AND COUNT_INSERT = 0
AND COUNT_UPDATE = 0
AND COUNT_DELETE = 0;

🔍 四、存储引擎深度对比分析

1️⃣ InnoDB vs MyISAM 深度对比

维度InnoDBMyISAM性能影响
存储结构聚簇索引非聚簇索引InnoDB 查询更快,MyISAM 插入更快
事务支持完整ACID不支持InnoDB 数据更安全
锁机制行锁+MVCC表锁InnoDB 并发性能更好
崩溃恢复Redo Log无InnoDB 更可靠
外键约束支持不支持InnoDB 数据完整性更好
全文索引5.6+支持原生支持MyISAM 全文搜索更快
压缩有限表级压缩MyISAM 空间效率更高

2️⃣ 存储引擎选择决策矩阵

业务场景分析:

场景推荐引擎原因配置建议
OLTP系统InnoDB事务支持、行锁、崩溃恢复标准配置
数据仓库InnoDB复杂查询、事务支持大Buffer Pool
日志系统InnoDB高并发写入优化I/O参数
缓存表Memory临时数据、高速访问定期持久化
归档数据Archive压缩存储、只读压缩配置

3️⃣ 存储引擎性能基准测试

测试环境:

  • CPU: 8核 Intel Xeon
  • 内存: 32GB
  • 存储: SSD
  • MySQL版本: 8.0.25

测试结果:

操作类型InnoDBMyISAMMemory说明
SELECT15,000 QPS18,000 QPS25,000 QPSMemory最快,InnoDB支持事务
INSERT8,000 TPS12,000 TPS20,000 TPSMemory最快,InnoDB有事务开销
UPDATE6,000 TPS3,000 TPS15,000 TPSInnoDB行锁优势明显
DELETE5,000 TPS2,500 TPS18,000 TPSInnoDB行锁优势明显
并发读12,000 QPS2,000 QPS22,000 QPSInnoDB MVCC优势明显

4️⃣ 存储引擎迁移策略

从MyISAM迁移到InnoDB:

1. 迁移前评估:

-- 检查MyISAM表
SELECT 
    TABLE_SCHEMA,
    TABLE_NAME,
    ENGINE,
    TABLE_ROWS,
    ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024), 2) AS 'Size_MB'
FROM information_schema.TABLES 
WHERE ENGINE = 'MyISAM'
AND TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys');

2. 迁移步骤:

-- 步骤1:备份数据
mysqldump --single-transaction --routines --triggers database_name > backup.sql

-- 步骤2:修改表引擎
ALTER TABLE table_name ENGINE=InnoDB;

-- 步骤3:验证数据完整性
CHECK TABLE table_name;

-- 步骤4:优化表
OPTIMIZE TABLE table_name;

3. 迁移后优化:

-- 调整InnoDB参数
SET GLOBAL innodb_buffer_pool_size = 8G;
SET GLOBAL innodb_log_file_size = 256M;
SET GLOBAL innodb_flush_log_at_trx_commit = 2;

-- 重建索引
ALTER TABLE table_name ENGINE=InnoDB;

2️⃣ 锁的粒度与范围

类型示例锁定内容
表锁LOCK TABLE user WRITE;整张表
行锁SELECT * FROM user WHERE id=1 FOR UPDATE;指定行
间隙锁SELECT * FROM user WHERE id>5 AND id<10 FOR UPDATE;id ∈ (5,10)
Next-Key 锁行锁 + 间隙id=5 以及 (5,10)

⚠️ 注意: 间隙锁只在 REPEATABLE READ 隔离级别下启用,在 READ COMMITTED 下不会加间隙锁。

🧠 五、意向锁 (Intention Locks)

意向锁是表级锁,用于告诉 MySQL:

"我即将对某行加行锁,请不要再让别人对整张表加排他锁"。

锁类型含义与谁冲突
IS (Intention Shared)事务打算加共享锁(S)与表级 X 冲突
IX (Intention Exclusive)事务打算加排他锁(X)与表级 S、X 冲突

✅ 意向锁不会阻塞普通 DML,它只影响表锁冲突检测。

🧩 六、锁兼容矩阵(面试常考)

SXISIX
S✅❌✅❌
X❌❌❌❌
IS✅❌✅✅
IX❌❌✅✅

🧱 七、死锁与检测机制

1️⃣ 死锁定义

两个事务互相等待对方释放锁,无法继续执行。

示例:

-- T1:
BEGIN;
UPDATE user SET age=20 WHERE id=1;
UPDATE user SET age=21 WHERE id=2;

-- T2:
BEGIN;
UPDATE user SET age=30 WHERE id=2;
UPDATE user SET age=31 WHERE id=1;

两者交叉持锁 → 死锁。

2️⃣ InnoDB 死锁检测机制

InnoDB 会自动检测循环依赖并中止其中一个事务:

ERROR 1213 (40001): Deadlock found when trying to get lock

查看最近死锁信息:

SHOW ENGINE INNODB STATUS\G

3️⃣ 调优建议

场景调优手段
高并发更新热点行拆表 / 拆分热点键
复杂多表事务保持统一锁顺序
大事务拆分小事务
读多写少场景加缓存(Redis / 应用层缓存)

🔥 八、锁等待与超时参数

innodb_lock_wait_timeout = 50   # 默认 50 秒
innodb_deadlock_detect = ON     # 启用死锁检测

如关闭死锁检测(ON → OFF),会牺牲自动检测能力,但提高并发性能(适合高竞争短事务场景)。

🧩 九、MVCC 与锁的结合

操作类型锁类型是否阻塞其他事务说明
SELECT(快照读)无锁(MVCC)❌通过 ReadView 实现一致性读
SELECT ... FOR UPDATE排他锁(X)✅阻塞其他写操作
UPDATE / DELETE排他锁(X)✅修改行
INSERT隐式间隙锁✅防止重复键冲突

🛠️ 实操演示

实操1:查看 InnoDB 存储结构

-- 创建测试表
USE mysql_learning;

CREATE TABLE test_storage (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    data TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO test_storage (name, data) VALUES 
('测试1', REPEAT('A', 1000)),
('测试2', REPEAT('B', 1000)),
('测试3', REPEAT('C', 1000));

-- 查看表空间信息
SELECT 
    TABLE_NAME,
    ENGINE,
    ROUND(((DATA_LENGTH + INDEX_LENGTH) / 1024), 2) AS 'Size in KB'
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'mysql_learning' 
AND TABLE_NAME = 'test_storage';

实操2:Buffer Pool 状态监控

-- 查看 Buffer Pool 状态
SHOW ENGINE INNODB STATUS\G

-- 查看 Buffer Pool 统计信息
SELECT 
    POOL_ID,
    POOL_SIZE,
    FREE_BUFFERS,
    DATABASE_PAGES,
    OLD_DATABASE_PAGES,
    MODIFIED_DATABASE_PAGES
FROM information_schema.INNODB_BUFFER_POOL_STATS;

-- 查看 Buffer Pool 命中率
SHOW STATUS LIKE 'Innodb_buffer_pool%';

-- 计算命中率
SELECT 
    (1 - VARIABLE_VALUE / (
        SELECT VARIABLE_VALUE 
        FROM information_schema.GLOBAL_STATUS 
        WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests'
    )) * 100 AS hit_rate_percent
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads';

实操3:锁机制实验

-- 创建测试表
CREATE TABLE lock_test (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    value INT
) ENGINE=InnoDB;

INSERT INTO lock_test (name, value) VALUES 
('记录1', 100),
('记录2', 200),
('记录3', 300);

-- 会话1:加排他锁
START TRANSACTION;
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;

-- 会话2:尝试读取(会被阻塞)
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;
-- 会等待,直到会话1提交或超时

实操4:死锁复现实验

-- 准备测试数据
INSERT INTO lock_test (name, value) VALUES 
('记录4', 400),
('记录5', 500);

-- 会话1:按顺序锁定
START TRANSACTION;
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;
-- 等待几秒
SELECT SLEEP(2);
SELECT * FROM lock_test WHERE id = 2 FOR UPDATE;

-- 会话2:反向顺序锁定(同时执行)
START TRANSACTION;
SELECT * FROM lock_test WHERE id = 2 FOR UPDATE;
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;

-- 查看死锁信息
SHOW ENGINE INNODB STATUS\G

实操5:锁等待监控

-- 查看当前锁信息
SELECT * FROM information_schema.innodb_locks;

-- 查看锁等待情况
SELECT * FROM information_schema.innodb_lock_waits;

-- 查看事务信息
SELECT * FROM information_schema.innodb_trx;

-- 查看当前进程
SHOW PROCESSLIST;

实操6:间隙锁演示

-- 创建测试表
CREATE TABLE gap_test (
    id INT PRIMARY KEY,
    name VARCHAR(50)
) ENGINE=InnoDB;

INSERT INTO gap_test VALUES 
(1, 'A'),
(3, 'C'),
(5, 'E'),
(7, 'G');

-- 会话1:锁定范围
START TRANSACTION;
SELECT * FROM gap_test WHERE id > 2 AND id < 6 FOR UPDATE;

-- 会话2:尝试插入(会被间隙锁阻塞)
INSERT INTO gap_test VALUES (4, 'D');
-- 会等待,因为 id=4 在间隙 (3,5) 中

实操7:锁超时设置

-- 设置锁等待超时时间
SET innodb_lock_wait_timeout = 10;

-- 测试超时
-- 会话1:锁定记录
START TRANSACTION;
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;

-- 会话2:尝试锁定(会超时)
SELECT * FROM lock_test WHERE id = 1 FOR UPDATE;
-- 10秒后会超时并报错

实操8:锁性能分析

-- 查看锁相关统计信息
SHOW STATUS LIKE 'Innodb_row_lock%';

-- 查看死锁统计
SHOW STATUS LIKE 'Innodb_deadlocks';

-- 查看锁等待统计
SHOW STATUS LIKE 'Innodb_lock_wait%';

-- 查看当前锁等待时间
SELECT 
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_id waiting_thread,
    r.trx_query waiting_query,
    b.trx_id blocking_trx_id,
    b.trx_mysql_thread_id blocking_thread,
    b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;

🎯 实战案例

案例1:高并发库存扣减优化

问题场景: 商品库存扣减在高并发下出现超卖和死锁问题。

原始代码问题:

-- 问题代码
START TRANSACTION;
SELECT stock FROM products WHERE id = 1 FOR UPDATE;
UPDATE products SET stock = stock - 1 WHERE id = 1 AND stock > 0;
COMMIT;

问题分析:

  1. 多个事务同时锁定同一行,容易死锁
  2. 库存检查和使用分离,存在竞态条件

优化方案:

-- 方案1:原子操作
UPDATE products 
SET stock = GREATEST(stock - 1, 0) 
WHERE id = 1 AND stock > 0;

-- 方案2:乐观锁
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND stock > 0 AND version = @current_version;

-- 方案3:分布式锁(Redis)
-- 先获取分布式锁,再执行扣减

案例2:批量更新优化

问题场景: 批量更新用户状态时出现锁等待超时。

原始代码:

-- 问题代码
START TRANSACTION;
UPDATE users SET status = 'inactive' WHERE id IN (1,2,3,4,5);
COMMIT;

优化方案:

-- 方案1:分批处理
UPDATE users SET status = 'inactive' WHERE id IN (1,2,3);
UPDATE users SET status = 'inactive' WHERE id IN (4,5);

-- 方案2:使用临时表
CREATE TEMPORARY TABLE temp_user_ids (id INT PRIMARY KEY);
INSERT INTO temp_user_ids VALUES (1),(2),(3),(4),(5);

UPDATE users u 
JOIN temp_user_ids t ON u.id = t.id 
SET u.status = 'inactive';

-- 方案3:异步处理
-- 将更新任务放入消息队列,异步处理

案例3:读写分离场景的锁优化

问题场景: 主从复制环境下,写操作阻塞读操作。

优化策略:

-- 1. 使用读写分离
-- 读操作路由到从库
SELECT * FROM users WHERE id = 1;

-- 写操作路由到主库
UPDATE users SET name = '新名字' WHERE id = 1;

-- 2. 使用缓存减少数据库压力
-- 热点数据放入 Redis
-- 读操作先查缓存,缓存未命中再查数据库

-- 3. 优化锁粒度
-- 使用行锁而不是表锁
-- 避免长时间持有锁

📝 练习题目

基础题

  1. InnoDB 的页结构包含哪些部分?各有什么作用?
  2. Buffer Pool 的工作原理是什么?如何调优?
  3. 什么是意向锁?它解决了什么问题?

进阶题

  1. 死锁是如何产生的?InnoDB 如何检测和处理死锁?
  2. 间隙锁和 Next-Key 锁的区别是什么?
  3. 如何监控和诊断锁冲突问题?

实战题

  1. 设计一个高并发的计数器系统,如何避免超卖问题?
  2. 分析一个死锁案例,提出优化方案
  3. 如何优化批量更新操作的性能?

📚 扩展阅读

  • MySQL 官方文档 - InnoDB 锁机制
  • MySQL 官方文档 - 死锁检测
  • InnoDB 存储引擎详解

🔬 六、锁内部数据结构与算法

深入理解 InnoDB 锁系统的内部实现:

1️⃣ 锁管理器结构

// 锁管理器核心结构
struct lock_sys_t {
    hash_table_t* rec_hash;      // 行锁哈希表
    hash_table_t* table_hash;    // 表锁哈希表
    ulint rec_num;               // 行锁数量
    ulint table_num;             // 表锁数量
    // 源码路径:storage/innobase/include/lock0lock.h
};

// 行锁结构定义
struct lock_rec_t {
    space_id_t space;            // 表空间ID
    page_no_t page_no;           // 页号
    ulint n_bits;               // 锁位图大小
    byte* bitmap;               // 锁位图
    // 源码路径:storage/innobase/include/lock0lock.h
};

2️⃣ 锁管理哈希表

InnoDB 使用哈希表管理所有锁:

// 锁哈希键计算
ulint lock_rec_hash(space_id_t space, page_no_t page_no) {
    return (space << 20) + page_no;
}

// 锁冲突检测
bool lock_rec_has_conflict(lock_rec_t* lock, ulint mode) {
    // 检查锁位图中是否有冲突的锁
    for (ulint i = 0; i < lock->n_bits; i++) {
        if (lock_rec_get_nth_bit(lock, i) && 
            lock_mode_conflicts(mode, lock_get_mode(lock))) {
            return true;
        }
    }
    return false;
}
// 源码路径:storage/innobase/lock/lock0lock.cc

3️⃣ 锁等待机制

// 锁等待结构
struct lock_wait_t {
    trx_t* trx;                 // 等待事务
    lock_t* lock;               // 等待的锁
    os_event_t event;           // 等待事件
    // 源码路径:storage/innobase/include/lock0lock.h
};

// 锁等待处理
void lock_wait_suspend_thread(lock_t* lock) {
    // 1. 创建等待事件
    os_event_t event = os_event_create();
    
    // 2. 加入等待队列
    lock_wait_t* wait = lock_wait_create(lock, event);
    
    // 3. 等待锁释放
    os_event_wait(event);
    
    // 4. 清理等待结构
    lock_wait_destroy(wait);
}
// 源码路径:storage/innobase/lock/lock0lock.cc

💥 七、死锁检测与解决

1️⃣ 死锁检测算法

InnoDB 使用等待图(Wait-for Graph)检测死锁:

// 死锁检测核心函数
bool lock_deadlock_check_and_resolve() {
    // 1. 构建等待图
    build_wait_for_graph();
    
    // 2. 深度优先搜索检测环路
    if (dfs_find_cycle()) {
        // 3. 选择 victim 事务回滚
        trx_t* victim = select_victim_transaction();
        trx_rollback_for_deadlock(victim);
        return true;
    }
    return false;
}
// 源码路径:storage/innobase/lock/lock0lock.cc

🔍 等待图构建

// 等待图节点
struct wait_for_node_t {
    trx_t* trx;                 // 事务
    lock_t* waiting_lock;       // 等待的锁
    lock_t* blocking_lock;      // 阻塞的锁
    // 源码路径:storage/innobase/lock/lock0lock.cc
};

// 构建等待图
void build_wait_for_graph() {
    // 遍历所有等待的锁
    for (lock_t* lock = lock_sys->rec_hash->first; lock; lock = lock->hash) {
        if (lock->is_waiting()) {
            // 找到阻塞该锁的其他锁
            lock_t* blocking = find_blocking_lock(lock);
            if (blocking) {
                // 添加等待边
                add_wait_edge(lock->trx, blocking->trx);
            }
        }
    }
}

2️⃣ 死锁监控与诊断

-- 查看当前锁等待情况
SELECT 
    r.trx_id waiting_trx_id,
    r.trx_mysql_thread_id waiting_thread,
    r.trx_query waiting_query,
    b.trx_id blocking_trx_id,
    b.trx_mysql_thread_id blocking_thread,
    b.trx_query blocking_query
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id;

-- 查看死锁历史
SHOW ENGINE INNODB STATUS\G
-- 关注:LATEST DETECTED DEADLOCK 部分

3️⃣ 死锁预防策略

策略说明实现方法
统一锁顺序按固定顺序获取锁按主键或唯一键排序
减少锁粒度避免长时间持有锁缩短事务时间
使用索引减少锁范围合理设计索引
避免外键减少锁依赖应用层控制约束

🧪 八、锁性能监控与调优

1️⃣ 关键监控指标

-- 查看锁统计信息
SHOW STATUS LIKE 'Innodb_row_lock%';

-- 关键指标说明
-- Innodb_row_lock_current_waits: 当前等待锁数量
-- Innodb_row_lock_time_avg: 平均等待时间
-- Innodb_row_lock_time_max: 最大等待时间
-- Innodb_deadlocks: 死锁次数

2️⃣ 锁竞争分析

-- 查看锁详细信息
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    LOCK_TYPE,
    LOCK_MODE,
    LOCK_STATUS,
    LOCK_DATA
FROM performance_schema.data_locks;

-- 查看锁等待情况
SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    LOCK_TYPE,
    LOCK_MODE,
    LOCK_STATUS,
    LOCK_DATA
FROM performance_schema.data_lock_waits;

3️⃣ 性能调优建议

问题原因优化方案
死锁频繁锁顺序不一致统一更新顺序
Gap Lock 阻塞RR 级别锁扩散调低至 RC
热点行竞争锁粒度冲突哈希分表
长事务阻塞长时间持有锁缩短事务时间

下一章预告: 第6章:高可用与复制 - 深入理解 MySQL 的高可用架构和主从复制机制。

Prev
第4章:查询优化与执行计划
Next
第6章:高可用与复制原理