InnoDB¶
InnoDB 不是独立的数据库,而是 MySQL 的一种存储引擎。MySQL 采用插件式存储引擎架构,同一张表可以用不同引擎存储数据,InnoDB 是自 MySQL 5.5 起的默认引擎
graph TD
A[MySQL Server 层<br/>连接/解析/优化/执行] --> B[存储引擎层]
B --> B1[InnoDB 默认]
B --> B2[MyISAM]
B --> B3[Memory]
B --> B4[...]
| 特性 | 说明 |
|---|---|
| 事务(ACID) | 完整支持事务,保证原子性、一致性、隔离性、持久性 |
| 行级锁 | 锁粒度细,高并发写入不互相阻塞 |
| MVCC | 多版本并发控制,读不阻塞写 |
| 外键约束 | 支持外键,保证参照完整性 |
| 崩溃恢复 | 通过 redo log 保证数据不丢失 |
| 聚簇索引 | 主键索引和数据存储在一起 |
1 数据存储结构¶
1.1 页(Page)¶
InnoDB 以 页(默认 16KB) 为单位读写磁盘,是内存和磁盘交互的最小单位
graph TD
A[表空间 Tablespace] --> B[段 Segment]
B --> C[区 Extent = 64个页]
C --> D[页 Page 16KB]
D --> E[行 Row]
1.2 聚簇索引(B+ 树)¶
InnoDB 的表数据本身就是一个 B+ 树,按主键组织:
- 叶子节点存整行数据(不是指针)
- 没有显式主键时,InnoDB 会找唯一非空索引,否则生成隐藏的
row_id
graph TD
A["非叶子节点<br/>只存主键+指针"] --> B["叶子节点1"]
A --> C["叶子节点2"]
B --> B1["id=1, 整行数据"]
C --> C1["id=2, 整行数据"]
1.3 二级索引与回表¶
非主键索引的叶子节点存的是 主键值,查到主键后还要去聚簇索引找整行,称为 回表
2 内存结构¶
InnoDB 最重要的内存区是 Buffer Pool(缓冲池):
graph LR
A[查询请求] --> B[Buffer Pool<br/>内存缓存的数据页]
B -->|命中| C[直接返回]
B -->|未命中| D[从磁盘读入缓冲池]
E[修改数据] -->|先改缓冲池| B
B -->|脏页| F[后台刷盘]
| 内存组件 | 作用 |
|---|---|
| Buffer Pool | 缓存数据页和索引页,减少磁盘 IO(核心) |
| Change Buffer | 缓存二级索引的写操作,合并后批量更新 |
| Log Buffer | 暂存 redo log,刷到磁盘 |
| Adaptive Hash Index | 对热点数据自动建哈希索引加速 |
3 日志与崩溃恢复¶
InnoDB 通过 redo log + undo log + binlog 保证持久性和一致性:
| 日志 | 作用 | 类比 |
|---|---|---|
| redo log | 物理日志,记录"对哪个页做了什么修改",用于 崩溃恢复 | 记"草稿",保证已提交不丢 |
| undo log | 逻辑日志,记录"修改前的值",用于 回滚 + MVCC | 记"撤销",保证能反悔 |
| binlog | Server 层日志,记录 SQL 语句,用于 主从复制/数据恢复 | 记"流水账" |
为什么需要 redo log(WAL 机制)
先写日志再写数据页(Write-Ahead Logging):
- 事务提交时,先写 redo log
- 数据页之后由后台线程慢慢刷盘
- 若中途崩溃,重启后用 redo log 重放 未刷盘的修改
graph LR
A[事务修改] --> B[写 redo log<br/>顺序写, 快]
B --> C[返回提交成功]
A --> D[改 Buffer Pool 中的脏页]
D --> E[后台异步刷盘]
4 事务与隔离级别¶
InnoDB 支持四种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现 |
|---|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | 无锁 |
| READ COMMITTED | ❌ | ✅ | ✅ | 每次读生成新 ReadView |
| REPEATABLE READ(默认) | ❌ | ❌ | ❌ | 事务级 ReadView + 间隙锁 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 全加锁 |
InnoDB 在 RR 级别通过 **间隙锁(Gap Lock)* 基本解决了幻读
5 MVCC(多版本并发控制)¶
InnoDB 通过 undo log + ReadView 实现快照读,让读操作不加锁:
- 每行记录有隐藏字段:
trx_id(事务ID)、roll_pointer(指向 undo log) - 读时生成 ReadView,判断哪些版本对当前事务可见
- 不同事务看到不同版本,互不阻塞
graph LR
A[行数据 v3<br/>trx_id=103] -->|roll_pointer| B[undo log v2<br/>trx_id=102]
B -->|roll_pointer| C[undo log v1<br/>trx_id=101]
6 锁机制¶
InnoDB 的锁:
| 锁类型 | 粒度 | 说明 |
|---|---|---|
| 共享锁(S) | 行 | 读锁,多个事务可同时持有 |
| 排他锁(X) | 行 | 写锁,独占 |
| 意向锁(IS/IX) | 表 | 表级,用于协调行锁和表锁 |
| 间隙锁(Gap Lock) | 范围 | 锁住索引记录之间的间隙,防幻读 |
| 临键锁(Next-Key Lock) | 行+间隙 | 默认的行锁形式 |
InnoDB vs MyISAM
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务 | ✅ 支持 | ❌ 不支持 |
| 锁粒度 | 行级锁 | 表级锁 |
| 外键 | ✅ 支持 | ❌ 不支持 |
| 崩溃恢复 | ✅ redo log | ❌ 易损坏 |
| 全文索引 | 5.6+ 支持 | 支持(早期更强) |
| 数据存储 | 聚簇索引(表数据+索引同文件) | 数据和索引分开(.MYD/.MYI) |
| 适用 | 事务型、高并发业务 | 只读、日志、数据仓库(已边缘化) |
评论区
欢迎在评论区指出文档错误,为文档提供宝贵意见,或写下你的疑问