MySQL 数据库 7(Day55)

主题:InnoDB 存储层次(行/页/区/段/表空间)与 .ibd 文件组成、共享表空间 vs 独立表空间、不停服在线迁移表(传输表空间)、Undo Log 的产生保留与在线自动收缩、索引介绍(六大类型/聚簇与回表/最左匹配)与索引实验数据准备


结构总览

MySQL 数据库 7
  ├─ 温故知新:数据存储层次(应用内存 / OS Cache / 磁盘)
  ├─ 表空间与 .ibd 文件组成:行→页(16KB)→区(64页≈1MB)→段→表空间
  ├─ 共享表空间 vs 独立表空间(innodb_file_per_table)
  ├─ 在线迁移表:FOR EXPORT → 复制 .ibd/.cfg → DISCARD → IMPORT
  ├─ Undo Log:回滚 + MVCC、旧版本保留条件、在线自动收缩
  └─ 索引介绍与实验准备:索引类型、B+树定位、EXPLAIN 对比

关键要点

数据存储层次: MySQL 数据从应用程序内存 → 操作系统缓存(OS Cache)→ 磁盘逐层流转。查询时 InnoDB 先查缓冲池(Buffer Pool),未命中再经 OS Cache 读磁盘数据页;修改通常先改内存中的数据页,通过 Redo Log 保证可恢复性,之后再刷脏页落盘。

SHOW VARIABLES LIKE 'innodb_buffer_pool_size';  -- 缓冲池大小
SHOW VARIABLES LIKE 'datadir';                  -- 数据目录
SHOW VARIABLES LIKE 'innodb_log%';              -- 日志相关配置

排错:不要以为 UPDATE 后数据立即写入 .ibd 文件(观察文件修改时间不可靠);加内存不会让所有查询永久变快——还要看缓冲池命中率、是否走索引、磁盘与锁等待。

表空间层次结构与 .ibd 文件组成:

行 Row(一条记录)
  ↓ 多行记录组成
页 Page:默认 16 KB,一次磁盘 I/O 的基本单位
  ↓ 64 个连续页组成
区 Extent:16KB × 64 = 1 MB
  ↓ 多个区组成
段 Segment(管理数据段或索引段)
  ↓ 多个段及管理信息组成
表空间 Tablespace → 以 .ibd 文件等形式保存

一页能存多少行不固定——由行记录大小、变长字段和页内管理信息共同决定,行越小存得越多。Compact 行格式(MySQL 5.0 引入)保存记录头信息、变长字段长度、空值信息和实际列数据,比旧格式更省空间。

SHOW VARIABLES LIKE 'innodb_page_size';   -- 默认 16384(16KB)
SHOW TABLE STATUS LIKE 't_space';         -- 表的引擎/行数/空间
SHOW CREATE TABLE t_space;

排错:「每页固定存 7992 行」是错误理解;「一个区只属于一张表」也不对——区是空间分配单位,由段和表空间统一管理;禁止直接删除 .ibd 文件删表,必须用 DROP TABLE / TRUNCATE TABLE 保持数据字典一致。

共享表空间 vs 独立表空间:

对比项目共享表空间独立表空间
存储方式多张表共用系统表空间(如 ibdata1)每表一个 .ibd 文件
文件管理集中管理可按表管理
删表后空间通常不归还操作系统删表/重建可释放文件空间
表迁移不便单独迁移支持按表导出导入
配置innodb_file_per_table=OFFinnodb_file_per_table=ON(现代默认)

关键认知:

SHOW VARIABLES LIKE 'innodb_file_per_table';
 
-- 查看表的数据/索引空间与空闲碎片
SELECT table_schema, table_name, engine, table_rows,
       data_length, index_length, data_free
FROM information_schema.tables
WHERE table_schema = 'tablespace_demo';

⚠️ 修改 innodb_file_per_table 只影响新建表,已有表要靠 ALTER TABLE ... ENGINE=InnoDB 重建迁移。DELETE 只删逻辑记录,.ibd 文件不会自动缩小——空间留给该表复用,要归还操作系统需重建表。

在线迁移表(不停服,传输表空间): 前提是源端与目标端表结构完全一致且都是 InnoDB。流程:源端锁表导出 → 复制文件 → 目标端丢弃表空间 → 放入文件 → 导入。

-- ① 源端:刷新表并获取元数据锁(生成 .cfg + 锁定写入)
FLUSH TABLES user_account FOR EXPORT;
 
-- ② shell:复制 .ibd 和 .cfg 到目标机器
-- cp /var/lib/mysql/source_db/user_account.{ibd,cfg} /backup/
-- scp 后放到目标库目录并 chown mysql:mysql
 
-- ③ 源端:复制完成后解锁
UNLOCK TABLES;
 
-- ④ 目标端:建好结构一致的空表后,丢弃本地表空间
ALTER TABLE user_account DISCARD TABLESPACE;
 
-- ⑤ 文件就位后导入表空间并验证
ALTER TABLE user_account IMPORT TABLESPACE;
SELECT * FROM user_account;

排错:IMPORT 报表空间不存在/文件错误 → 确认已先 DISCARD、检查文件路径属主权限;只拷了 .ibd 没拷 .cfg → 传统传输表空间两者都要;迁移期间源端仍有写入 → 必须在 FOR EXPORT 之后才能拷贝。结构不一致是最常见失败原因,用 SHOW CREATE TABLE 逐一比对字段顺序/类型/索引/字符集。

Undo Log 的产生、保留与在线自动收缩: Undo 记录修改前的数据版本,两大用途——①事务回滚恢复原值;②MVCC 让其他事务按隔离级别读到旧版本。事务提交后 Undo 不一定立即删除:只要还有活动事务需要读旧版本,就必须保留;没有读者时才具备回收条件,空间达到阈值后由后台执行自动截断收缩。

SHOW VARIABLES LIKE 'innodb_undo%';                 -- Undo 配置
SELECT * FROM information_schema.innodb_trx;        -- 当前事务(重点看开始时间)
SELECT * FROM performance_schema.data_lock_waits;   -- 锁等待
SHOW PROCESSLIST;
[mysqld]
innodb_undo_tablespaces=2          # 独立 Undo 表空间个数
innodb_undo_log_truncate=ON        # 开启在线自动收缩
innodb_max_undo_log_size=1073741824  # 超过 1GB 触发截断

排错:提交后 Undo 空间没变小 → 查长事务/未提交事务(长事务是 Undo 膨胀头号原因);Undo 文件大就直接删文件是绝对禁止的——由 InnoDB 管理回收;开启 truncate 后也不会立即收缩,要满足大小阈值、版本清理和后台任务条件。

索引介绍: 索引(Index,也称 Key)是加速查找的数据结构。无索引时从第一行逐行比对(全表扫描 Full Table Scan);有索引时先在索引结构中定位,再按定位信息取数据,大幅减少扫描范围。

索引类型特点
主键索引主键约束自动创建,唯一 + 非空;InnoDB 中即聚簇索引
唯一索引值不重复,允许 NULL
普通索引无唯一性要求,纯加速
联合索引多字段组成,如 (name, age),遵循最左匹配
全文索引文本搜索场景
空间索引空间数据类型

InnoDB 主键索引是聚簇索引(数据按主键组织);二级(普通)索引叶子节点只存索引字段 + 主键值,查完整记录需按主键回表到聚簇索引再取一次。

CREATE INDEX idx_user_info_name ON user_info (name);      -- 普通
CREATE UNIQUE INDEX uk_user_name ON user_info (name);     -- 唯一
CREATE INDEX idx_city_age ON user_info (city, age);       -- 联合
DROP INDEX idx_city_age ON user_info;                     -- 删除
 
EXPLAIN SELECT * FROM user_info WHERE name = 'egon';      -- 看是否走索引
SHOW INDEX FROM user_info;

最左匹配示例(联合索引 (name, age, city)):WHERE name=.. ✅、WHERE name=.. AND age=.. ✅、只有 WHERE age=.. ❌(缺最左列)。其他失效场景:对索引字段做函数运算、前置模糊 LIKE '%egon'(LIKE 'egon%' 可以走)、字段类型不一致引发隐式转换。索引不是越多越好——占空间且增加 INSERT/UPDATE/DELETE 维护成本。

索引实验数据准备要点: 数据量不能太小(否则优化器认为全表扫描更快);条件字段要有足够重复值才能观察过滤效果;用 EXPLAIN 看优化器是否选了索引(关注 type/key/rows/Extra);建索引前后 SQL 保持一致。

当日总结

  • 数据在应用内存 → OS Cache → Buffer Pool → 磁盘间流转;性能取决于内存命中率与磁盘 I/O 次数
  • InnoDB 层次:行 → 页(16KB,I/O 基本单位)→ 区(64 连续页 ≈ 1MB)→ 段 → 表空间(.ibd 文件)
  • 页容纳行数随记录大小变化;区是分配单位,不专属某张表;禁止直接删 .ibd 删表
  • 共享表空间多表共用以至难迁移;独立表空间(innodb_file_per_table=ON)每表一个文件,便于管理与迁移
  • DELETE 不缩文件;改 file_per_table 只影响新表;回收空间靠 ALTER TABLE 重建
  • 在线迁移五步:FLUSH … FOR EXPORT → 拷 .ibd+.cfg → UNLOCK → DISCARD → IMPORT,前提两端表结构一致
  • Undo 服务回滚 + MVCC;提交后旧版本被活动事务依赖就不能回收;长事务导致 Undo 膨胀;innodb_undo_log_truncate=ON 自动收缩
  • 索引六类:主键/唯一/普通/联合/全文/空间;二级索引叶子存主键需回表;联合索引最左匹配;函数运算/前置 %/隐式转换使索引失效
  • EXPLAIN 对比建索引前后执行计划是验证手段;小表优化器可能放弃索引

相关页面