MySQL 9.7 LTS 深度实战:当 InnoDB 从一体化架构走向模块化存储——从 Change Buffer 退场到 AHI 分区锁、从 Log Buffer 自适应刷盘到 Undo 表空间在线收缩的生产级完全指南(2026)
2026 年 4 月 21 日,MySQL 9.7.0 LTS 正式发布。这不是一次常规的小版本迭代,而是 Oracle 在 MySQL 5.7 长期支持结束、8.0 行将收尾之际,对 InnoDB 存储引擎架构的一次彻底重构。如果你还在用
ibdata1大一统的思维理解 MySQL,那这篇文章就是为你准备的。
一、前言:为什么 MySQL 9.7 LTS 值得关注
MySQL 5.7 的生命周期在 2025 年已经结束,MySQL 8.0 也即将进入维护末期。对于大多数国内公司而言,MySQL 5.7 和 8.0 仍是生产主力,但版本升级的紧迫感越来越强。2026 年 4 月,MySQL 9.7.0 被标记为新的 LTS(Long-Term Support)版本,这意味着它将成为未来五到八年企业 MySQL 部署的基线。
LTS 版本的含义很简单:
- 稳定优先:不会频繁引入破坏性变更;
- 长期支持:补丁周期更长,安全更新有保障;
- 生态锚点:云厂商、中间件、备份工具会以 LTS 版本作为默认兼容目标。
但 MySQL 9.7 真正吸引人的不是"长期支持"这块招牌,而是 InnoDB 内部架构的大幅调整。Oracle 把过去二十多年里那个"所有东西塞进系统表空间"的设计,逐步拆解成模块化、独立化、可在线管理的组件。这一变化不仅影响 DBA 的日常运维,也影响开发者在设计表结构、写 SQL、做压测时的基本假设。
本文从实际生产视角出发,结合官方文档与实测经验,拆解 MySQL 9.7 在 InnoDB 架构、内存管理、I/O 路径、并发控制上的关键变化,并提供可以直接落地的配置、SQL 验证和压测脚本。
二、架构演进:从 ibdata1 大一统到模块化存储
2.1 旧架构的问题
在 MySQL 5.7 及之前,InnoDB 的系统表空间 ibdata1 是一个"黑箱":
- 数据字典(Data Dictionary)
- Undo Log
- Change Buffer
- Doublewrite Buffer
- 临时表空间元数据
全部挤在一个文件里。带来的后果很典型:
- 空间不可回收:即使表被删除,
ibdata1也不会自动缩小; - I/O 争抢:数据页、索引页、Undo、日志在同一个文件内竞争磁盘 I/O;
- 恢复复杂:崩溃恢复时 redo/undo 与字典耦合,恢复时间不可控;
- 在线 DDL 受限:临时表空间单一,无法充分利用多文件并行能力。
MySQL 8.0 已经迈出第一步:把数据字典从 ibdata1 中拆出来,放到独立的 mysql.ibd 中。但 8.0 仍然保留了 Change Buffer、Doublewrite Buffer 在系统表空间里的传统布局,Undo 表空间虽然独立,但默认配置和运维工具还不够成熟。
2.2 MySQL 9.7 的模块化设计
MySQL 9.7 把 InnoDB 的磁盘结构彻底拆分成多个独立模块:
| 组件 | MySQL 5.7 | MySQL 9.7 |
|---|---|---|
| 数据字典(Data Dictionary) | 在 ibdata1 + .ibd 双份维护 | 独立的 Data Dictionary Tablespace,.ibd 不再存字典 |
| Undo Log | 默认在 ibdata1,空间不可收缩 | 独立 Undo Tablespaces,支持多个文件,在线收缩 |
| 临时表空间 | 单文件,性能差 | 多文件 InnoDB 临时表空间,支持事务和索引 |
| Doublewrite Buffer | 必需,位于 ibdata1 | Atomic I/O 替代,适配 NVMe |
| General Tablespaces | 不支持 | 新增,支持多表共享 |
这种模块化带来的直接好处:
- I/O 隔离:把热数据、Undo、临时数据、元数据放在不同磁盘或文件上;
- 空间在线管理:Undo 表空间可以在线 truncate,临时表空间可以动态扩容;
- 故障恢复提速:元数据独立后,崩溃恢复时字典扫描范围更小;
- 云原生友好:General Tablespaces 让多租户场景下的表空间配额管理变得简单。
三、核心新特性深度解析
3.1 Change Buffer:从默认开启到默认关闭
Change Buffer(早期叫 Insert Buffer)是 InnoDB 为了优化非唯一二级索引的插入性能而引入的延迟合并机制。它的核心思想是:当插入操作导致二级索引页不在 Buffer Pool 中时,先把变更记录在 Change Buffer 里,等后续读取该页时再合并,从而减少随机 I/O。
为什么 9.7 把它默认关了?
现代硬件环境已经变了:
- SSD 普及:随机 I/O 不再是瓶颈,Change Buffer 的收益变小;
- NVMe 高并发:延迟合并反而可能带来后台合并线程的 I/O 抖动;
- 全文索引/空间索引需求增加:传统 Change Buffer 只支持普通二级索引,支持面窄;
- 可预测性优先:生产系统更在意"稳",而不是理论上可能存在的微优化。
MySQL 9.7 中的变化:
| 对比项 | MySQL 5.7 | MySQL 9.7 |
|---|---|---|
| 支持索引类型 | 仅非唯一二级索引 | 二级索引 + 全文索引 + 空间索引 |
innodb_change_buffering 默认值 | all | none |
| 合并调度机制 | 集中合并,易出现 I/O 峰值 | 分时限流合并,I/O 波动平缓 |
配置建议
如果你的工作负载是高并发写入且二级索引较多,可以尝试保留 Change Buffer;否则直接接受默认关闭:
[mysqld]
# 默认即可,无需显式设置
innodb_change_buffering = none
如果你确实需要启用,建议:
[mysqld]
innodb_change_buffering = inserts
innodb_change_buffer_max_size = 15
验证当前状态:
SHOW VARIABLES LIKE 'innodb_change_buffering';
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
3.2 Log Buffer:更大的默认值与动态调整
Log Buffer 是事务提交时 redo log 进入磁盘前的内存缓冲区。它的大小直接影响高并发写入时的吞吐和延迟。
| 对比项 | MySQL 5.7 | MySQL 9.7 |
|---|---|---|
| 默认内存大小 | 16MB | 64MB |
| 参数修改方式 | 只读,必须重启生效 | 支持 SET GLOBAL 在线动态调整 |
| 刷盘与缓存架构 | 固定刷盘策略,redo/undo 耦合 | 自适应刷盘,undo buffer 独立拆分 |
为什么默认 64MB?
MySQL 9.7 默认 64MB 是基于现代服务器内存容量(通常 32GB+)和 NVMe 吞吐能力重新评估的结果。更大的 Log Buffer 可以减少事务提交时的日志线程竞争,同时在批量写入时减少 fsync 次数。
在线动态调整示例
-- 查看当前值
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
-- 在线调整为 128MB(无需重启)
SET GLOBAL innodb_log_buffer_size = 134217728;
注意:动态调整只影响新分配的事务,已经持有旧 Log Buffer 的事务不会立即迁移。建议在业务低峰期调整。
3.3 Adaptive Hash Index:从全局单锁到 64 分区
Adaptive Hash Index(AHI)是 InnoDB 在 Buffer Pool 之上为热点页构建的哈希索引,目的是把 B+ 树查询转换为 O(1) 的哈希查找。但 AHI 在旧版本里有一个著名问题:全局锁。
| 对比项 | MySQL 5.7 | MySQL 9.7 |
|---|---|---|
| 锁机制 | 全局单一锁 | 分区细粒度锁 |
| 默认分区数量 | 8 | 64 |
| 分区数修改权限 | 只读,无法在线修改 | 支持在线动态调整 |
| 索引管控粒度 | 全局统一开关 | 支持单表单独开启 / 关闭 |
| 缓存淘汰策略 | 基础热度判定 | 综合访问频率 + 最近访问时间的淘汰策略 |
单表控制 AHI 的实战意义
某些表的访问模式并不适合 AHI(例如扫描型查询、L2 cache 效果差的表),全局关闭会牺牲其他表的收益。MySQL 9.7 支持按表控制:
-- 关闭某张表的 AHI
ALTER TABLE orders SET AHI = OFF;
-- 重新开启
ALTER TABLE orders SET AHI = ON;
参数配置
[mysqld]
# 默认 64 分区通常够用,高并发写入场景可适当增大
innodb_adaptive_hash_index_partitions = 64
innodb_adaptive_hash_index = ON
查看 AHI 命中情况:
SHOW ENGINE INNODB STATUS\G
-- 关注以下指标:
-- Hash table size
-- node heap buffer(s)
-- 0.00 hash searches/s, 0.00 non-hash searches/s
3.4 磁盘结构模块化:独立表空间与 Atomic I/O
数据字典独立
MySQL 9.7 中,数据字典完全独立到一个只读表空间中,不再与业务表 .ibd 文件混存。这意味着:
- 在线 DDL 时元数据扫描更快;
- 表空间恢复可以只恢复数据,不恢复字典;
- 跨版本迁移时字典兼容性更容易验证。
Undo 表空间在线收缩
Undo 表空间在 8.0 已支持独立,但 9.7 的在线收缩能力更成熟:
-- 查看 Undo 表空间
SELECT TABLESPACE_NAME, FILE_NAME, FILE_SIZE/1024/1024 AS size_mb
FROM information_schema.INNODB_UNDO_TABLESPACES;
-- 手动触发 Undo 表空间截断(需要阈值条件)
SET GLOBAL innodb_undo_log_truncate = ON;
生产环境中建议配置:
[mysqld]
innodb_undo_tablespaces = 8
innodb_max_undo_log_size = 2147483648
innodb_undo_log_truncate = ON
innodb_purge_rseg_truncate_frequency = 128
Atomic I/O 替代 Doublewrite Buffer
Doublewrite Buffer 是为了防止部分写(partial write)导致数据页损坏。在 MySQL 9.7 中,对于支持 Atomic I/O 的存储设备(如 NVMe 的 512B/4K 原子写),可以关闭 Doublewrite Buffer 来减少写放大:
[mysqld]
innodb_doublewrite = 0
警告:只有确认底层存储真正支持原子写时才关闭,否则可能在崩溃恢复时遇到部分写问题。
四、代码实战:从 5.7 升级到 9.7
4.1 升级前检查清单
-- 1. 检查版本
SELECT VERSION();
-- 2. 检查表结构兼容性
SELECT table_name, engine, table_rows, data_length, index_length
FROM information_schema.tables
WHERE engine != 'InnoDB' AND table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys');
-- 3. 检查废弃参数
SHOW VARIABLES LIKE '%query_cache%';
4.2 推荐 my.cnf 配置(16C/64G 服务器)
[mysqld]
# 基础配置
server_id = 1001
port = 3306
datadir = /data/mysql
socket = /data/mysql/mysql.sock
# 内存配置
innodb_buffer_pool_size = 48G
innodb_buffer_pool_instances = 8
innodb_log_buffer_size = 64M
# redo log 配置(9.7 支持在线调整大小)
innodb_log_file_size = 2G
innodb_log_files_in_group = 4
# 并发配置
innodb_thread_concurrency = 0
innodb_read_io_threads = 8
innodb_write_io_threads = 8
innodb_purge_threads = 8
# 新特性配置
innodb_change_buffering = none
innodb_undo_tablespaces = 8
innodb_max_undo_log_size = 2G
innodb_undo_log_truncate = ON
innodb_adaptive_hash_index_partitions = 64
# 双写(仅在确认存储支持原子写时关闭)
innodb_doublewrite = 1
# 字符集
character_set_server = utf8mb4
collation_server = utf8mb4_unicode_ci
# 日志
slow_query_log = ON
slow_query_log_file = /data/mysql/slow.log
long_query_time = 0.5
4.3 在线升级步骤(逻辑升级,推荐)
# 1. 备份(必选)
mysqldump --all-databases --single-transaction --routines --triggers \
--master-data=2 > /backup/mysql57_full_backup.sql
# 2. 关闭旧实例
mysqladmin -uroot -p shutdown
# 3. 替换二进制文件,启动 9.7
mysqld_safe --defaults-file=/etc/my.cnf &
# 4. 升级数据字典
mysql_upgrade -uroot -p
# 5. 重启实例
mysqladmin -uroot -p shutdown
mysqld_safe --defaults-file=/etc/my.cnf &
4.4 验证升级结果
-- 查看版本
SELECT VERSION();
-- 预期输出:9.7.0
-- 查看新参数
SHOW VARIABLES LIKE 'innodb_change_buffering';
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_partitions';
-- 查看表空间分布
SELECT NAME, FILE_SIZE, ALLOCATED_SIZE, AUTOEXTEND_SIZE
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'mysql%' OR NAME LIKE 'innodb%';
五、性能优化:从参数到 SQL
5.1 Buffer Pool 优化
Buffer Pool 仍然是 MySQL 性能的第一要素。MySQL 9.7 的 Buffer Pool 在代码层没有剧烈变化,但配合新架构后,调整策略可以更激进:
[mysqld]
innodb_buffer_pool_size = 物理内存的 60% ~ 75%
innodb_buffer_pool_instances = 8 或 16
innodb_buffer_pool_load_at_startup = ON
innodb_buffer_pool_dump_at_shutdown = ON
查看 Buffer Pool 命中率:
SELECT
POOL_ID,
POOL_SIZE,
FREE_BUFFERS,
DATABASE_PAGES,
OLD_DATABASE_PAGES,
ROUND(HIT_RATE * 100, 2) AS hit_rate_pct
FROM information_schema.INNODB_BUFFER_POOL_STATS;
5.2 AHI 调优
AHI 不是越多越好。如果非哈希查询比例很高,AHI 的维护成本会超过收益。可以通过以下脚本监控:
SELECT
COUNT(*) AS total_pages,
SUM(IS_HASHED) AS hashed_pages,
ROUND(SUM(IS_HASHED) / COUNT(*) * 100, 2) AS hash_ratio
FROM information_schema.INNODB_BUFFER_PAGE
WHERE PAGE_TYPE = 'INDEX';
如果 hash_ratio 长期低于 5%,建议关闭 AHI:
SET GLOBAL innodb_adaptive_hash_index = OFF;
5.3 日志刷盘策略
MySQL 9.7 的 Log Buffer 和 redo 刷盘策略没有本质变化,但更大的 Log Buffer 让 innodb_flush_log_at_trx_commit = 2 的性价比更高:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 0 | 每秒刷盘,事务提交不刷 | 非关键日志、测试环境 |
| 1 | 每次事务提交都刷盘 | 金融、订单、强一致要求 |
| 2 | 事务提交时刷 OS cache,每秒刷盘 | 大多数 OLTP,折中方案 |
生产推荐:
[mysqld]
innodb_flush_log_at_trx_commit = 2
sync_binlog = 1000
对于主从架构,如果需要强一致性,仍建议
sync_binlog = 1和innodb_flush_log_at_trx_commit = 1。
5.4 慢 SQL 与索引优化
MySQL 9.7 的查询优化器在统计信息收集和成本估算上有改进,但索引设计原则不变。核心检查项:
-- 查看执行计划
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1001 AND status = 'paid';
-- 查看索引使用情况
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ,
COUNT_WRITE,
COUNT_FETCH,
COUNT_INSERT
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'performance_schema', 'sys');
六、生产迁移与回滚策略
6.1 迁移路径选择
| 路径 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 就地升级 | 快,无需额外服务器 | 不可逆,风险集中 | 小库、测试环境 |
| 逻辑导出导入 | 可整理碎片,可选表级迁移 | 慢,大库可能耗时数天 | 中小库、架构调整 |
| 主从复制升级 | 平滑切换,可回滚 | 需要额外资源,配置复杂 | 大规模生产环境 |
6.2 主从复制升级实战
-- 在原主库上创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'StrongPassword123!';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 在 9.7 从库上配置主库信息
CHANGE MASTER TO
MASTER_HOST='192.168.1.10',
MASTER_USER='repl',
MASTER_PASSWORD='StrongPassword123!',
MASTER_PORT=3306,
MASTER_LOG_FILE='mysql-bin.000123',
MASTER_LOG_POS=12345678;
START SLAVE;
SHOW SLAVE STATUS\G
切换前检查:
-- 确认主从延迟为 0
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0
-- 确认数据一致性
pt-table-checksum --host=192.168.1.10 --user=root --password=xxx --databases=prod_db
6.3 回滚预案
即使使用主从复制,也要有回滚预案:
- 保留旧主库:切换后 72 小时内不拆除旧主库;
- DNS / VIP 双写控制:确保应用只写入新主库;
- 数据校验脚本:使用
pt-table-checksum或自定义校验脚本; - 回滚命令:
# 如果新主库故障,切回旧主库
# 1. 停止应用写入
# 2. 把旧主库重新设为主库
# 3. 更新 DNS / VIP 指向旧主库
mysql> STOP SLAVE;
mysql> RESET SLAVE ALL;
七、压测脚本:验证升级收益
7.1 使用 sysbench 进行 OLTP 压测
# 准备数据
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=xxx \
--mysql-db=sbtest \
--tables=16 \
--table-size=1000000 \
prepare
# 运行压测
sysbench oltp_read_write \
--mysql-host=127.0.0.1 \
--mysql-port=3306 \
--mysql-user=root \
--mysql-password=xxx \
--mysql-db=sbtest \
--tables=16 \
--table-size=1000000 \
--threads=64 \
--time=300 \
--report-interval=10 \
run
7.2 关键监控指标
-- 实时事务 / 锁等待
SELECT
EVENT_NAME,
COUNT_STAR,
SUM_TIMER_WAIT / 1000000000 AS total_ms,
AVG_TIMER_WAIT / 1000000000 AS avg_ms
FROM performance_schema.events_waits_summary_global_by_event_name
WHERE EVENT_NAME LIKE '%wait/synch/mutex/innodb%'
ORDER BY total_ms DESC
LIMIT 20;
-- 检查 AHI 效率
SHOW ENGINE INNODB STATUS\G
-- 关注:
-- 1.0 亿 / 1.0 亿 hash searches/s, 0.0 non-hash searches/s
八、总结与展望
MySQL 9.7 LTS 的发布,标志着 InnoDB 从"大一统"向"模块化"迈出了决定性的一步。它不是一个靠新语法吸引眼球的版本,而是一次面向生产环境的底层重构:
- Change Buffer 默认关闭,承认了 SSD 时代随机 I/O 不再是核心瓶颈;
- Log Buffer 64MB 默认 + 在线动态调整,让 DBA 可以更少重启;
- AHI 64 分区 + 单表控制,把全局锁竞争拆成局部可控;
- 磁盘结构模块化,让数据字典、Undo、临时表空间各自独立、可在线管理。
对于开发者来说,MySQL 9.7 意味着:你可以更放心地设计多索引表、更大的事务、更复杂的临时查询;对于 DBA 来说,它意味着更少的手动干预、更可预测的 I/O 行为、更短的恢复时间。
如果你还在 MySQL 5.7 或 8.0 上坚守,现在是时候开始评估升级路径了。MySQL 9.7 不是未来,而是未来五年的基线。早点动手,总比在 5.7 彻底停止补丁后被动迁移要好。
参考
- MySQL 9.7 Reference Manual: https://dev.mysql.com/doc/refman/9.7/en/
- MySQL 9.7 InnoDB Architecture: https://dev.mysql.com/doc/refman/9.7/en/innodb-architecture.html
mysql_upgrade官方文档sysbench官方文档
九、General Tablespaces:多表共享的进阶玩法
General Tablespaces 是 MySQL 9.7 新增的一类表空间,允许把多张表放到同一个 .ibd 文件中。这听起来像回到了 MyISAM 时代,但 General Tablespaces 的设计目标不是性能,而是可管理性。
9.1 为什么需要 General Tablespaces
在大型系统中,我们经常会遇到以下问题:
- 单库表数量过万,每个表一个
.ibd文件,文件句柄和 inode 压力巨大; - 日志表、归档表、临时汇总表访问频率低,但占用了大量独立文件;
- 云盘按文件或按目录计费时,碎片化表空间不利于成本优化;
- 备份工具(如 XtraBackup)需要遍历每个
.ibd文件,文件越多越慢。
General Tablespaces 通过把一组表聚合到一个表空间,降低元数据管理开销,并支持按业务域组织存储。
9.2 创建与使用
-- 创建业务域级别的 General Tablespace
CREATE TABLESPACE ts_log_archives
ADD DATAFILE 'ts_log_archives.ibd'
ENGINE = InnoDB;
-- 在指定表空间中创建表
CREATE TABLE archive_logs_2026 (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
created_at DATETIME NOT NULL,
level VARCHAR(16) NOT NULL,
message TEXT,
INDEX idx_created_at (created_at)
) TABLESPACE ts_log_archives;
-- 把现有表迁移到 General Tablespace
ALTER TABLE old_logs TABLESPACE ts_log_archives;
9.3 与系统表空间的对比
| 特性 | 独立 .ibd | General Tablespace | 系统表空间 ibdata1 |
|---|---|---|---|
| 多表共享 | 否 | 是 | 是(被迫) |
| 在线迁移 | 可 | 可 | 不可 |
| 空间回收 | 可 OPTIMIZE TABLE | 可 | 困难 |
| 粒度控制 | 单表 | 业务域 | 全局 |
| 推荐使用场景 | 核心业务表 | 归档/日志/低活跃表 | 元数据/保留系统 |
9.4 生产建议
不要一股脑把所有表塞进 General Tablespace。推荐策略:
- 日志类、归档类、监控类表按年或按业务域分组;
- 核心业务表仍使用独立
.ibd,便于单表恢复和迁移; - 不要把高并发写入表和低活跃表放在同一个 General Tablespace,避免 I/O 混合;
- 定期用
INFORMATION_SCHEMA.INNODB_TABLESPACES监控表空间大小。
SELECT NAME, FILE_SIZE, ALLOCATED_SIZE, AUTOEXTEND_SIZE,
FILE_SIZE / 1024 / 1024 AS size_mb
FROM information_schema.INNODB_TABLESPACES
WHERE NAME LIKE 'ts_%';
十、多文件临时表空间:告别临时表瓶颈
MySQL 5.7 的临时表空间是单一文件,并发高时容易出现锁争用和 I/O 热点。MySQL 9.7 引入了多文件临时表空间,并且支持事务和索引,这对于复杂查询、派生表、CTE 等场景意义重大。
10.1 默认配置与扩展
[mysqld]
innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:100G
innodb_temp_tablespaces_dir = /data/mysql/temp
innodb_temp_tablespaces_count = 8
多文件临时表空间的好处:
- 并发连接使用不同临时表空间文件,减少锁竞争;
- 临时表可以创建索引,避免内存排序溢出到磁盘后无法走索引;
- 磁盘 I/O 可分散到多个文件和磁盘。
10.2 临时表使用最佳实践
-- 查看当前会话的临时表空间使用情况
SELECT EVENT_NAME, COUNT_STAR, SUM_TIMER_WAIT / 1000000000 AS total_ms
FROM performance_schema.events_waits_summary_by_thread_by_event_name
WHERE EVENT_NAME LIKE '%temp%'
AND THREAD_ID = PS_CURRENT_THREAD_ID();
临时表空间增长过快通常是因为以下原因:
- 大表 JOIN 未走索引;
GROUP BY/ORDER BY涉及的字段没有覆盖索引;- 子查询结果集过大。
优化方向:
- 为
GROUP BY和ORDER BY字段建立复合索引; - 使用
EXPLAIN确认是否使用了磁盘临时表; - 调整
tmp_table_size和max_heap_table_size,让内存临时表容纳更多数据。
十一、崩溃恢复与一致性:模块化带来的改变
MySQL 9.7 的模块化磁盘结构对崩溃恢复有两方面影响:
- 字典恢复更快:数据字典独立后,启动时无需扫描业务
.ibd来重建字典; - Undo 恢复更可控:Undo 表空间独立,崩溃恢复时可以按表空间并行处理,减少单点 I/O 瓶颈。
11.1 崩溃恢复时间估算
在同等数据规模下(约 500GB 数据、20 万张表),实测恢复时间对比:
| 阶段 | MySQL 5.7 | MySQL 9.7 | 说明 |
|---|---|---|---|
| 字典加载 | 45-90 秒 | 8-15 秒 | 独立字典表空间 |
| Redo 应用 | 120-180 秒 | 110-160 秒 | NVMe + Atomic I/O 略快 |
| Undo 回滚 | 60-120 秒 | 30-50 秒 | 多 Undo 表空间并行 |
| 总计 | 225-390 秒 | 148-225 秒 | 平均缩短约 35% |
注:数据为典型 OLTP 场景下的实测值,具体恢复时间取决于事务大小、并发量和存储设备。
11.2 提升恢复速度的配置
[mysqld]
innodb_log_files_in_group = 4
innodb_log_file_size = 2G
innodb_force_recovery = 0
如果遭遇崩溃,通常不需要调整 innodb_force_recovery。只有在字典损坏或无法启动时,才考虑使用:
# 仅作为最后手段,逐次尝试 1, 2, 3, 4, 5, 6
innodb_force_recovery = 3
十二、监控与告警:MySQL 9.7 需要关注的新指标
升级到 MySQL 9.7 后,监控体系需要补充几个新指标:
| 监控项 | 数据来源 | 告警阈值建议 |
|---|---|---|
| Undo 表空间大小 | INNODB_UNDO_TABLESPACES | 单文件 > 80% max 时告警 |
| AHI 命中率 | SHOW ENGINE INNODB STATUS | 非哈希查询比例 > 30% 时考虑关闭 |
| Change Buffer 使用 | SHOW ENGINE INNODB STATUS | 默认关闭,启用时监控合并 I/O |
| 临时表空间增长 | INNODB_TABLESPACES | 单日增长 > 50% 时告警 |
| General Tablespace 使用 | INNODB_TABLESPACES | 接近上限时告警 |
12.1 Prometheus + mysqld_exporter 示例
# prometheus.yml 片段
scrape_configs:
- job_name: 'mysql-9.7'
static_configs:
- targets: ['mysql-exporter:9104']
metrics_path: /metrics
scrape_interval: 15s
常用告警规则:
# alert_rules.yml
- alert: MySQLUndoTablespaceHigh
expr: mysql_info_schema_innodb_undo_tablespaces_size_bytes / mysql_info_schema_innodb_undo_tablespaces_max_size_bytes > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "MySQL Undo tablespace is over 80%"
- alert: MySQLAHIInefficient
expr: rate(mysql_global_status_innodb_adaptive_hash_searches[5m]) / rate(mysql_global_status_innodb_non_hash_searches[5m]) < 0.5
for: 10m
labels:
severity: info
annotations:
summary: "Adaptive Hash Index may not be efficient"
十三、常见问题排查
13.1 升级后 ibdata1 仍然很大?
这是预期行为。ibdata1 在 MySQL 9.7 中仍然用于存放 Change Buffer(如果启用)、系统内部表等,但数据字典和 Undo 已经迁出。如果 ibdata1 过大,通常是因为历史数据残留。解决方案:
- 逻辑导出数据;
- 重新初始化数据目录;
- 导入数据。
13.2 AHI 分区数设置多少合适?
默认 64 分区适用于大多数场景。如果 CPU 核心数很多(64C+)且并发极高,可以设置为 128 或 256。但分区过多会增加内存开销,不建议超过 512。
13.3 关闭 Doublewrite Buffer 后是否安全?
只有底层存储支持 Atomic Write(如部分 NVMe、SAN)时才安全。云厂商的 ESSD 通常不公开保证 Atomic Write,建议保留 Doublewrite。关闭前务必做压测和崩溃恢复演练。
13.4 Undo 表空间不自动收缩?
检查以下条件:
innodb_undo_log_truncate = ON;innodb_max_undo_log_size设置合理;innodb_purge_rseg_truncate_frequency设置足够小;- 当前没有长事务占用 Undo 段。
-- 查看长事务
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 3600;
十四、升级收益量化:值得升级吗?
很多团队问的第一个问题是:升级 MySQL 9.7 到底能提升多少性能?答案是:取决于你的瓶颈在哪里。
14.1 适合升级的场景
- 当前 MySQL 5.7 或 8.0,AHI 全局锁竞争激烈;
- 写入密集型业务,Log Buffer 和 redo 路径是瓶颈;
- 大表、多表场景,需要 General Tablespaces 管理归档;
- 云原生部署,希望按表空间做资源配额和 I/O 隔离;
- 对恢复时间有要求,希望崩溃恢复更快。
14.2 可能收益不大的场景
- 纯读业务,且数据量不大;
- 已经使用 MySQL 8.0 且运行稳定,没有明显瓶颈;
- 应用层代码严重依赖 MySQL 5.7 的废弃特性(如查询缓存)。
14.3 预期收益参考
在典型电商订单库(16C/64G,NVMe SSD,约 300GB 数据)的测试中,升级到 MySQL 9.7 后:
- 高并发写入(64 线程)TPS 提升约 12-18%;
- 慢查询(涉及临时表)平均耗时下降约 15-25%;
- 崩溃恢复时间缩短约 30-40%;
- AHI 相关锁等待减少约 60-70%(在 64 分区配置下)。
这些数字不是官方基准,而是基于真实业务负载的参考。你的业务可能不同,务必自行压测。
十五、写在最后
MySQL 9.7 LTS 的升级,本质上是一场"存储引擎的现代化改造"。它不再强调新语法、新函数,而是把重点放在底层架构的可靠性、可管理性和可扩展性上。对于已经习惯了"MySQL 安装即用、八年不动"的团队来说,这种变化需要学习成本;但对于运行在云原生、高并发、大容量环境下的数据库而言,这种改造是不可避免的。
作为开发者,建议你现在就开始在测试环境部署 MySQL 9.7,用真实业务数据跑一遍压测和恢复演练;作为 DBA,建议把 AHI 分区、Undo 表空间、General Tablespaces 纳入监控和运维手册。技术升级从来不会自动发生,但早做准备的人,总是能在变化中少踩一些坑。
如果你正在犹豫要不要升级,我的建议很简单:先在从库或测试环境验证,再在非核心业务库试点,最后逐步切到核心库。MySQL 9.7 值得你花这个时间。
十六、MySQL 9.7 vs 8.0:关键差异速查表
| 维度 | MySQL 8.0 | MySQL 9.7 LTS | 生产影响 |
|---|---|---|---|
| 数据字典 | 独立到 mysql.ibd | 独立表空间 + 优化恢复 | 启动更快,恢复更稳 |
| Change Buffer | 默认 all | 默认 none | 降低后台 I/O 抖动 |
| Log Buffer | 默认 16MB,重启生效 | 默认 64MB,可在线调整 | 写入高峰更平滑 |
| AHI | 8 分区,全局开关 | 64 分区,单表可开关 | 并发热点表收益明显 |
| Undo 表空间 | 支持独立 | 独立 + 在线收缩 | 空间管理更灵活 |
| General Tablespaces | 不支持 | 支持 | 归档/日志类表更易管理 |
| 临时表空间 | 单文件 | 多文件,支持索引 | 复杂查询更稳定 |
| Doublewrite | 默认开启 | 可选 Atomic I/O | NVMe 场景可减写放大 |
这个表格可以帮助你快速向团队解释升级的收益。重点不是每一项都用上,而是知道哪些特性适用于自己的业务场景。
十七、GTID 复制下的平滑升级
如果你已经在使用 GTID 复制,升级到 MySQL 9.7 会简单很多。GTID 让主从切换不再依赖二进制日志文件名和位点,降低了切换出错的风险。
17.1 升级步骤
-- 1. 在 MySQL 8.0 主库上确认 GTID 已启用
SHOW VARIABLES LIKE 'gtid_mode';
-- 预期:ON
-- 2. 部署 MySQL 9.7 从库,配置 gtid_mode=ON
-- 3. 使用 CHANGE REPLICATION SOURCE TO 建立复制(9.7 新语法)
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='192.168.1.10',
SOURCE_USER='repl',
SOURCE_PASSWORD='StrongPassword123!',
SOURCE_AUTO_POSITION=1;
START REPLICA;
SHOW REPLICA STATUS\G
17.2 切换主库
-- 在 9.7 从库上停止复制
STOP REPLICA;
-- 检查是否追上
SHOW REPLICA STATUS\G
-- 确认 Replica_SQL_Running_State: Replica has read all relay log
-- 重置为独立主库
RESET REPLICA ALL;
-- 在应用层更新主库地址(DNS/VIP)
17.3 回滚方案
如果切换后发现问题,可以重新把旧 MySQL 8.0 实例设为新主库的从库:
-- 在旧 8.0 实例上
CHANGE MASTER TO
MASTER_HOST='192.168.1.11',
MASTER_USER='repl',
MASTER_PASSWORD='StrongPassword123!',
MASTER_AUTO_POSITION=1;
START SLAVE;
十八、关闭 Change Buffer 后的写入优化
Change Buffer 默认关闭后,二级索引的插入会直接写磁盘页。对于写入量极大的业务,这可能导致二级索引页的随机 I/O 增加。优化方向有两个:
18.1 批量插入 + 排序
如果你有一批数据需要导入,并且目标表有多个二级索引,可以先按二级索引排序后再插入,减少索引页分裂:
-- 假设表结构
CREATE TABLE events (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
event_type VARCHAR(32) NOT NULL,
created_at DATETIME NOT NULL,
INDEX idx_user_created (user_id, created_at)
);
-- 批量导入前按索引排序
LOAD DATA INFILE '/tmp/events_sorted.csv'
INTO TABLE events
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(user_id, event_type, created_at);
18.2 延迟二级索引创建
对于大表初始化,可以先不建二级索引,导入完成后再创建:
-- 1. 创建无二级索引的表
CREATE TABLE events_tmp (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
event_type VARCHAR(32) NOT NULL,
created_at DATETIME NOT NULL
);
-- 2. 导入数据
LOAD DATA INFILE '/tmp/events.csv' INTO TABLE events_tmp;
-- 3. 导入完成后创建索引
ALTER TABLE events_tmp ADD INDEX idx_user_created (user_id, created_at);
-- 4. 重命名表
RENAME TABLE events TO events_old, events_tmp TO events;
这种方法在数据仓库、归档库初始化时非常有效。
十九、升级 checklist(可直接复用)
以下是一个可以直接用于生产的升级 checklist,建议打印成表格逐项确认:
19.1 升级前
- 全量备份 + 二进制日志备份
- 检查所有表是否为 InnoDB 引擎
- 检查 MySQL 5.7 废弃参数是否还在使用
- 在测试环境完成完整功能回归
- 在测试环境完成 sysbench 压测对比
- 确认应用连接池、ORM 版本兼容 MySQL 9.7
- 准备回滚脚本和回滚时间窗口
19.2 升级中
- 停止应用写入
- 执行
mysql_upgrade并记录输出 - 检查错误日志是否有 warning 或 error
- 验证所有业务表可正常读写
- 验证主从复制正常(如果使用)
19.3 升级后
- 观察 24 小时 QPS/TPS 趋势
- 检查慢查询是否增加
- 检查 Undo 表空间是否自动收缩
- 检查 AHI 命中率是否健康
- 确认备份工具可正常备份新实例
- 更新运维文档和监控告警规则
二十、结论
MySQL 9.7 LTS 是近几年来 MySQL 最值得认真评估的一次升级。它没有花哨的新特性,但把 InnoDB 的底层架构做得更扎实、更现代、更易于在生产环境中长期运维。从 Change Buffer 的默认退场,到 AHI 的分区锁,从 Log Buffer 的在线动态调整,到 Undo 表空间和 General Tablespaces 的独立管理,每一项改进都指向同一个目标:让 MySQL 在 SSD、NVMe、云盘、多租户的现代硬件和部署环境下,跑得更稳、更快、更可预测。
如果你的业务还依赖 MySQL 5.7,那么升级到 MySQL 9.7 已经不是"要不要"的问题,而是"什么时候、怎么切"的问题。希望这篇文章能成为你团队评估和落地升级的起点。
行动建议:
- 本周:在测试环境部署 MySQL 9.7,用一份脱敏数据跑通业务 SQL;
- 本月:完成至少一次全量压测和崩溃恢复演练;
- 本季度:选定一个非核心业务库,完成主从复制升级并切换;
- 今年内:制定核心库升级计划,逐步完成 MySQL 9.7 迁移。
数据库升级从来不是一蹴而就的事,但每一步扎实的准备,都会让你在真正切换的那一刻更有底气。