Skip to content

MySQL 架构与存储引擎

概念

本节覆盖 MySQL 的逻辑分层架构、一条 SQL 语句从发出到返回结果的完整链路、InnoDB 与 MyISAM 的核心差异,以及 InnoDB 的 Buffer Pool 和存储结构。这些内容是理解锁、事务、索引等高频考点的基础。


核心原理

1. MySQL 逻辑架构分层

┌─────────────────────────────────────────────┐
│                 客户端 (Client)               │
└───────────────────┬─────────────────────────┘
                    │ TCP / Unix Socket
┌───────────────────▼─────────────────────────┐
│              连接器 (Connector)               │
│  · 身份验证 · 权限查询 · 连接池管理            │
└───────────────────┬─────────────────────────┘

┌───────────────────▼─────────────────────────┐
│   查询缓存 (Query Cache) — MySQL 8.0 已移除   │
│  · 命中则直接返回,否则继续                    │
└───────────────────┬─────────────────────────┘

┌───────────────────▼─────────────────────────┐
│              分析器 (Parser)                  │
│  · 词法分析 · 语法分析 · 生成解析树            │
└───────────────────┬─────────────────────────┘

┌───────────────────▼─────────────────────────┐
│              优化器 (Optimizer)               │
│  · 选择索引 · 决定 JOIN 顺序 · 生成执行计划    │
└───────────────────┬─────────────────────────┘

┌───────────────────▼─────────────────────────┐
│              执行器 (Executor)                │
│  · 权限校验 · 调用存储引擎接口 · 组装结果集    │
└───────────────────┬─────────────────────────┘

┌───────────────────▼─────────────────────────┐
│           存储引擎 (Storage Engine)           │
│     InnoDB  /  MyISAM  /  Memory  / …        │
│  · 数据读写 · 索引管理 · 事务 · 锁            │
└─────────────────────────────────────────────┘

Server 层(连接器→执行器)与存储引擎层通过存储引擎 API 解耦,可插拔替换。


2. SELECT 语句完整执行流程

客户端发送 SQL


连接器:验证用户名密码,加载权限


查询缓存(8.0 已移除):
    命中 ──▶ 直接返回结果
    未命中 ──▶ 继续


分析器:词法 + 语法解析,识别表名、列名


优化器:选最优索引,生成执行计划


执行器:调用存储引擎接口逐行读取


存储引擎:从 Buffer Pool 或磁盘读取数据页


返回结果集给客户端

3. UPDATE 语句完整执行流程(含 redo log / binlog)

执行 UPDATE t SET c=c+1 WHERE id=1


执行器找到 id=1 的行(先查 Buffer Pool,未命中则从磁盘读入)


执行器调用 InnoDB 写接口,修改内存中的数据页


InnoDB 写 redo log(prepare 状态)
    │   ← WAL(Write-Ahead Logging)保证崩溃恢复

Server 层写 binlog(追加到 binlog 文件)
    │   ← 用于主从复制、数据恢复

InnoDB 将 redo log 标记为 commit 状态


事务提交,返回执行成功


(后台)脏页异步刷回磁盘

两阶段提交(prepare → binlog → commit)确保 redo log 与 binlog 的一致性,防止主从数据不一致。


4. InnoDB vs MyISAM 核心对比

特性InnoDBMyISAM
事务支持✅ 支持(ACID)❌ 不支持
锁粒度行锁(+ 表锁)表锁
崩溃恢复✅ redo log 自动恢复❌ 需手动修复
外键✅ 支持❌ 不支持
MVCC✅ 支持(快照读)❌ 不支持
索引类型聚簇索引(主键即数据)非聚簇索引(索引与数据分离)
全文索引✅(5.6+)
Count(*) 性能较慢(需扫描)很快(维护行数变量)
适用场景OLTP,读写混合,需要事务只读 / 读多写少,统计类

现代项目默认选 InnoDB。MyISAM 仅在极少数只读+高频 COUNT(*) 场景有优势,且 MySQL 8.0 系统表也已全部迁移至 InnoDB。


5. Buffer Pool 工作原理

Buffer Pool 是 InnoDB 在内存中维护的数据页缓存,默认页大小 16KB。

磁盘 I/O(慢)                内存 Buffer Pool(快)
┌──────────┐    读入数据页    ┌────────────────────────┐
│  .ibd    │ ─────────────▶  │  LRU 链表               │
│  数据文件 │                 │  ┌────────┬──────────┐  │
└──────────┘                 │  │ New区  │  Old区   │  │
                             │  │ (5/8)  │  (3/8)   │  │
                             │  └────────┴──────────┘  │
                             │  · 新页先进 Old 头部      │
                             │  · 1s 后再次访问才升入 New │
                             │  · Old 尾部页面被淘汰      │
                             └────────────────────────┘

改进 LRU(防止全表扫描污染缓存):

  • 新读入的页先插入 Old 区头部,不直接进 New 区
  • 只有在 Old 区停留超过 innodb_old_blocks_time(默认 1000ms)后再次被访问,才晋升到 New 区头部
  • 避免一次大查询将热点数据全部替换出去

脏页刷新时机:

  1. redo log 快写满时(强制刷脏)
  2. Buffer Pool 内存不足,需要淘汰脏页时
  3. MySQL 空闲时后台线程定期刷新
  4. 正常关闭 MySQL 时

6. InnoDB 存储结构

表空间 (Tablespace / .ibd 文件)
└── 段 (Segment)
    ├── 叶子节点段(存储真实数据行)
    ├── 非叶子节点段(存储索引非叶层)
    └── 回滚段(undo log)
        └── 区 (Extent) — 连续 64 个页 = 1MB
            └── 页 (Page) — 16KB(I/O 最小单元)
                └── 行 (Row) — 实际数据记录
层级大小说明
页 (Page)16 KBB+ 树节点,读写最小单位
区 (Extent)1 MB(64页)顺序分配,减少碎片
段 (Segment)若干区区分叶子节点与非叶子节点
表空间若干段默认每张表一个 .ibd 文件(innodb_file_per_table)

面试常问 & 怎么答

Q1:一条 SQL 语句在 MySQL 中是怎么执行的?

先说分层:MySQL 分为 Server 层和存储引擎层。Server 层包含连接器、分析器、优化器、执行器;存储引擎层负责实际的数据读写。

执行流程:

  1. 连接器验证身份、加载权限
  2. 分析器做词法+语法解析,确认表名列名是否存在
  3. 优化器选择索引、确定 JOIN 顺序,生成执行计划
  4. 执行器调用存储引擎接口,按执行计划逐行读取数据
  5. 存储引擎(InnoDB)先查 Buffer Pool,未命中则从磁盘读入

如果是 UPDATE,还涉及 redo log 的两阶段提交(prepare → 写 binlog → commit)来保证崩溃安全和主从一致。


Q2:InnoDB 和 MyISAM 有什么区别?怎么选?

核心差异:InnoDB 支持事务(ACID)、行锁、外键、MVCC、崩溃自动恢复;MyISAM 都不支持,但 COUNT(*) 更快,因为它维护了一个行数变量。

索引结构也不同:InnoDB 用聚簇索引,主键 B+ 树叶子节点直接存数据行;MyISAM 用非聚簇索引,叶子节点存的是数据文件的物理地址。

怎么选:现代项目几乎都选 InnoDB,因为需要事务、崩溃恢复和更细粒度的锁。只有非常老的只读报表系统或有大量 COUNT(*) 无过滤条件查询的场景才会考虑 MyISAM,MySQL 8.0 已经全面切到 InnoDB。


Q3:Buffer Pool 是什么?为什么需要它?

Buffer Pool 是 InnoDB 在内存中维护的数据页缓存,本质上是一块大内存区域,以 16KB 的页为单位管理。

为什么需要:磁盘 I/O 比内存慢几个数量级。把频繁访问的数据页缓存在内存里,读取时先查 Buffer Pool,写入时也先修改内存(WAL 机制),再异步刷盘,极大提升了读写性能。

内部用改进版 LRU 链表管理页的淘汰,分 New 区(热数据,5/8)和 Old 区(冷数据,3/8),新读入的页先进 Old 区,防止全表扫描把热点数据全部挤出缓存。


看到什么就先想到这类

触发信号联想方向
"SQL 执行慢" / "explain 怎么看"优化器选了什么索引?执行计划哪步最贵?
"崩溃后数据会丢吗"redo log 两阶段提交,Buffer Pool 脏页
"主从数据不一致"binlog 格式(statement/row/mixed),两阶段提交
"为什么用 InnoDB"事务、行锁、MVCC、崩溃恢复
"count(*) 慢"InnoDB 无行数缓存,需全表或索引扫描;考虑用 Redis 计数
"内存命中率低"Buffer Pool 大小配置(innodb_buffer_pool_size),LRU 策略
"表空间碎片"InnoDB 区(Extent)连续分配,OPTIMIZE TABLE 可整理