数据的地图 —— 表空间与数据目录
前面我们讲了数据存在”页”里,页通过链表串起来,再通过B+树索引加速查找。但这些页到底放在磁盘上的什么位置?一个.ibd文件内部是怎么组织的?今天我们来看看InnoDB表空间的”地图”。
📋 开篇自测:你已经知道多少?
- MySQL的数据文件默认存放在哪个目录下?这个目录叫什么?
- 一张InnoDB表对应磁盘上的哪些文件?
- 表空间内部除了”页”,还有哪些更大的管理单位?
一、数据目录——MySQL的家在哪儿
MySQL把所有的数据文件都放在一个目录里,这个目录叫数据目录(Data Directory)。
-- 查看MySQL的数据目录在哪里
SHOW VARIABLES LIKE 'datadir';
常见的默认路径:
- Linux:
/var/lib/mysql/ - macOS(Homebrew):
/usr/local/var/mysql/或/opt/homebrew/var/mysql/ - Windows:
C:\ProgramData\MySQL\MySQL Server 8.0\Data\
让我们看看这个目录里有什么:
ls -la /var/lib/mysql/
你会看到类似这样的结构:
/var/lib/mysql/
├── mysql/ # MySQL系统数据库
├── performance_schema/ # 性能监控数据库
├── sys/ # 系统视图数据库
├── my_database/ # 你创建的数据库
│ ├── users.ibd # users表的数据和索引
│ ├── orders.ibd # orders表的数据和索引
│ └── ...
├── ibdata1 # 系统表空间文件
├── ib_logfile0 # redo日志文件
├── ib_logfile1 # redo日志文件
├── undo_001 # undo表空间文件
├── undo_002 # undo表空间文件
├── mysql.ibd # MySQL 8.0的数据字典
└── ...
注意关键一点:每个数据库对应一个子目录,每张InnoDB表对应该目录下的一个.ibd文件。
数据库的物理表示
当你执行CREATE DATABASE shop;时,MySQL其实就是在数据目录下创建了一个名为shop的子目录。当你执行DROP DATABASE shop;时,它就删掉了这个目录及其中的所有文件。
CREATE DATABASE shop;
-- 文件系统效果:创建了 /var/lib/mysql/shop/ 目录
USE shop;
CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(100));
-- 文件系统效果:创建了 /var/lib/mysql/shop/products.ibd 文件
DROP TABLE products;
-- 文件系统效果:删除了 products.ibd 文件
🤔 想一想 如果你直接在文件系统中删除了一个
.ibd文件(而不是通过SQL的DROP TABLE),MySQL还能正常工作吗?会发生什么?
二、表空间——数据的组织层级
在InnoDB中,“表空间(Tablespace)“是一个逻辑概念,它代表了一组数据页的集合。你可以把表空间想象成一个大型仓库,里面有很多货架(区),每个货架有很多格子(页)。
系统表空间 vs 独立表空间
InnoDB有两种主要的表空间:
系统表空间(System Tablespace)
对应文件ibdata1(可能还有ibdata2等)。在MySQL早期版本中,所有表的数据都混在这一个文件里。就像把所有部门的货物都堆在同一个大仓库——管理起来很混乱。
系统表空间现在主要用来存储:
- InnoDB的内部数据字典信息
- 双写缓冲区(Doublewrite Buffer)
- 变更缓冲区(Change Buffer)
- undo日志(在某些配置下)
独立表空间(File-Per-Table Tablespace)
从MySQL 5.6.6开始默认启用innodb_file_per_table=ON。每张表的数据和索引存放在各自独立的.ibd文件中。就像给每个部门分配了独立的仓库——清晰、独立、便于管理。
-- 查看是否启用了独立表空间
SHOW VARIABLES LIKE 'innodb_file_per_table';
-- 默认值:ON
-- 查看所有表空间信息
SELECT * FROM information_schema.INNODB_TABLESPACES
WHERE NAME NOT LIKE 'mysql/%' AND NAME NOT LIKE 'sys/%' LIMIT 10\G
独立表空间的好处:
- 删除表时磁盘空间可以真正释放(直接删除.ibd文件)
- 可以把不同表放在不同磁盘上
- 便于单表备份和恢复
- 避免系统表空间无限膨胀
三、表空间的内部结构——从页到区到段
一个.ibd文件(独立表空间)内部有着清晰的层级结构。从小到大:
页(Page)→ 区(Extent)→ 段(Segment)→ 表空间(Tablespace)
页(Page)—— 最小管理单位
我们已经很熟悉了:16KB一个页。这是InnoDB进行磁盘I/O的最小单位。
页有很多种类型:
- 数据页(INDEX Page):存储行记录,也就是B+树的节点
- Undo页:存储undo日志
- 系统页:存储系统信息
- 空闲页:尚未使用的页
- 更多类型:Insert Buffer位图页、Inode页等
区(Extent)—— 64个连续页
一个区由64个连续的页组成,大小为64 × 16KB = 1MB。
为什么要有”区”这个概念?
想象你在一个停车场找车位。如果你的车队有10辆车,你肯定希望它们停在一起(连续车位),而不是分散在停车场各处。顺序排列意味着磁盘可以做顺序读取,比随机读取快得多。
InnoDB在分配空间时,会尽量以区为单位分配连续的页。这样做范围查询时(比如SELECT * FROM orders WHERE create_time > '2025-01-01'),可以顺序读取连续的数据页,充分利用磁盘的顺序I/O优势。
段(Segment)—— 逻辑分组
段是一个逻辑概念,由若干个区组成。InnoDB为每个索引分配两个段:
- 叶子节点段(Leaf Node Segment):存放B+树的叶子节点(包含实际数据)
- 非叶子节点段(Non-Leaf Node Segment):存放B+树的内部节点(索引导航用)
为什么要把叶子节点和非叶子节点分开存放?
因为非叶子节点是”导航用的路标”,被频繁访问,应该尽可能缓存在内存中。叶子节点是”实际的数据”,数据量大得多。把它们分开,有助于内存管理和I/O优化。
打个比方:把书的目录单独装订成一本小册子(非叶子节点段),把正文装成一本厚书(叶子节点段)。你经常翻的是目录小册子,它小巧、可以随身带(缓存在内存中);正文太厚,需要时再去书架上找(从磁盘读取)。
碎片区——小表的优待
如果每个段至少分配一个区(1MB),那么创建一张只有几条记录的小表也至少要占2MB(叶子节点段1MB + 非叶子节点段1MB)。这太浪费了。
InnoDB的解决方案是碎片区(Fragment Extent)。对于数据量很小的表,InnoDB先从碎片区中分配单独的页,而不是分配整个区。当某个段的数据量增长到一定程度(约32页),才开始以完整的区为单位分配空间。
小表(几行数据):
叶子节点段 → 碎片区中的1-2个页
非叶子节点段 → 碎片区中的1个页
大表(百万行数据):
叶子节点段 → 数百个完整的区
非叶子节点段 → 几个完整的区
⚠️ 常见误区 误区:DELETE大量数据后,.ibd文件会变小。 实际上,InnoDB删除数据只是标记删除,不会把磁盘空间归还给操作系统。.ibd文件的大小只增不减(除非你执行
OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB来重建表)。这就像你从仓库搬走了一半的货物,但仓库的建筑面积并不会缩小——只是多了很多空货架。
四、数据字典——MySQL的”户口本”
MySQL需要记录很多元信息:有哪些数据库、每个数据库有哪些表、每张表有哪些列、列的类型是什么、有哪些索引……这些元信息统称为数据字典(Data Dictionary)。
MySQL 8.0之前
数据字典信息分散在多个地方:
.frm文件:存储表的定义(列、索引等)- 系统表空间(ibdata1)中的内部表
information_schema:一组虚拟表,从各处汇总元信息
MySQL 8.0之后
MySQL 8.0做了一个重大改进:把所有数据字典信息统一存储在InnoDB表中(mysql.ibd文件)。不再有.frm文件。
-- 查看数据库列表(数据来自数据字典)
SELECT * FROM information_schema.SCHEMATA;
-- 查看某个数据库下的表
SELECT TABLE_NAME, ENGINE, TABLE_ROWS, DATA_LENGTH
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_database';
-- 查看某张表的列信息
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'your_database' AND TABLE_NAME = 'your_table';
-- 查看某张表的索引信息
SELECT INDEX_NAME, COLUMN_NAME, SEQ_IN_INDEX, NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'your_database' AND TABLE_NAME = 'your_table';
🤔 想一想 为什么MySQL 8.0要把数据字典从文件系统(.frm文件)迁移到InnoDB内部?这种改变带来了哪些好处?
五、redo日志文件与其他系统文件
数据目录中除了表空间文件,还有一些重要的系统文件:
redo日志文件
redo日志是保障事务持久性的关键文件(后面的章节会详细讲)。
自 MySQL 8.0.30 起,InnoDB redo 日志架构发生了重大变更。文件命名从 ib_logfile0、ib_logfile1 改为 #innodb_redo/ 目录下的 #ib_redoN 文件,容量由统一的 innodb_redo_log_capacity 变量控制(默认 100MB)。
# MySQL 8.0.30+
#innodb_redo/
├── #ib_redo0
├── #ib_redo1
└── ...
# MySQL 8.0.29 及更早版本
ib_logfile0
ib_logfile1
-- MySQL 8.0.30+:查看redo日志总容量
SHOW VARIABLES LIKE 'innodb_redo_log_capacity';
-- MySQL 8.0.29 及更早版本(这两个变量在 8.0.30+ 中已废弃)
-- SHOW VARIABLES LIKE 'innodb_log_file_size';
-- SHOW VARIABLES LIKE 'innodb_log_files_in_group';
undo表空间文件
undo_001
undo_002
undo表空间存储undo日志,用于事务回滚和MVCC。MySQL 8.0默认创建两个undo表空间。
临时表空间文件
ibtmp1 # 全局临时表空间
temp_1.ibt, temp_2.ibt # 会话级临时表空间
临时表空间用于存储临时表和排序操作的中间结果。
二进制日志文件(binlog)
binlog.000001
binlog.000002
binlog.index
binlog记录了所有修改数据的SQL语句,用于主从复制和数据恢复。注意binlog是MySQL Server层面的,不是InnoDB特有的。
-- 查看binlog是否开启
SHOW VARIABLES LIKE 'log_bin';
-- 查看当前的binlog文件列表
SHOW BINARY LOGS;
-- 查看某个binlog文件的内容
SHOW BINLOG EVENTS IN 'binlog.000001' LIMIT 20;
慢查询日志
slow_query.log
记录执行时间超过阈值的SQL语句,是性能优化的重要参考。
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
-- 设置慢查询阈值(单位:秒)
SET GLOBAL long_query_time = 1;
-- 查看慢查询日志路径
SHOW VARIABLES LIKE 'slow_query_log_file';
六、文件与物理布局的全景图
让我们把整个MySQL的文件体系串起来看:
数据目录 (datadir)
│
├── [系统文件]
│ ├── ibdata1 ← 系统表空间(双写缓冲区、变更缓冲区等)
│ ├── mysql.ibd ← 数据字典(MySQL 8.0+)
│ ├── #innodb_redo/ ← redo日志(8.0.30+,旧版为ib_logfile0/1)
│ ├── undo_001/002 ← undo表空间
│ ├── ibtmp1 ← 临时表空间
│ ├── binlog.000001/... ← binlog日志
│ └── slow_query.log ← 慢查询日志
│
├── [数据库A的目录]
│ ├── table1.ibd ← 表1的独立表空间(数据+索引)
│ ├── table2.ibd ← 表2的独立表空间
│ └── ...
│
└── [数据库B的目录]
├── table3.ibd
└── ...
每个.ibd文件内部:
.ibd文件
└── 表空间
├── 段(叶子节点段)
│ ├── 区1(64个连续页)
│ │ ├── 页1(16KB)← 存若干行记录
│ │ ├── 页2(16KB)
│ │ └── ...(共64页)
│ ├── 区2
│ └── ...
└── 段(非叶子节点段)
├── 区1
└── ...
这就是MySQL数据从逻辑概念到物理文件的完整映射。理解了这个全景图,很多看似神秘的MySQL行为(比如为什么删除数据后磁盘空间不释放、为什么OPTIMIZE TABLE会重建表文件等)就都有了解释。
🤔 想一想 如果你需要把一张大表从一台MySQL服务器迁移到另一台,有哪些方法?其中哪种方法和我们今天学的文件结构直接相关?
📝 掌握度自测
- MySQL的数据目录(datadir)存放了哪些类型的文件?每种文件的作用是什么?
- 系统表空间(ibdata1)和独立表空间(.ibd文件)各存储什么内容?为什么推荐使用独立表空间?
- 表空间内部的层级结构是什么?页、区、段分别是什么大小、什么作用?
- 为什么InnoDB要把B+树的叶子节点和非叶子节点放在不同的段中?
- DELETE大量数据后,.ibd文件的大小会变化吗?如何真正回收磁盘空间?
💡 自我评估
- 答对5题:对MySQL的物理存储有了全面理解,可以处理存储层面的运维问题了
- 答对3-4题:核心概念清楚,建议动手在文件系统中观察一下MySQL的数据目录结构
- 答对0-2题:建议对照全景图再梳理一遍,重点理解”逻辑概念到物理文件”的映射关系
购买课程解锁全部内容
让查询飞起来:MySQL 从索引到主从高可用
¥29.90