MySQL表压缩:用CPU换I/O的秘密武器
这段内容是 MySQL 官方文档 17.9.1.1 节:InnoDB 表压缩概述(Overview of Table Compression),它深入解释了 InnoDB 表压缩的工作原理、优势、限制和内存使用机制。
我们来逐段拆解、翻译并通俗化讲解,让你彻底理解这个技术的核心思想。
🎯 一句话总结
InnoDB 表压缩 通过牺牲少量 CPU 资源,换来 更小的数据存储、更少的磁盘 I/O 和更高的性能,特别适合 读多写少的场景和 SSD 存储。但它要求使用独立表空间(
.ibd
文件),并且在内存中会同时缓存压缩页和解压页,需合理规划 Buffer Pool 大小。
🔍 一、为什么需要压缩?—— 现代数据库的瓶颈转移
文档第一段说:
“Because processors and cache memories have increased in speed more than disk storage devices, many workloads are disk-bound.”
✅ 通俗解释:
- CPU 和内存(RAM)的速度提升远快于磁盘
- 所以现在大多数数据库性能瓶颈不在 CPU,而在 磁盘 I/O(读写速度慢)
📌 比如:
- 从内存读数据:纳秒级(ns)
- 从 SSD 读数据:微秒级(μs)→ 慢 100~1000 倍
- 从 HDD 读数据:毫秒级(ms)→ 慢 10万倍以上!
💡 解决方案:数据压缩
- 数据变小了 → 读写的数据量减少 → I/O 减少 → 性能提升
- 虽然压缩/解压要消耗 CPU,但 CPU 很快,这点开销值得
📈 二、压缩的好处(Trade-off:用 CPU 换 I/O)
“Data compression enables smaller database size, reduced I/O, and improved throughput, at the small cost of increased CPU utilization.”
✅ 压缩的三大收益:
好处 | 说明 |
---|---|
📉 更小的数据库体积 | 磁盘占用减少,节省存储成本 |
🚀 更少的 I/O | 每次读写更少的数据块,尤其对随机读有利 |
📈 更高的吞吐量 | 单位时间内能处理更多请求(TPS/QPS 提升) |
⚖️ 代价:
- 增加 CPU 使用率(压缩和解压需要计算)
📌 关键结论:
只要你的 CPU 不是瓶颈,用 CPU 换 I/O 是非常划算的!
🔧 三、表压缩如何工作?—— 核心机制
1️⃣ 压缩页大小可以小于 innodb_page_size
“An InnoDB table created with ROW_FORMAT=COMPRESSED can use a smaller page size on disk than the configured innodb_page_size value.”
- 默认
innodb_page_size = 16KB
- 启用压缩后,磁盘上的页可以更小,比如 8KB、4KB、2KB、1KB
- 小页 = 每次读写的数据更少 → I/O 更高效
📌 类比:
就像把一本书从“每页 1000 字”改成“每页 500 字”,找内容更快,翻页更轻量。
2️⃣ 如何设置压缩页大小?
通过 KEY_BLOCK_SIZE
参数:
CREATE TABLE t1 (...) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; -- 目标压缩页大小为 8KB
KEY_BLOCK_SIZE
是 目标压缩大小(单位 KB)- 实际是否能压缩到这个大小,取决于数据内容
📌 注意:
KEY_BLOCK_SIZE
必须 ≤innodb_page_size / 2
(即 ≤ 8KB)- 支持值:1, 2, 4, 8(KB)
3️⃣ 为什么必须用独立表空间?
“The table must be placed in a file-per-table tablespace or general tablespace… system tablespace cannot store compressed tables.”
- 系统表空间(
ibdata1
)是共享的,管理复杂,不支持压缩页 - 只有 独立表空间(每个表一个
.ibd
文件) 或 通用表空间 支持压缩
✅ 所以只要 innodb_file_per_table=ON
(默认开启),就可以用压缩。
📏 四、KEY_BLOCK_SIZE
的选择:不是越小越好!
“The level of compression is the same regardless of the KEY_BLOCK_SIZE value… But if you specify a value that is too small, there is additional overhead…”
✅ 重要理解:
KEY_BLOCK_SIZE
只是 目标大小,不是压缩率- 压缩算法(
zlib
)的压缩率是固定的,与KEY_BLOCK_SIZE
无关
❗ 问题:设得太小会怎样?
如果 KEY_BLOCK_SIZE
太小(比如 1KB),但数据压缩后仍大于 1KB:
- InnoDB 无法将多行数据塞进一页
- 导致 页分裂(page split) 或 频繁重组
- 增加 CPU 和 I/O 开销,性能反而下降
🚫 硬限制:
KEY_BLOCK_SIZE
不能小于表中 所有索引键的总长度- 否则
CREATE TABLE
或ALTER TABLE
会失败
📌 示例:
CREATE TABLE t1 (id BIGINT PRIMARY KEY,name VARCHAR(255),INDEX idx_name (name)
) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=1;
→ 可能失败,因为 name
索引键太长,1KB 放不下。
💾 五、内存中如何管理压缩页?—— Buffer Pool 的双重缓存
这是最关键的部分!
“In the buffer pool, the compressed data is held in small pages… MySQL also creates an uncompressed page…”
✅ 内存中的工作机制:
步骤 | 说明 |
---|---|
1️⃣ 读取数据 | 从磁盘读取压缩页(如 8KB)→ 放入 Buffer Pool |
2️⃣ 解压 | 创建一个 16KB 的解压页(原始页大小)用于读取或修改 |
3️⃣ 使用 | 所有 SQL 操作都在这个 解压页 上进行 |
4️⃣ 写回 | 修改后,更新 压缩页,并标记为“脏页” |
5️⃣ 淘汰 | 当 Buffer Pool 不够时,解压页被释放,保留压缩页 |
🖼️ 图示:
Buffer Pool 内存:[ 压缩页 8KB ] ←→ [ 解压页 16KB ]↑ ↑磁盘存储 用于读写操作
⚠️ 对 Buffer Pool 的影响
- 同一个数据,在内存中可能同时存在 压缩页 + 解压页
- 占用更多内存
- 需要 更大的 Buffer Pool 来避免频繁淘汰和重新解压
📌 但 MySQL 会智能管理:
- 解压页是“临时”的,空间紧张时会被淘汰
- 下次访问时再从压缩页重新解压
✅ 六、适用场景推荐
场景 | 是否适合压缩 |
---|---|
✅ 读多写少(如报表、历史数据) | 强烈推荐 |
✅ SSD 存储 | 推荐(减少写放大,延长寿命) |
✅ 大表、带索引的表 | 推荐(索引也压缩) |
✅ 内存充足 | 推荐(能容纳更多数据) |
❌ 高频更新的热表 | 谨慎使用(CPU 和重组开销) |
❌ 小表(< 100MB) | 不推荐(收益小) |
❌ 已加密或二进制数据 | 不推荐(难压缩) |
✅ 总结:关键要点
要点 | 说明 |
---|---|
🔹 压缩目的 | 用 CPU 换 I/O,提升性能 |
🔹 启用方式 | ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=N |
🔹 支持条件 | 必须是 file-per-table 或通用表空间 |
🔹 压缩页大小 | 由 KEY_BLOCK_SIZE 控制(1/2/4/8KB) |
🔹 不是越小越好 | 太小会导致页重组,性能下降 |
🔹 内存机制 | Buffer Pool 同时缓存压缩页和解压页 |
🔹 Buffer Pool 需求 | 可能需要更大内存 |
🔹 推荐场景 | 读密集、SSD、大表、归档数据 |
💡 一句话建议:
如果你的表大、读得多、磁盘是 SSD,且内存充足,启用表压缩(
ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8
) 是一个性价比极高的优化手段。但记得监控 CPU 和压缩效率,避免适得其反。
需要我帮你写一个 SQL 脚本,分析当前数据库中哪些表最适合启用压缩吗?