在索引原理与失效分析中,我们剖析了 B+ 树如何加速查询。这篇把这些概念放到 MySQL InnoDB 的具体实现里看。
InnoDB 是 MySQL 默认的事务型存储引擎,也是生产环境中最广泛使用的引擎。它将 B+ 树索引与数据融为一体(聚簇索引),用 Undo Log 实现多版本并发控制,用 Change Buffer 优化二级索引的写入性能,用 Doublewrite 防止页撕裂。本文逐一拆解这些机制,重点落在架构组件、行格式与页结构、聚簇索引与二级索引、Change Buffer 与 Doublewrite。锁机制(Record/Gap/Next-Key Lock)涉及并发控制与幻读防护,篇幅独立且与隔离级别强相关,留到锁篇单独展开。
前置知识
了解 B+ 树索引的基本原理,便于理解聚簇索引与二级索引的回表机制,索引失效分析的细节参见索引原理与失效分析
基本的操作系统概念:页、内存缓存、fsync、崩溃恢复
一、InnoDB 架构概述
1.1 整体架构
InnoDB 的架构分为内存结构和磁盘结构两大部分,中间通过后台线程进行数据搬移与日志刷写。
1.2 内存结构详解
Buffer Pool
Buffer Pool 是 InnoDB 中最大的内存消费者,默认大小为 128MB(134217728 字节)。它以页(默认 16KB)为单位缓存磁盘数据,避免每次读写都访问磁盘。
InnoDB 的 LRU 并非朴素的 LRU,而是改进的 LRU:将链表分为 young 区(热数据,约 5/8)和 old 区(冷数据,约 3/8)。新读入的页先放入 old 区头部,只有被再次访问且超过 innodb_old_blocks_time(默认 1 秒)后才移入 young 区。这避免了全表扫描等一次性操作将热数据挤出缓存。
// Buffer Pool 的核心数据结构(简化)typedef struct buf_pool_t { lru_list_t lru_list; // 改进 LRU:young 区 + old 区 flush_list_t flush_list; // 脏页链表(已修改但未刷盘) page_hash_t page_hash; // 页哈希表(快速查找) size_t old_ratio; // old 区占比,默认 3/8} buf_pool_t;Log Buffer
Log Buffer 是 Redo Log 的内存缓冲区,事务提交时根据 innodb_flush_log_at_trx_commit 参数决定刷盘策略:
0Optionalinteger
1Requiredinteger
2Optionalinteger
生产环境中 innodb_flush_log_at_trx_commit 必须设为 1,否则崩溃可能丢失已提交事务的数据。这是 InnoDB 持久性保证的基石。
Change Buffer
Change Buffer 是二级索引页的变更缓冲区。当二级索引页不在 Buffer Pool 中时,InnoDB 不急于从磁盘读取该页,而是将变更记录在 Change Buffer 中,等后续页面被读取时再合并。这在第五节中详细分析。
Adaptive Hash Index
AHI 是 InnoDB 的自优化机制:监控到某些 B+ 树页面被频繁访问时,自动建立哈希索引,将 O(log n) 查找降为 O(1)。高并发写入场景下 AHI 的锁竞争可能成为瓶颈,可通过 SET GLOBAL innodb_adaptive_hash_index = OFF 关闭。
1.3 磁盘结构详解
表空间体系
InnoDB 通过**表空间(Tablespace)**管理磁盘存储。不同类型的表空间承担不同职责:
| 表空间类型 | 文件 | 内容 | 说明 |
|---|---|---|---|
| System Tablespace | ibdata1 | 数据字典、Doublewrite、Change Buffer | 共享,不建议存用户数据 |
| File-Per-Table | *.ibd | 单表的数据与索引 | innodb_file_per_table=ON(默认) |
| General Tablespace | 自定义 | 多表共享 | 灵活但少用 |
| Undo Tablespace | undo_001/002 | Undo Log | 独立于系统表空间 |
| Temporary Tablespace | ibtmp1 | 临时表数据 | 重启自动重建 |
Redo Log
Redo Log 是 InnoDB 的 WAL 实现,采用固定大小、循环写入的方式。由 ib_logfile0 和 ib_logfile1 两个文件组成,写满后从头覆盖。
版本变化:ib_logfile0/1 是 MySQL 8.0.30 之前的实现。8.0.30 起 Redo Log 改为 #innodb_redo 目录下的多个 #ib_redo* 文件,容量由 innodb_redo_log_capacity 统一管理(旧参数 innodb_log_file_size / innodb_log_files_in_group 仍保留兼容但被新参数覆盖)。循环写入与 LSN 机制不变。
Redo Log 的核心是 LSN(Log Sequence Number),单调递增的序号贯穿整个 InnoDB:每次修改数据页 LSN 递增,每个数据页头部记录 page_lsn,检查点之前的脏页可以安全覆盖 Redo Log。
Undo Log
Undo Log 记录数据修改前的旧值,用于事务回滚和 MVCC。Undo Log 存储在回滚段(Rollback Segment)中,以版本链的形式组织,这是 InnoDB MVCC 的基础。详细机制在第四节中展开。
二、行格式与页结构
2.1 行格式
InnoDB 支持四种行格式,其中 Compact 和 Dynamic 是最常用的:
Compact 行格式
Compact 格式将一行数据分为记录头和记录体两部分:
// Compact 行格式(简化)typedef struct compact_record_t { // ---- 记录头(Record Header)---- uint8_t n_owned; // 页目录槽位数 uint16_t heap_no; // 堆编号(0=infimum, 1=supremum) uint8_t record_type; // 0=普通, 1=B+树指针, 2=infimum, 3=supremum uint16_t next_record; // 下一条记录偏移量 uint16_t info_flags; // 标志位(deleted_mask 等)
// ---- 记录体(Record Body)---- uint8_t var_len_list[]; // 变长字段长度列表(逆序,每列 1~2 字节) uint8_t null_bitmap; // NULL 标志位(每列 1 bit) trx_id_t trx_id; // 事务 ID(6 字节) roll_ptr_t roll_ptr; // 回滚指针(7 字节)→ Undo Log row_id_t row_id; // 行 ID(6 字节,无主键时自动生成) byte col_data[]; // 实际列数据} compact_record_t;每行数据都包含 trx_id、roll_ptr 和 row_id 三个隐藏列。trx_id 记录最后一次修改该行的事务 ID,roll_ptr 指向 Undo Log 中的上一版本,这就是 InnoDB MVCC 版本链的物理基础。详见第四节。
Dynamic 行格式
Dynamic 是 Compact 的改进版,核心区别在于溢出页处理:
| 特性 | Compact | Dynamic |
|---|---|---|
| 长列存储 | 前 768 字节在页内,其余在溢出页 | 仅存 20 字节指针,全部在溢出页 |
| 页内空间利用率 | 较低(长列占大量空间) | 较高(页内只存指针) |
| 适用场景 | 旧版本兼容 | MySQL 5.7+ 默认,推荐使用 |
-- 查看表的行格式SELECT TABLE_NAME, ROW_FORMATFROM INFORMATION_SCHEMA.TABLESWHERE TABLE_SCHEMA = 'testdb';
-- 修改行格式ALTER TABLE users ROW_FORMAT = DYNAMIC;2.2 页结构
InnoDB 的页是磁盘 I/O 的最小单位,默认大小 16KB。每个页由以下部分组成:
// InnoDB 16KB 页结构(简化)typedef struct page_t { // ---- File Header(38 字节)---- uint32_t space_id; // 表空间 ID uint32_t page_no; // 页编号 lsn_t lsn; // 最后修改的 LSN page_type_t type; // 页类型 uint32_t prev_page; // B+ 树前一页 uint32_t next_page; // B+ 树后一页
// ---- Page Header(56 字节)---- uint16_t n_dir_slots; // 页目录槽数 uint16_t heap_top; // 堆顶指针 uint16_t n_heap; // 堆中记录数 uint16_t first_garbage; // 第一条已删除记录 uint16_t garbage_bytes; // 已删除记录总字节数 uint16_t last_insert; // 最后插入位置
// ---- Infimum + Supremum(边界哨兵)---- record_t infimum; // "比任何记录都小" record_t supremum; // "比任何记录都大"
// ---- User Records(单向链表,按主键排序)---- record_t records[];
// ---- Free Space ----
// ---- Page Directory(稀疏索引)---- uint16_t dir_slots[]; // 二分查找→定位 4~8 条记录组→线性遍历
// ---- File Trailer(8 字节)---- uint32_t checksum; // 页面校验和 lsn_t lsn; // 与 File Header LSN 对照} page_t;页内记录的查找过程:
:::step{通过 B+ 树定位到页}
:::step{定位到 4~8 条记录的组} :::
:::
三、聚簇索引与二级索引
3.1 聚簇索引
InnoDB 是索引组织表(Index-Organized Table, IOT),数据按主键的 B+ 树物理存储。聚簇索引的叶子节点直接包含完整的行数据,而非指向数据的指针。
聚簇索引的关键特性:
- 一张表只有一个聚簇索引,数据只能按一种顺序物理存储
- 主键即聚簇索引,如果没有定义主键,InnoDB 选择第一个唯一非空索引;如果也没有,InnoDB 自动生成 6 字节的隐藏
row_id - 范围查询高效,叶子节点通过双向链表连接,范围扫描只需顺序遍历
3.2 二级索引
二级索引的叶子节点不存储完整行数据,而是存储主键值。通过二级索引查找数据需要回表,先在二级索引中找到主键值,再到聚簇索引中查找完整行。
-- 回表过程演示SELECT * FROM users WHERE name = 'Mike';
-- 执行步骤:-- 1. 在二级索引 idx_name 中查找 'Mike' → 得到主键 id=3-- 2. 在聚簇索引中查找 id=3 → 得到完整行数据-- 3. 返回结果
-- 覆盖索引:避免回表SELECT id, name FROM users WHERE name = 'Mike';-- 二级索引已包含 id 和 name,无需回表!覆盖索引是避免回表的关键优化。如果查询所需的列全部包含在二级索引中,InnoDB 直接从索引返回数据,无需访问聚簇索引。关于覆盖索引的更多细节,参见索引原理与失效分析中的覆盖索引章节。
3.3 联合索引
联合索引是对多个列建立的 B+ 树索引,遵循最左前缀原则,索引项按定义顺序排列,先按第一列排序,第一列相同再按第二列排序,以此类推。
-- 创建联合索引CREATE INDEX idx_city_age_name ON users(city, age, name);
-- 能使用索引的查询SELECT * FROM users WHERE city = 'Beijing'; -- 最左前缀SELECT * FROM users WHERE city = 'Beijing' AND age = 25; -- 最左两列SELECT * FROM users WHERE city = 'Beijing' AND age = 25 AND name = 'Alice'; -- 全部列
-- 不能使用索引的查询(缺少最左列)SELECT * FROM users WHERE age = 25; -- 跳过了 citySELECT * FROM users WHERE name = 'Alice'; -- 跳过了 city 和 age
-- 部分使用索引(最左前缀匹配到 city)SELECT * FROM users WHERE city = 'Beijing' AND name = 'Alice'; -- 只用到 city 列3.4 索引组织表 vs 堆表
InnoDB 的索引组织表与 PostgreSQL 的堆表是两种根本不同的存储模型:
| 维度 | 索引组织表(InnoDB) | 堆表(PostgreSQL) |
|---|---|---|
| 数据存储位置 | 聚簇索引的叶子节点 | 独立的堆文件 |
| 二级索引存储 | 主键值 | 行标识符(CTID) |
| 二级索引更新 | 需更新主键值引用 | 只需更新 CTID 指针 |
| 主键更新代价 | 极高(所有二级索引需更新) | 较低(CTID 不变) |
| 范围查询 | 高效(物理有序) | 需额外排序 |
| 空间占用 | 二级索引较大(存主键值) | 二级索引较小(存 CTID) |
| 典型代表 | InnoDB、Oracle | PostgreSQL、SQL Server |
在索引组织表中,主键的选择直接影响所有二级索引的空间开销和更新代价,短且不变的主键(如自增整数)能最小化二级索引的空间开销并避免主键更新级联。关于两种模型的深入对比,参见第六节。
四、InnoDB MVCC 实现
MVCC 的通用理论框架涉及隔离级别与快照读。现在来看 InnoDB 是如何具体实现的。
4.1 Undo Log 版本链
每行数据的 roll_ptr 指向 Undo Log 中的旧版本,形成一条版本链:
-- 版本链的形成过程-- 事务 101:INSERTINSERT INTO users (id, name) VALUES (1, 'Alice');-- 当前版本:name='Alice', trx_id=101
-- 事务 102:UPDATEUPDATE users SET name = 'Bob' WHERE id = 1;-- 当前版本:name='Bob', trx_id=102, roll_ptr → Undo(name='Alice', trx_id=101)
-- 事务 103:UPDATEUPDATE users SET name = 'Charlie' WHERE id = 1;-- 当前版本:name='Charlie', trx_id=103, roll_ptr → Undo(name='Bob', trx_id=102)-- → Undo(name='Alice', trx_id=101)4.2 ReadView
ReadView 是事务在进行快照读时创建的”可见性快照”,包含四个关键字段:
// ReadView 结构(简化)typedef struct read_view_t { trx_id_t creator_trx_id; // 创建该 ReadView 的事务 ID trx_id_t up_limit_id; // 最小活跃事务 ID(m_ids 最小值) trx_id_t low_limit_id; // 下一个待分配的事务 ID(max(m_ids)+1) trx_id_t m_ids[]; // 创建时刻所有活跃事务 ID 列表} read_view_t;def is_visible(trx_id, read_view): # 1. trx_id < up_limit_id:事务已提交,可见 if trx_id < read_view.up_limit_id: return True
# 2. trx_id >= low_limit_id:事务在 ReadView 创建后才开始,不可见 if trx_id >= read_view.low_limit_id: return False
# 3. up_limit_id <= trx_id < low_limit_id:检查是否在活跃列表中 if trx_id in read_view.m_ids: return False # 事务尚未提交,不可见 else: return True # 事务已提交,可见4.3 可见性判断规则
给定一个版本的 trx_id,判断它对当前 ReadView 是否可见:
如果当前版本不可见,则沿 roll_ptr 访问上一个版本,重复判断,直到找到可见版本或到达版本链末尾。
4.4 RR vs RC 的 ReadView 差异
这是 InnoDB 中 REPEATABLE READ 和 READ COMMITTED 的本质区别,不在于锁,而在于 ReadView 的创建时机:
| 隔离级别 | ReadView 创建时机 | 效果 |
|---|---|---|
| READ COMMITTED | 每次执行 SELECT 都创建新的 ReadView | 每次查询都能看到最新已提交数据 |
| REPEATABLE READ | 事务中第一次 SELECT 时创建 ReadView,后续复用 | 事务内多次读取结果一致 |
-- RC vs RR 的行为差异-- 事务 A -- 事务 BBEGIN; BEGIN; SELECT name FROM users WHERE id = 1; -- 结果:'Alice'(ReadView-1)
UPDATE users SET name = 'Bob'WHERE id = 1;COMMIT;
SELECT name FROM users WHERE id = 1;-- RC:'Bob'(新 ReadView,看到已提交的修改)-- RR:'Alice'(复用 ReadView-1,看不到事务 B 的修改)InnoDB 的 REPEATABLE READ 不仅防止不可重复读,还通过 Gap Lock 和 Next-Key Lock 防止幻读,这超出了 SQL 标准对 RR 隔离级别的要求。而 READ COMMITTED 下 Gap Lock 不生效,这也是为什么 RC 模式下并发度更高但隔离性更弱的原因。锁机制的细节(Record/Gap/Next-Key Lock 的加锁规则、死锁分析)留到锁篇单独展开。
五、Change Buffer 与 Doublewrite
5.1 Change Buffer
Change Buffer 是 InnoDB 对二级索引 DML 操作的优化。核心思想:二级索引页不在 Buffer Pool 中时,不立即读入,而是将变更缓存在 Change Buffer 中。
-- Change Buffer 相关参数SET GLOBAL innodb_change_buffer_max_size = 25; -- 占 Buffer Pool 比例,默认 25%SET GLOBAL innodb_change_buffering = all; -- all/inserts/deletes/changes/noneChange Buffer 的适用场景与限制:
| 场景 | Change Buffer 效果 | 原因 |
|---|---|---|
| 大量写入、少量读取 | 显著提升 | 减少随机读 I/O |
| 写入后立即读取 | 反而降低 | 读取触发 merge,增加开销 |
| 唯一索引 | 不适用 | 插入时必须检查唯一性,必须读入索引页 |
| SSD 存储 | 效果有限 | 随机读 I/O 本身很快 |
5.2 Doublewrite
Doublewrite 是 InnoDB 防止**页撕裂(Partial Page Write)**的机制。页撕裂发生在写入 16KB 数据页时系统崩溃,可能只有部分页面被写入磁盘,导致数据页损坏。
Doublewrite Buffer 位于 System Tablespace 中的 2MB 连续空间,分为 2 个段,每个段 1MB = 64 个 16KB 页。写入数据页前先将完整页面写入 Doublewrite Buffer 并 fsync,确保即使写入过程中崩溃也能从 Doublewrite 恢复完整页面。
版本变化:上述”System Tablespace 内 2MB 连续空间”是 MySQL 8.0.20 之前的实现。8.0.20 起 Doublewrite 改为独立文件(#ib_16384_0.dblwr 存元数据,#ib_16384_1.dblwr 存实际页副本,容量不再是固定 2MB),由 innodb_doublewrite 等参数控制。防页撕裂的原理不变,只是存储位置和形式变了。
有人可能会问:Redo Log 不是能恢复数据吗?为什么还需要 Doublewrite?答案是:Redo Log 记录的是”对页面的修改操作”(物理逻辑日志),它假设页面本身是完整的。如果页面已经撕裂,Redo Log 无法正确重放。Doublewrite 保证了页面的物理完整性,Redo Log 保证了页面的逻辑正确性,两者各司其职,缺一不可。
5.3 Redo Log 与 LSN
Redo Log 是 InnoDB 崩溃恢复的核心。LSN(Log Sequence Number)是贯穿整个 InnoDB 的单调递增序号,每次修改数据页时递增。
LSN 在 InnoDB 中的流转涉及几个关键节点:数据页修改时产生新的 LSN 并记入 Redo Log Buffer;Redo Log 刷盘时更新 Log flushed up to;数据页刷盘时更新页头的 page_lsn;检查点推进时更新 Last checkpoint at。检查点之前的脏页已安全落盘,对应区间的 Redo Log 可以安全覆盖。
崩溃恢复流程:从最近的检查点 LSN 开始扫描 Redo Log,对每条 Redo 记录,若记录的 LSN 小于等于数据页的 page_lsn 则跳过(页面已是最新),否则重做(应用修改)。
-- 查看 LSN 和检查点信息SHOW ENGINE INNODB STATUS\G-- 关注 "LOG" 段:Log sequence number / Log flushed up to / Last checkpoint at
-- Redo Log 相关参数SHOW VARIABLES LIKE 'innodb_log%';六、InnoDB vs PostgreSQL 对比
InnoDB 和 PostgreSQL 代表了关系型数据库两种截然不同的设计哲学。理解它们的差异,是做出正确技术选型的基础。
6.1 索引组织表 vs 堆表
| 对比维度 | InnoDB(索引组织表) | PostgreSQL(堆表) |
|---|---|---|
| 数据物理顺序 | 按主键有序 | 无序(堆文件) |
| 二级索引引用 | 主键值(可能很大) | CTID(6 字节) |
| 主键更新 | 级联更新所有二级索引 | 不影响二级索引 |
| 范围查询 | 天然有序,高效 | 需排序或索引扫描 |
| 二次索引查找 | 需回表(B+ 树再查一次) | 直接通过 CTID 访问堆 |
| 空间效率 | 二级索引较大 | 二级索引紧凑 |
6.2 Undo Log vs Append-Only MVCC
这是两种数据库最根本的架构差异,如何实现多版本并发控制:
| 对比维度 | InnoDB(Undo Log) | PostgreSQL(Append-Only) |
|---|---|---|
| 旧版本存储 | Undo Log(独立区域) | 堆表中(新版本插入新行) |
| 版本链方向 | 当前版本 → 旧版本 | 旧版本 → 当前版本 |
| 读旧版本 | 沿 roll_ptr 访问 Undo Log | 通过 xmin/xmax 判断可见性 |
| 清理机制 | Undo Log Purge | VACUUM |
| 更新操作 | 原地更新 + 写 Undo Log | 插入新行 + 标记旧行 |
| 表膨胀 | 无(旧版本在 Undo Log 中) | 严重(需 VACUUM 回收) |
| 索引更新 | 二级索引可能需回表判断可见性 | HOT 更新避免索引变更 |
-- 原地更新 + Undo LogUPDATE users SET name = 'Bob' WHERE id = 1;-- 1. 旧值写入 Undo Log 2. 原地修改数据页 3. roll_ptr 指向旧版本-- Append-OnlyUPDATE users SET name = 'Bob' WHERE id = 1;-- 1. 旧行标记 xmax 2. 插入新行设 xmin 3. VACUUM 回收旧行两种 MVCC 实现各有优劣。InnoDB 的 Undo Log 方式不会导致表膨胀,但 Undo Log 空间有限,长事务可能导致 Undo Log 暴涨。PostgreSQL 的 Append-Only 方式实现简洁,但表膨胀是运维的核心痛点,VACUUM 的调优直接影响生产稳定性。
6.3 锁与并发控制
| 对比维度 | InnoDB | PostgreSQL |
|---|---|---|
| 行锁实现 | 锁定索引记录 | 锁定行标识符 |
| 幻读防护 | Gap Lock / Next-Key Lock | Serializable Snapshot Isolation |
| 死锁检测 | 自动检测 + 回滚 | 自动检测 + 回滚 |
| 锁升级 | 无(始终行级锁) | 无(始终行级锁) |
| 加锁对象 | 索引记录(非数据行) | 元组(Tuple) |
| RC 隔离级别 | Gap Lock 不生效 | 无 Gap Lock 概念 |
七、踩坑与运维
7.1 Change Buffer 在唯一索引与写密集场景下的陷阱
Change Buffer 的设计初衷是减少二级索引的随机读 I/O,但它在唯一索引上不生效。插入唯一索引时,InnoDB 必须读取索引页检查唯一性约束,无法绕过这次磁盘读,Change Buffer 就失去了意义。这意味着:如果你建了唯一索引,Insert 性能会明显低于普通二级索引。
写密集场景下还有一个隐患:大量变更堆积在 Change Buffer 中迟迟没有合并,一旦触发批量 merge(例如全表扫描或备份),会瞬间产生大量随机 I/O,导致响应时间尖峰。这在生产环境中表现为”平时写入很快,某次查询或备份时数据库突然变慢”。
写密集表如果同时有唯一索引,Change Buffer 无法优化这部分写入。评估唯一索引的必要性:业务上确实需要唯一性约束时才建,不要为了”查询快一点”就加唯一索引,普通二级索引配合 Change Buffer 通常吞吐更高。
待补充真实案例:某业务表加唯一索引后写入 QPS 下降的具体数据。
7.2 Doublewrite 的空间与 I/O 开销
Doublewrite Buffer 占用 System Tablespace 中固定 2MB 的连续空间,且每次刷脏页都要先写一遍 Doublewrite 再写实际表空间,相当于每次刷盘多写一份数据。在写密集场景下,这部分额外 I/O 不可忽视。
MySQL 8.0.20 起,Doublewrite 可以配置为独立文件或关闭(innodb_doublewrite = OFF),关闭后刷盘 I/O 减半,但一旦发生部分写(partial page write),数据页将无法用 Redo Log 恢复,只能靠备份恢复。生产环境一般不建议关闭。
八、运维参数速查
InnoDB 的关键行为可通过参数调优,以下是生产环境最常关注的几项:
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
innodb_buffer_pool_size | 128MB | Buffer Pool 大小,决定数据页和索引页的内存缓存容量 | 专用 DB 服务器设为物理内存的 60%~80%,过小会导致频繁磁盘读 |
innodb_flush_log_at_trx_commit | 1 | 事务提交时 Redo Log 的刷盘策略 | 生产环境必须设为 1 保证持久性,设为 0 或 2 崩溃会丢数据 |
innodb_doublewrite | ON | 是否开启 Doublewrite Buffer | 生产环境保持 ON,写密集且可接受数据丢失风险时才考虑关闭 |
innodb_change_buffer_max_size | 25 | Change Buffer 占 Buffer Pool 的最大比例(百分比) | 写多读少的二级索引场景可调高到 50,读多写少或大量唯一索引时调低 |
# 查看当前 InnoDB 关键参数mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; \SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; \SHOW VARIABLES LIKE 'innodb_doublewrite'; \SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';"
# 在线调整 Change Buffer 上限(动态参数)mysql -e "SET GLOBAL innodb_change_buffer_max_size = 30;"支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






