十二,InnoDB引擎底层原理

一,逻辑存储结构

逻辑存储结构图

  1. 表空间

    表空间文件在Linux下存放在 /var/lib/mysql文件中的xxx.ibd文件就是表空间文件,表空间文件用来存储,记录,索引等数据。

  2. 段分为,数据段(Leaf node segment)索引段(Non-leaf node segment)回滚段(Rollbacksegment) ,InnoDB是索引组织表,数据段就是B+树的叶子节点,索引段就是非叶子节点,段用来管理Extend(区)。一个段相当于一张表

  3. 区是表空间的单元结构,每个区大小为1M,默认情况下InnoDB存储引擎页大小为16k,一个区一共16个连续的页。

    • 页是InnoDB存储引擎磁盘管理的最小单元。

    • 每个区默认16KB,为了保证页的连续性,InnoDB存储引擎每次从磁盘申请4到5个区。

  4. 行指的是InnoDB存储的数据,表结构中俩个隐藏字段

    • Trx_id:最后一次操作事务的id

    • Roll pointer:指针,指向增删改之前的数据,可以拿这个找到修改之前的数据。

二,架构

MySQL5.5版本后,默认使用InoDB存储引擎,它擅长事务处理,具有崩溃恢复性特性!

下图为InnoDB架构图,左边为内存结构,右边为磁盘结构。

2.1 内存结构

2.1.1 Buffer Pool(缓冲池)

缓冲池是主内存的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作的时候,先操作缓存池中的数据(如果没有,从磁盘加载并且缓存),然后以一定频率刷新到磁盘,从而减少磁盘IO,加快处理速度

在缓存池中有一块一块的,这个是页,缓存池以页为单位,底层采用链表数据结构管理Page。

根据状态将Page分为三类

  1. free page :空闲页,未被使用的页。
  2. clean page :被使用的页,数据没有被修改过。
  3. dirty page :脏页,被使用的页,页中数据和磁盘中数据不一致。

Buffer Pool 的核心作用

  1. 加速数据读取
    • 首次读取数据时,从磁盘加载到 Buffer Pool;后续查询直接访问内存,避免磁盘 I/O。
  2. 优化数据写入
    • 数据修改(如 INSERT/UPDATE/DELETE)先在 Buffer Pool 中更新,形成 脏页(Dirty Page),再异步刷回磁盘。
  3. 降低响应延迟
    • 内存访问速度远高于磁盘,Buffer Pool 是数据库高性能的基石。

Buffer Pool 的工作原理

1. 数据读取流程

  • 命中缓存(Page Hit):查询数据时,若目标页已在 Buffer Pool 中,直接返回内存数据。
  • 未命中缓存(Page Miss) :若数据不在 Buffer Pool,从磁盘加载数据页到内存,并替换掉最近最少使用的页(通过 LRU 算法)。

2. 数据写入流程

  • 脏页机制 :修改操作先在 Buffer Pool 中更新数据页,标记为"脏页",由后台线程(Checkpoint)异步刷盘。
  • 写合并优化:多次修改同一页时,仅需一次磁盘写入。

3. 内存管理策略

  • LRU 算法改进
    • 将 Buffer Pool 分为 Young区(热点数据)Old区(新加载数据),避免冷数据冲刷热点数据。
    • 参数 innodb_old_blocks_time 控制新页在 Old 区的最小停留时间(默认 1秒),防止短期大扫描污染缓存。

Buffer Pool 的关键配置

  1. innodb_buffer_pool_size
    • Buffer Pool 的总大小,建议设置为物理内存的 50%~80%(如 64GB 内存的机器可设为 48GB)。
  2. innodb_buffer_pool_instances
    • 将 Buffer Pool 拆分为多个实例(建议等于 CPU 核心数),减少并发访问冲突。
  3. innodb_flush_method
    • 控制脏页刷盘方式(如 O_DIRECT 绕过操作系统缓存,减少双重缓存开销)。

Buffer Pool 的监控与调优

1. 查看 Buffer Pool 状态

sql 复制代码
SHOW ENGINE INNODB STATUS;  
-- 在输出中找到 "BUFFER POOL AND MEMORY" 部分
  • 关键指标
    • Database pages:已缓存的数据页数量。
    • Free pages:空闲页数量(若长期为 0,可能需扩容 Buffer Pool)。
    • Hit rate:缓存命中率(理想值 > 99%)。

2. 预热 Buffer Pool

重启数据库后,主动加载热点数据到内存:

sql 复制代码
SELECT * FROM 热点表;  -- 全表扫描或核心查询

3. 优化建议

  • 避免过小的 Buffer Pool:频繁的缓存未命中会导致性能骤降。
  • 合理分配多实例:高并发场景减少锁竞争。
  • 监控命中率:若命中率低于 95%,需考虑扩容。

2.1.2 Change Buffer(更改缓存区)

Change Buffer 是 InnoDB 存储引擎的核心优化机制,用于缓存 非唯一索引 的变更操作(如 INSERTUPDATEDELETE),减少随机磁盘 I/O,提升写入性能。

  • 核心思想:延迟非唯一索引的更新,合并多次操作为单次磁盘写入。
  • 归属:Change Buffer 是 Buffer Pool 的一部分,占用 Buffer Pool 的内存空间。

Change Buffer 的核心作用

  1. 减少随机 I/O
    • 非唯一索引的变更操作(如插入新记录)不需要立即写入磁盘,而是暂存在 Change Buffer 中。
    • 后续通过后台线程合并(Merge)到磁盘,将多次随机写合并为顺序写。
  2. 提升并发写入性能
    • 减少索引更新时的磁盘竞争,尤其在高并发写入场景下效果显著。

Change Buffer 的工作原理

1. 写入流程

  • 非唯一索引的变更 : 当执行 INSERTUPDATEDELETE 时,若目标页不在 Buffer Pool 中,则将变更操作记录到 Change Buffer。
  • 合并(Merge): 当后续查询需要访问该索引页时,将 Change Buffer 中的变更合并到磁盘上的索引页。

2. 示例场景

sql 复制代码
-- 表结构:id(主键), name(非唯一索引)
-- 事务A插入一条数据
INSERT INTO users (id, name) VALUES (100, 'Alice');

-- 若name索引页不在Buffer Pool中:
-- 1. 将 `name='Alice'` 的插入操作记录到Change Buffer。
-- 2. 主键索引页(id=100)直接更新到Buffer Pool。
-- 3. 当后续查询需要访问name索引页时,合并Change Buffer中的操作到磁盘。

Change Buffer 的适用场景

  1. 非唯一索引的写入
    • 主键和唯一索引的更新需要立即检查唯一性,无法使用 Change Buffer。
  2. 写多读少的业务
    • 适合日志表、流水表等高写入负载的场景。
  3. 索引页不在内存中
    • 若索引页已在 Buffer Pool 中,直接更新,无需通过 Change Buffer。

Change Buffer 的配置与监控

1. 核心参数

  • innodb_change_buffer_max_size : Change Buffer 占 Buffer Pool 的最大比例(默认 25%),可调整范围为 0~50%。
  • innodb_change_buffering : 控制哪些操作启用 Change Buffer(默认 all):
    • none:禁用。
    • inserts:仅缓存插入操作。
    • deletes:仅缓存删除操作。
    • purges:仅缓存物理删除(页合并操作)。

2. 监控 Change Buffer

通过 SHOW ENGINE INNODB STATUS 查看状态:

sql 复制代码
-- 在输出中找到 "INSERT BUFFER AND ADAPTIVE HASH INDEX" 部分
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 3, free list len 10, seg size 13, 
0 merges, 0 merges merged operations
0 inserts, 0 merged recs, 0 deletes
  • 关键指标
    • merges:合并操作次数(越高说明 Change Buffer 优化效果越明显)。
    • inserts:缓存的插入操作次数。

Change Buffer 的优化建议

  1. 合理设置 innodb_change_buffer_max_size
    • 对于写密集型业务,可适当增大比例(如 30%~40%)。
    • 若业务以读为主,可降低比例或禁用。
  2. 避免频繁的唯一索引更新
    • 唯一索引无法利用 Change Buffer,尽量减少此类索引的数量。
  3. 监控合并效率
    • merges 值长期为 0,可能表明 Change Buffer 未生效(如索引页常驻内存)。

Change Buffer 的局限性

  1. 不适用于唯一索引
    • 唯一索引需要立即检查唯一性约束,无法延迟更新。
  2. 内存占用
    • Change Buffer 占用 Buffer Pool 空间,过大的设置可能挤占数据页缓存。
  3. 合并延迟风险
    • 极端情况下,大量未合并的变更可能导致查询性能波动。

总结

  • Change Buffer 是写入性能的"加速器":通过延迟非唯一索引更新,减少随机 I/O。
  • 适用场景:非唯一索引、高并发写入、索引页不常驻内存。
  • 调优核心:合理配置内存比例,监控合并效率,避免过度依赖。

2.1.3 Log Buffer(日志缓存区)

Log Buffer 是 InnoDB 存储引擎的内存缓冲区,用于临时存储 重做日志(Redo Log),通过批量写入磁盘的方式减少事务提交时的 I/O 开销,从而提升数据库性能。

  • 核心作用:优化事务提交的延迟,平衡性能与数据持久性。
  • 数据流向:事务产生的 Redo Log → Log Buffer → Redo Log Files(磁盘)。

Log Buffer 的核心功能

  1. 减少磁盘 I/O 次数
    • 事务提交时,Redo Log 先写入 Log Buffer,再按策略批量刷盘,避免每次提交都直接写磁盘。
  2. 支持高并发写入
    • 多个事务的 Redo Log 在内存中合并写入,缓解磁盘争用问题。
  3. 数据持久性保障
    • 通过配置参数(如 innodb_flush_log_at_trx_commit)控制日志刷盘策略,确保崩溃恢复能力。

Log Buffer 的工作机制

1. 写入流程

  • 事务提交时
    1. Redo Log 生成并写入 Log Buffer。
    2. 根据 innodb_flush_log_at_trx_commit 参数决定何时将 Log Buffer 刷入 Redo Log Files。
  • 后台线程刷盘
    InnoDB 的 Master Thread 每隔 1 秒(默认)自动将 Log Buffer 中的日志刷盘。

2. 刷盘策略(关键参数)

  • innodb_flush_log_at_trx_commit

    行为 数据安全性 性能
    0 日志每秒刷盘一次,事务提交时不强制刷盘。 最低 最高
    1 每次事务提交时立即刷盘(默认值)。 最高 最低
    2 日志写入操作系统缓存,由操作系统决定刷盘时机(崩溃可能丢失最近 1s 日志)。 中等 中等
  • 建议场景

    • 金融级数据安全 :设为 1(如订单、支付系统)。
    • 高并发写入 :设为 2(如日志采集、监控数据),但需接受崩溃时少量数据丢失风险。
    • 测试环境 :设为 0,牺牲安全性换取最大吞吐量。

3. Log Buffer 大小配置

  • 参数 innodb_log_buffer_size
    • 默认值:16MB(MySQL 8.0)。
    • 调整依据:
      • 若事务较大或高并发写入,可增大至 64MB~256MB。
      • 监控 SHOW GLOBAL STATUS LIKE 'Innodb_log_waits',若 Innodb_log_waits > 0,表明 Log Buffer 不足,需扩容。

Log Buffer 的监控与优化

1. 查看 Log Buffer 状态

sql 复制代码
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';  
-- 若 Innodb_log_waits > 0,表示事务因 Log Buffer 空间不足而等待刷盘。

2. 优化建议

  1. 合理设置 Buffer 大小
    • 根据业务负载调整 innodb_log_buffer_size,避免频繁等待刷盘。
  2. 权衡安全性与性能
    • 通过 innodb_flush_log_at_trx_commit 控制数据持久化级别。
  3. 使用高性能存储
    • Redo Log Files 应放在高速磁盘(如 SSD)上,减少刷盘延迟。

3. 性能对比示例

  • 场景 1innodb_flush_log_at_trx_commit=1
    • 每次提交事务都刷盘,TPS 较低,但崩溃后数据零丢失。
  • 场景 2innodb_flush_log_at_trx_commit=2
    • 日志写入操作系统缓存,TPS 较高,但崩溃可能丢失约 1s 的数据。

Log Buffer 与其他组件的协同

  • Redo Log Files : Log Buffer 最终将日志写入磁盘上的 Redo Log Files(ib_logfile0ib_logfile1)。
  • Checkpoint 机制: 定期将 Buffer Pool 中的脏页刷盘,并推进 Redo Log 的检查点,减少崩溃恢复时间。

总结

  • Log Buffer 是事务性能的关键组件:通过内存缓冲 Redo Log,减少高频磁盘写入。
  • 配置核心
    • innodb_flush_log_at_trx_commit:平衡数据安全与性能。
    • innodb_log_buffer_size:避免事务等待刷盘。
  • 监控重点Innodb_log_waits 指标和日志写入延迟。

2.1.4 Adaaptive Hash index(自适应哈希索引)

自适应哈希索引(AHI) 是 InnoDB 自动为高频访问的索引页构建的 内存哈希索引 ,用于加速 等值查询 (如 WHERE id=100)。

  • 本质 :在 B+ 树索引基础上构建哈希索引,将查询复杂度从 O(log n) 降低至 O(1)
  • 目标:优化热点数据的查询性能,减少 B+ 树的层级遍历开销。

AHI 的工作原理

1. 触发条件

  • 高频等值查询 :某索引的特定值被频繁访问(如主键查询 WHERE user_id=500)。
  • 索引页常驻内存:目标索引页需在 Buffer Pool 中被多次访问。

2. 自动构建过程

  1. InnoDB 监控索引的访问模式,识别高频查询的键值。
  2. 在内存中为这些键值构建哈希索引(键值 → 数据页位置)。
  3. 后续查询直接通过哈希表定位数据,跳过 B+ 树遍历。

3. 示例

sql 复制代码
-- 高频查询触发 AHI 构建
SELECT * FROM users WHERE id=100; -- 首次查询需遍历 B+ 树
SELECT * FROM users WHERE id=100; -- 第二次查询通过 AHI 直接定位

AHI 的适用场景

  1. 等值查询(Point Query)
    • 主键或唯一索引的精确匹配查询(如 WHERE order_id=12345)。
  2. 热点数据访问
    • 少数行被频繁访问(如用户表的热门账号、商品表的爆款 SKU)。
  3. 读多写少的业务
    • AHI 在写入时需维护哈希表,高并发写入可能降低性能。

AHI 的配置与监控

1. 启用/禁用 AHI

  • 参数 innodb_adaptive_hash_index
    • ON:默认启用。
    • OFF:禁用(某些高并发写入场景建议关闭)。

2. 监控 AHI 状态

通过 SHOW ENGINE INNODB STATUS 查看哈希索引使用情况:

sql 复制代码
-- 在输出中找到 "INSERT BUFFER AND ADAPTIVE HASH INDEX" 部分
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Hash table size 553193, node heap has 17 buffer(s)
Hash table size 553193, node heap has 15 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
  • 关键指标
    • hash searches/s:每秒哈希索引命中次数。
    • non-hash searches/s:每秒未走哈希索引的查询次数。

3. 优化建议

  • 高并发写入场景 :若 AHI 导致锁竞争(如 RW-latch 争用),可考虑禁用。
  • 内存充足时:保持默认开启,加速热点查询。

AHI 的优缺点

优点 缺点
显著加速等值查询(O(1)复杂度)。 仅支持等值查询,不适用于范围查询。
自动管理,无需手动维护。 占用额外内存(哈希表存储开销)。
对热点数据优化效果明显。 高并发写入时可能引发锁竞争。

AHI 与其他索引的关系

  • B+ 树索引:AHI 基于 B+ 树索引构建,是其性能的补充而非替代。
  • 覆盖索引:若查询可通过覆盖索引完成,AHI 可能不会被触发。
  • 唯一索引 vs 非唯一索引:AHI 对唯一索引的优化效果更显著。

2.2 磁盘结构

2.2.1 System Tablespace(系统表空间)

文件ibdata1(默认名,可配置多个文件)。

存储内容

  • 数据字典:表结构、索引等元数据。
  • 双写缓冲(Double write Buffer):防止页断裂(Partial Page Writes),确保数据页写入的原子性。
  • Undo Log(可选):在 MySQL 8.0 之前,Undo Log 默认存储在系统表空间;8.0+ 后默认独立为 Undo 表空间。

配置参数

  • innodb_data_file_path:定义系统表空间文件路径和大小。

2.2.2 File-Per-Table Tablespaces

文件 :每个 InnoDB 表对应一个 .ibd 文件。

优势

  • 空间回收:删除表时直接释放磁盘空间。
  • 备份灵活 :支持单表备份(如 Transportable Tablespaces)。

配置

  • innodb_file_per_table=ON(默认开启)。

2.2.3 General Tablespaces(通用表空间)

定义 :通用表空间是由用户 手动创建 的表空间,可将 多个表 存储在同一物理文件中(或自定义路径),提供更灵活的存储管理。

核心作用

  • 集中存储:将多个表的物理数据合并到一个文件中,便于管理。
  • 性能优化:通过将表分配到不同的磁盘(如 SSD 和 HDD 混合部署),优化 I/O 性能。
  • 兼容性:支持所有存储引擎(如 InnoDB、MyISAM)。

创建通用表空间

sql 复制代码
-- 创建通用表空间(数据文件路径需绝对路径)
CREATE TABLESPACE `general_ts` 
ADD DATAFILE '/var/lib/mysql/general_ts.ibd' 
ENGINE=InnoDB;

将表绑定到通用表空间

sql 复制代码
-- 创建新表时指定表空间
CREATE TABLE t1 (id INT) TABLESPACE `general_ts`;

-- 修改现有表的表空间
ALTER TABLE t2 TABLESPACE `general_ts`;

删除通用表空间

sql 复制代码
-- 需先移除表空间内的所有表
ALTER TABLE t1 TABLESPACE `innodb_file_per_table`;
ALTER TABLE t2 TABLESPACE `innodb_file_per_table`;
DROP TABLESPACE `general_ts`;

通用表空间的适用场景

  1. 批量冷数据存储: 将不常访问的历史数据表集中存储到低速磁盘(如 HDD)。
  2. 跨表空间管理 : 按业务模块划分表空间(如 finance_tslog_ts),便于备份和迁移。
  3. 特定性能优化: 将高并发表分散到不同磁盘的通用表空间,减少 I/O 竞争。

注意事项

  • 空间回收 :删除通用表空间中的表时,需手动释放空间(ALTER TABLE ... TABLESPACE)。
  • 兼容性限制
    • 通用表空间中的表不支持 TABLESPACE=innodb_file_per_table 以外的表空间转换。
    • 某些操作(如 TRUNCATE TABLE)可能导致空间无法立即回收。

2.2.4 Undo Tablespaces(撤销表空间)

MySQL中的 Undo表空间(Undo Tablespaces) 是InnoDB存储引擎用于管理事务回滚(Undo)日志的核心组件,支持事务的原子性和多版本并发控制(MVCC)

核心作用

  • 事务回滚:存储事务修改前的旧数据版本,确保事务失败时可回滚到之前状态。
  • MVCC支持:为并发事务提供数据的历史版本,实现非阻塞读。
  • 崩溃恢复:在数据库重启时,通过Undo日志修复未完成的事务。

架构演进

  • 系统表空间(早期) :Undo日志最初存储在共享的ibdata1文件中,易导致文件过大和管理不便。
  • 独立Undo表空间(MySQL 5.6+) :Undo日志从系统表空间分离,成为独立的.ibu文件(如undo_001.ibu),默认创建2个,最多支持127个。

关键配置参数

  • innodb_undo_tablespaces:设置Undo表空间数量(静态参数,需重启生效)。
  • innodb_undo_directory:指定Undo文件存储路径(默认在数据目录)。
  • innodb_rollback_segments:每个Undo表空间包含的回滚段数量(默认128个),支持更高事务并发。
  • innodb_undo_log_truncate :启用自动截断(默认ON),结合innodb_purge_rseg_truncate_frequency控制清理频率。

性能优化与调优

  • 数量调整:更多Undo表空间可减少回滚段争用,提升高并发下的性能(建议根据事务负载调整)。
  • 空间回收 :启用innodb_undo_log_truncate后,空闲的Undo表空间会自动收缩(需innodb_undo_logs≥35)。
  • 长事务风险 :长时间未提交的事务会阻碍Undo日志清理,需监控LONG_TRX并优化事务设计。

监控与管理

  • 信息查询 :通过INFORMATION_SCHEMA.INNODB_TABLESPACES查看Undo表空间状态。
  • 手动截断ALTER UNDO TABLESPACE tablespace_name SET INACTIVE;标记为可清理状态。
  • 磁盘空间:监控Undo文件大小,避免因MVCC或长事务导致空间膨胀。

2.2.5 Temporary Tablespaces(临时表空间)

MySQL中的临时表空间(Temporary Tablespaces) 是InnoDB存储引擎用于管理临时数据的专用存储区域,主要服务于查询执行过程中产生的临时表、排序操作、UNION操作等需要临时存储的场景。

核心作用

  • 存储临时表 :存放用户显式创建的临时表(CREATE TEMPORARY TABLE)或隐式生成的内部临时表(如排序、GROUP BYDISTINCT操作)。
  • 支持排序和中间结果 :处理复杂查询时,若内存不足(超过tmp_table_sizemax_heap_table_size),将中间结果写入临时表空间。
  • 会话隔离:每个会话可能使用独立的临时表空间,避免并发操作的资源竞争。

临时表空间的类型

  • 全局临时表空间(Global Temporary Tablespace)
    • 默认临时表空间 :MySQL 5.7+后引入,默认文件名为ibtmp1,存储所有非压缩的临时表数据。
    • 共享性:所有会话共享此表空间,但每个临时表的数据相互隔离。
  • 独立会话临时表空间(Session Temporary Tablespaces)
    • MySQL 8.0+特性 :每个会话可能创建独立的临时表空间文件(如temp_{N}.ibt),避免全局表空间的争用。
    • 自动清理:会话结束后自动删除对应的临时文件。

文件存储

  • 默认路径 :位于MySQL数据目录下(如/var/lib/mysql/ibtmp1)。
  • 动态扩展 :初始大小由innodb_temp_data_file_path定义(默认ibtmp1:12M:autoextend),随数据增长自动扩展,但不会自动收缩。

关键配置参数

  • innodb_temp_data_file_path :定义全局临时表空间的文件名、初始大小和扩展策略(如ibtmp1:64M:autoextend:max:20G)。
  • internal_tmp_mem_storage_engine :控制内存临时表的引擎(默认MEMORY),超过内存限制时转为InnoDB临时表。
  • tmp_table_size:内存临时表的最大容量阈值(默认16MB),超出后转为磁盘临时表。
  • innodb_temp_tablespaces_dir(MySQL 8.0+):指定独立会话临时表空间的存储目录。

查看临时表空间使用

  • 全局临时表空间

    sql 复制代码
    -- 查看当前大小和扩展上限
    SELECT FILE_NAME, TABLESPACE_NAME, ENGINE, INITIAL_SIZE, MAXIMUM_SIZE 
    FROM INFORMATION_SCHEMA.FILES 
    WHERE TABLESPACE_NAME = 'innodb_temporary';
  • 会话临时表空间(MySQL 8.0+):

    sql 复制代码
    SELECT * FROM INFORMATION_SCHEMA.INNODB_SESSION_TEMP_TABLESPACES;

空间回收

  • 全局临时表空间
    • 重启MySQLibtmp1文件会在重启时重置为初始大小(需谨慎操作)。
    • 限制最大大小 :通过innodb_temp_data_file_path设置max值(如max:20G)。
  • 会话临时表空间:会话结束后自动删除对应文件。

2.2.6 Doublewrite Buffer Files(双写缓冲区)

MySQL中的双写缓冲区(Doublewrite Buffer) 是InnoDB存储引擎用于确保数据页写入的完整性和崩溃恢复安全性的核心机制,主要解决"部分页写入(Partial Page Write)"问题

核心作用

  • 防止部分页写入: 在发生系统崩溃或电源故障时,磁盘上的数据页可能因写入不完整而损坏。双写缓冲区通过两次写入(先写缓冲区,再写实际数据页)确保数据页的原子性。
  • 崩溃恢复: 重启时,InnoDB通过双写缓冲区中的副本修复损坏的数据页,保证数据一致性。
  • 支持异步写入: 允许InnoDB的脏页刷新(Flush)操作异步执行,同时不牺牲数据可靠性。

写入流程

  1. 脏页准备写入磁盘: InnoDB的缓冲池(Buffer Pool)中的脏页(修改过的页)需要刷新到磁盘。
  2. 先写双写缓冲区: 脏页首先被顺序写入双写缓冲区(连续存储区域,减少随机I/O开销)。
  3. 再写实际数据文件: 双写缓冲区写入完成后,再将脏页写入表空间的实际数据文件。
  4. 原子性保证: 若在步骤3中发生崩溃,重启时通过双写缓冲区中的完整副本来修复数据文件。

恢复流程

  • 崩溃后,InnoDB检查数据页的校验和(Checksum)。
  • 若数据页损坏,则从双写缓冲区中读取完整副本覆盖损坏页。

架构设计

  • 存储位置 : 默认位于系统表空间(ibdata1)中,MySQL 8.0.20+后支持独立文件(#ib_16384_0.dblwr),提升灵活性和性能。
  • 结构特性
    • 双写缓冲区按顺序写入,减少随机I/O。
    • 每个页写入双写缓冲区时占用连续的128个页(每个页16KB,共2MB)。
  • 独立双写文件(MySQL 8.0.20+)
    • 通过innodb_doublewrite_dirinnodb_doublewrite_files配置路径和文件数量。
    • 支持更高效的空间管理和存储分离。

关键配置参数

  • innodb_doublewrite : 启用或禁用双写缓冲区(默认ON)。若存储设备支持原子写(如某些SSD),可考虑关闭以提升性能。
  • innodb_doublewrite_dir(MySQL 8.0.20+): 指定独立双写文件的存储目录。
  • innodb_doublewrite_files (MySQL 8.0.20+): 设置独立双写文件的数量(默认2)。

性能开销

  • 额外I/O操作:每个脏页需要写入两次(双写缓冲区 + 实际数据文件),理论上可能增加约5%~10%的I/O负载。
  • 顺序写入优化:双写缓冲区的顺序写入特性可部分抵消随机I/O的开销。

优化建议

  • 启用原子写的存储设备 : 若存储硬件(如支持FUA的SSD)或文件系统(如ZFS)保证单页写入的原子性,可关闭双写缓冲区:

    sql 复制代码
    SET GLOBAL innodb_doublewrite = OFF;
  • 独立存储路径(MySQL 8.0.20+): 将双写文件与数据文件分离到不同磁盘,减少I/O争用。

  • 监控写入负载 : 通过SHOW STATUS LIKE 'Innodb_dblwr%'观察双写缓冲区的使用频率:

    • Innodb_dblwr_pages_written:已写入双写缓冲区的总页数。
    • Innodb_dblwr_writes:双写缓冲区的写入次数。

查看状态

sql 复制代码
-- 查看双写缓冲区的写入统计
SHOW STATUS LIKE 'Innodb_dblwr%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Innodb_dblwr_pages_written | 5000  |  -- 写入双写缓冲区的总页数
| Innodb_dblwr_writes        | 200   |  -- 双写缓冲区的写入次数
+----------------------------+-------+

检查配置

sql 复制代码
-- 确认双写缓冲区是否启用
SHOW VARIABLES LIKE 'innodb_doublewrite';
+---------------------+-------+
| Variable_name       | Value |
+---------------------+-------+
| innodb_doublewrite  | ON    |
+---------------------+-------+

2.2.7 Redo Log(重做日志)

MySQL中的Redo Log(重做日志) 是InnoDB存储引擎实现事务持久性(ACID中的Durability)的核心机制,用于在数据库崩溃或意外宕机时恢复未完全写入数据文件的事务操作。

  • 崩溃恢复:记录事务对数据页的物理修改,确保未提交的事务修改在重启后能重放(Redo)。
  • 持久性保障:事务提交前,其修改必须写入Redo Log(遵循WAL原则:Write-Ahead Logging)。
  • 异步刷新优化:允许InnoDB将脏页(修改后的数据页)延迟写入磁盘,通过Redo Log保证数据不丢失。

Redo Log的组成

  • 日志文件组(Redo Log Files) : 默认由两个文件(ib_logfile0ib_logfile1)组成的循环队列,文件数量和大小可通过参数配置。
  • 日志缓冲区(Log Buffer): 内存中的缓冲区(默认16MB),事务修改首先写入此处,再按策略刷入磁盘日志文件。
  • LSN(Log Sequence Number): 唯一标识日志位置的序列号,用于跟踪写入和恢复进度。

写入流程

  1. 事务修改缓冲池:事务修改数据页时,先在内存的Buffer Pool中完成。
  2. 生成Redo记录:将修改操作转化为物理日志(如页号、偏移量、修改内容)写入Log Buffer。
  3. 刷盘策略
    • 事务提交时刷盘 :默认通过innodb_flush_log_at_trx_commit=1保证每次提交都同步写入磁盘。
    • 异步批量刷盘 :Log Buffer根据innodb_flush_log_at_timeout(默认1秒)定期刷新。
  4. 循环覆盖写入:当日志文件写满后,从头部重新开始覆盖(需确保Checkpoint已推进)。

Checkpoint机制

  • 作用:标记已刷新到数据文件的日志位置,避免无限日志增长。
  • 触发条件:日志空间不足、脏页刷新到磁盘、后台线程定期触发等。
  • 关键角色
    • Checkpoint LSN:最后一次成功刷脏的日志位置,之前的日志可安全覆盖。
    • 模糊Checkpoint:允许部分脏页未刷盘,通过恢复时重放日志保证一致性。

关键配置参数

  • innodb_log_file_size:单个Redo Log文件大小(默认48MB),建议设置为1-4GB以平衡恢复时间和I/O性能。
  • innodb_log_files_in_group:Redo Log文件数量(默认2),最多支持100个。
  • innodb_log_buffer_size:Log Buffer大小(默认16MB),高并发事务需适当增大。
  • innodb_flush_log_at_trx_commit
    • =1(默认):每次事务提交同步刷盘(最安全,性能最低)。
    • =0:每秒异步刷盘(可能丢失1秒数据)。
    • =2:写入OS缓存,不保证立即刷盘(宕机可能丢失数据)。
  • innodb_flush_log_at_timeout:异步刷盘间隔(默认1秒)。

调整日志文件大小

  • 原则:更大的日志文件减少Checkpoint频率,但增加恢复时间。
  • 建议 : 若TPS(每秒事务数)高,设置innodb_log_file_size为1-4GB,确保日志足够覆盖两次Checkpoint间的事务量。

优化刷盘策略

  • 平衡安全与性能
    • 金融场景用innodb_flush_log_at_trx_commit=1
    • 可容忍少量数据丢失的场景用=0=2
  • 使用高速存储:将Redo Log文件放在NVMe SSD或RAID卡带缓存的磁盘。

监控日志压力

  • 指标

    sql 复制代码
    SHOW ENGINE INNODB STATUS\G
    -- 查看LOG部分的写入情况:
    LOG
    Log sequence number          6068232011  -- 当前LSN(最新日志位置)
    Log buffer assigned up to    6068232011
    Log buffer completed up to   6068232011
    Log written up to            6068232011  -- 已写入日志文件的LSN
    Log flushed up to            6068232011  -- 已刷盘的LSN
    Added dirty pages up to      6068232011
    Pages flushed up to          6068232011  -- Checkpoint LSN
  • 性能问题判断 : 若Log flushed up to长期落后Log sequence number,表明刷盘速度不足,需优化I/O或调整日志配置。


查看日志文件状态

sql 复制代码
-- 查看Redo Log文件路径和大小
SELECT FILE_NAME, FILE_TYPE, TABLESPACE_NAME, ENGINE 
FROM INFORMATION_SCHEMA.FILES 
WHERE FILE_TYPE = 'REDO LOG';

手动触发Checkpoint

sql 复制代码
SET GLOBAL innodb_fast_shutdown = 0;  -- 完全关闭时触发Checkpoint

紧急日志扩展

  1. 修改innodb_log_file_size参数。
  2. 关闭MySQL,删除旧的Redo Log文件。
  3. 重启MySQL,自动生成新大小的日志文件。

2.3 后台线程

作用:MySQL的InnoDB存储引擎通过多个后台线程协同工作,保障事务处理、数据持久化、内存管理及资源清理等核心功能的高效执行。

2.3.1 Master Thread(主线程)

  • 核心职责:负责全局性的协调任务,早期版本(如MySQL 5.5前)承担多种职责,后续版本拆分为更细粒度的线程。

  • 主要任务

    • 脏页刷新(Flush):定期将缓冲池(Buffer Pool)中的脏页写入磁盘。
    • 日志刷新(Log Flush):触发Redo Log的刷盘操作。
    • 合并插入缓冲(Insert Buffer Merge):将Change Buffer中的修改合并到实际数据页。
    • 触发Checkpoint:推进Redo Log的检查点,释放可覆盖的日志空间。
  • 配置参数

    • innodb_io_capacity:控制每秒I/O操作的上限(默认200),影响脏页刷新速率。
    • innodb_max_dirty_pages_pct:缓冲池中脏页的最大占比阈值(默认90%),超过时触发强制刷新。
  • 监控

    sql 复制代码
    SHOW ENGINE INNODB STATUS\G
    -- 查看BACKGROUND THREAD部分

2.3.2 IO Threads(I/O线程)

  • 作用:处理异步I/O操作(如数据页读取、日志写入),减少主线程阻塞。
  • 分类与分工
    • Read Threads :处理数据页的预读和随机读请求。
      • innodb_read_io_threads(默认4):控制读线程数量。
    • Write Threads :负责脏页的写入操作。
      • innodb_write_io_threads(默认4):控制写线程数量。
    • Log Thread:专门处理Redo Log的写入和刷盘(通常为1个)。
    • Insert Buffer Thread:处理Change Buffer的合并操作(通常为1个)。
  • 优化建议
    • 根据存储设备性能(如SSD)增加线程数,例如设置为8-16。
    • 监控I/O等待时间,调整innodb_io_capacity以适应磁盘吞吐量。

2.3.3 Purge Thread(清理线程)

  • 核心任务
    • 清理已提交事务不再需要的Undo日志。
    • 回收Undo表空间,支持MVCC的多版本数据清理。
  • 配置参数
    • innodb_purge_threads(默认4):控制清理线程数量(MySQL 5.6+支持多线程Purge)。
    • innodb_purge_batch_size:每次清理操作的Undo页数量(默认300)。
  • 监控与调优
    • 通过SHOW ENGINE INNODB STATUS观察History list length,若持续增长表明Purge速度不足。
    • 增加innodb_purge_threadsinnodb_purge_batch_size以加速清理。

2.3.4 Page Cleaner Thread(页清理线程)

  • 引入版本:MySQL 5.7+,替代Master Thread的脏页刷新任务。
  • 作用
    • 将脏页从Buffer Pool刷新到磁盘,确保缓冲池有足够空闲页。
    • 支持并行刷新,减少对用户线程的影响。
  • 配置参数
    • innodb_page_cleaners(默认4):控制清理线程数量(建议≤Buffer Pool实例数)。
    • innodb_lru_scan_depth:每个Buffer Pool实例的LRU扫描深度(默认1024)。
  • 优化建议
    • SHOW STATUS LIKE 'Innodb_buffer_pool_wait_free'值较高,需增加清理线程或调整innodb_io_capacity

2.3.5 Change Buffer Merge Thread(插入缓冲合并线程)

  • 作用:将Change Buffer中缓存的非唯一索引修改(INSERT/UPDATE/DELETE)合并到实际数据页,减少随机I/O。

  • 触发条件

    • 相关索引页被读取到Buffer Pool时。
    • 后台线程定期合并。
  • 监控

    sql 复制代码
    SHOW ENGINE INNODB STATUS\G
    -- 查看INSERT BUFFER AND ADAPTIVE HASH INDEX部分

2.3.6 Redo Log Flush Thread(Redo日志刷盘线程)

  • MySQL 8.0+优化: Redo Log刷盘任务从用户线程剥离,由独立后台线程处理,减少事务提交延迟。
  • 参数
    • innodb_log_writer_threads(默认ON):启用独立日志写入线程。

查看所有后台线程

sql 复制代码
SELECT * FROM performance_schema.threads 
WHERE NAME LIKE '%innodb%';

关键性能指标

sql 复制代码
-- 查看InnoDB状态中的THREADS部分
SHOW ENGINE INNODB STATUS\G

2.4 执行流程

2.4.1数据读取(Read)

  • 缓冲池(Buffer Pool)查询
    • 首先检查所需数据页是否在缓冲池中。若存在,直接返回数据。
    • 若不存在,触发磁盘I/O,从表空间文件(.ibd)读取数据页到缓冲池。
  • 多版本并发控制(MVCC)
    • 根据事务隔离级别(如RC/RR),通过Undo Log构建一致性视图,返回符合事务可见性的数据版本。

2.4.2 数据写入(Insert/Update/Delete)

内存操作

  • 修改缓冲池
    • 在缓冲池中找到目标数据页(若未加载,先从磁盘读取)。
    • 修改数据页内容(插入新行、更新或标记删除),标记为脏页(Dirty Page)。
  • 生成日志
    • Redo Log:记录数据页的物理修改,写入Log Buffer。
    • Undo Log:记录修改前的数据版本,用于事务回滚和MVCC,写入Undo Tablespace。

事务提交

  • 日志刷盘(WAL原则)
    • 根据innodb_flush_log_at_trx_commit配置,将Log Buffer中的Redo Log同步或异步刷入磁盘(ib_logfileN)。
    • 若事务回滚,使用Undo Log恢复数据到修改前状态。
  • 异步处理
    • Change Buffer(仅非唯一索引):对非唯一索引的修改暂存于Change Buffer,延迟合并到磁盘。
    • 脏页刷新:由Page Cleaner Thread异步将脏页写入表空间文件。

2.4.3 数据持久化(Flushing)

  • 脏页刷新
    • 后台线程(Page Cleaner)根据innodb_io_capacityinnodb_max_dirty_pages_pct参数,定期将缓冲池中的脏页写入磁盘。
    • 使用Doublewrite Buffer确保数据页写入的原子性,避免部分页写入(Partial Page Write)。
  • Checkpoint机制
    • 记录已刷新到磁盘的LSN(Log Sequence Number),标记Redo Log可覆盖范围。
    • 崩溃恢复时,从最近的Checkpoint重放后续Redo Log。

2.4.4 崩溃恢复(Crash Recovery)

  • Redo Log重放
    • 从最近的Checkpoint开始,重新应用所有未写入数据文件的Redo Log,恢复脏页。
  • Undo Log回滚
    • 对未提交的事务,使用Undo Log回滚已修改的数据,保证原子性。

2.4.5 流程示意图

flowchart TD A[用户请求] --> B{缓冲池命中?} B -->|Yes| C[直接操作内存数据页] B -->|No| D[从磁盘加载数据页] C --> E[修改数据页为脏页] D --> E E --> F[生成Redo/Undo日志] F --> G{事务提交} G -->|Yes| H[Redo日志刷盘] G -->|No| I[等待提交/回滚] H --> J[后台异步刷新脏页] J --> K[通过Doublewrite写入磁盘] K --> L[推进Checkpoint]

三,事务原理

3.1 事务介绍

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有操作作为一个整体一起向系统提交撤销操作请求,这些操作要么同时成功要么同时失败。

3.2 事务的四大性质ACID

  1. 原子性:事务是不可分割的最小操作单元,要么全部成功,要么全部失败。

  2. 一致性:事务完成时,必须使所有的数据保持一致性。

  3. 隔离性:数据库系统提高隔离机制,保证事务在不受外部并发操作影响的独立环境运行。

  4. 持久性:事务一旦提交或回滚,他对数据库的数据改变就是永久的。

3.3 事务的原理

事务的原子性,一致性,持久性都是由redo log和undo log实现的,事务的隔离性是由 锁机制和MVCC实现的。

3.3.1 redo log

重做日志,记录的是事务提交时数据页的物理修改用来实现事务的持久性

该日志文件由俩部分组成:

  1. 重做日志缓冲(redo log buffer):在内存中

  2. 重做日志文件(redo log file):在磁盘中

当事务提交后会把所有修改信息都存放到该日志文件中,用于在刷新脏页到磁盘中,发生错误时,进行数据的恢复。

大概流程:

  1. 客户端A对innoDB存储引擎的表进行增删改事务操作

  2. 先访问内存结构中的缓冲池,如果增删改数据在其中不存在,

    就会从磁盘中读取数据再刷新到缓冲池(这个数据必须是唯一索引,否则会先进入到更改缓冲区)

  3. 在缓冲池中变成脏页,并记录在redolog buffer中,后直接刷新到磁盘中

  4. 如果脏页在一段时间后刷新到磁盘中报错了,可以通过redo log进行恢复。


使用redo log直接刷新到磁盘结构的好处

  • 事务一般是一组多条的增删改查操作,故事务提交的时候会随机的操作多条的记录,这些记录会操作多条数据页,这样会产生大量的随机磁盘IO,而直接将redo log文件异步刷新到磁盘io中,由于它是日志文件,日志文件都是追加的,此时是顺序磁盘IO,这样会节约大量的磁盘IO,这种机制叫**WAL(Write-Ahead Logging)(先写日志)**然后过一段时间脏页日志才会刷新到磁盘中,故俩份日志是循环清理的

  • 事务的redo log日志是为了解决脏页刷新到磁盘出错时进行数据的恢复使用的,用来保证数据的持久性

3.3.2 undo log

undo log日志是用来保证事务的原子性 的,undo log也叫回滚日志,用于记录数据被修改前的信息,作用为:提供回滚和MVCC

  • redo log记录的是物理日志!

  • undo log记录的是逻辑日志,可以认为当执行delete 一条记录时,undo log中会记录一条对应的insert记录,反之同理,当执行rollback时,就可以从undo log中的逻辑记录读取到对应内容,从而进行回滚。

  • Undo log销毁

    undo log在事务执行时产生,事务提交时,并不会马上删除undo log,因为这些日志可能还用于MVCC

  • Undo log存储

    undo log采用,的方式进行管理和记录,存放在前面介绍的rollback segment回滚中,内部包含了1024个undo log segment

    这个段是逻辑存储结构的段

四,MVCC(多版本并发控制)

4.1 MVCC的几个基本概念

4.1.1 当前读

我们读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁,

对于我们日常的操作,如:select...lock in share mode(共享锁),select...for update,update,insert,delete(排他锁)都是一种当前读。

案例: 在RR的隔离级别下

  1. 事务A进行查询操作,事务B进行更新操作并提交事务
  2. 事务A使用当前读select...此时读取的是事务B更新之前的数据(原因是隔离级别)
  3. 事务A使用select...lock in share mode(当前读)此时读取的是事务B更新之后的数据。

4.1.2 快照读

简单的select(不加锁)就是快照读,读取的是记录数据的可见版本,可能是历史数据,不加锁是非阻塞读。

  1. Read Committed隔离级别:每次select都生成一个快照读
  2. Repeatable Read隔离级别:开启事务后第一个select语句才是快照读的地方(后续查的就是这个快照数据)
  3. Serializable隔离级别:快照读会退化为当前读

4.2 MVCC介绍

全称Multi-Version Concurrency Control 多版本并发控制,指的是维护一个数据的多个版本,使得读写操作没有冲突。快照读为MYSQL实现MVCC提供了一个非阻塞读功能。

4.3 MVCC-实现原理

4.3.1 记录中的隐藏字段

当我们创建了表除了自己本身创建的字段,innoDB引擎会自动给我们创建三个字段

分别是:

隐藏字段 含义
DB_TRX_ID 最近修改事务ID,记录插入这条记录或者最后一次修改该记录的ID(事务id)
DB_ROLL_PTR 回滚指针,指向这条记录的上一个版本,用于配合undo log指向上一个版本
DB_ROW_ID 隐藏主键,如果表结构没有指定主键,就会生成该隐藏字段

可以查看表空间文件内容来查看隐藏字段信息,事务id是自增的!

在MYSQL中提供了一个命令来查看表空间文件的记录信息

sql 复制代码
ibd2sdi xxx.ibd

4.3.2 undo log版本链

回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志

  • 当insert的时候,产生的undo log日志只在回滚时需要,在事务提交后,可被立即删除。

  • 当update、delete的时候,产生的undo log日志不只是回滚时需要,在快照读时也需要,不会被立即删除。

Undo log版本链

不同事务或相同事务对同一条记录进行修改,会导致该记录的undo log生成一个记录的版本链表,链表的头部是最新的旧记录,链表的尾部是最早的旧记录。

执行顺序:从上到下代表执行顺序。在同一行代表同一时间执行。

4.3.3 ReadView(读视图)

ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id

ReadView中包含了四个核心字段

字段 含义
m_ids 当前活跃的事务id集合
min_trx_id 最小活跃的事务ID
max_trx_id 预分配事务ID,当前最大事务ID+1(因为事务ID是自增的)
creator_trx_id ReadView创建者的事务ID

版本链数据访问规则

trx_id 代表的是当前事务的id

用这个id和ReadView的四个核心字段进行比对

  1. trx_id == creator_trx_id 可以访问该版本 这个条件成立代表数据是当前这个事务更改的

  2. trx_id < min_trx_id 可以访问该版本 这个条件成立,说明数据已提交

  3. trx_id > max_trx_id 不可以访问该版本 这个条件成立,说明事务是在ReadView生成之后才开启的

  4. min_trx_id <= trx_id <= max_trx_id 如果trx_id不在m_ids中,就可以访问该版本

    这个条件成立,说明数据已经提交。

根据隔离级别的不同ReadView生成时机不同

  1. 在Read Committed隔离级别下

    在事务第一次执行快照读时都生成一个Read view

  2. 在Repeatable read隔离级别下

    在事务第一次执行快照读时生成,后续复用这个Read view

4.4 在RC隔离级别下,版本链访问原理分析

在事务第一次执行快照读时都生成一个Read view

第一个快照读,读取情况

这个快照读,根据规则,读取的是0x00002这个地址的数据,对应着事务2修改后的数据内容。

第二个快照读,读取情况

这个快照读,根据规则读取到的是,0x00003地址的数据内容,就是事务3修改后的数据。

4.5 在RR隔离级别下,版本链访问原理分析

在RR隔离级别下,只有事务在第一次执行快照读的时候生成ReadView,后续复用这个ReadView

具体的和RC规则一致,不重复讲解。

相关推荐
xx155802862xx2 小时前
在CentOS 7上仅安装部署MySQL 8.0客户端
mysql·adb·centos
_extraordinary_2 小时前
MySQL 事务(一)
数据库·mysql
计算机学姐3 小时前
基于SpringBoot的小区停车位管理系统
java·vue.js·spring boot·后端·mysql·spring·maven
luo_guibin4 小时前
DVWA在线靶场-SQL注入部分
数据库·sql·mysql
码熔burning5 小时前
MySQL 分页查询优化
数据库·mysql
PgSheep6 小时前
一文通俗讲解MySQL数据库常见面试题-持续更新
java·数据库·mysql·面试
2501_911121237 小时前
【无标题】
数据库·sql·mysql
Blue.ztl8 小时前
菜鸟之路day31一一MySQL之多表设计
android·数据库·mysql
篱笆院的狗17 小时前
MySQL 中如何进行 SQL 调优?
java·sql·mysql
学习2年半18 小时前
服务器mysql连接我碰到的错误
运维·服务器·mysql