Skip to content
核心概念与架构

核心概念与架构

MySQL 的三层架构、一条 UPDATE 语句的完整生命周期、InnoDB 的内存与磁盘结构、8.0 数据字典与行格式。这一篇是后面所有章节的地基:索引(04)、事务与锁(05)、MVCC(06)、日志(07)讲到的每个机制,都能在这里找到它"住在架构的哪一层"。


1. 三层架构鸟瞰

1.1 分层全景

把 MySQL 想成一栋三楼公寓:楼下管收发室(连接),二楼管文书处理(SQL 解析与优化),三楼按户主不同雇了不同的管家(存储引擎)。

客户端(mysql CLI / JDBC / go-sql-driver)
   │  TCP 3306 或 unix socket
┌──▼─────────────────────────────────────────────────────┐
│ 连接层                                                  │
│   连接器:TCP 握手、认证(caching_sha2_password)        │
│   权限:user@host 粒度的授权检查                          │
│   线程模型:每连接一个线程;连接池、超时管理              │
├────────────────────────────────────────────────────────┤
│ 服务层(Server 层,所有引擎共享)                        │
│   解析器(词法/语法 → AST)→ 预处理器 → 优化器           │
│   执行器:调用引擎接口逐行取数/改数                       │
│   公共模块:内置函数、字符集、临时表                      │
│   binlog:写在这里——它是 Server 层的产物                 │
├────────────────────────────────────────────────────────┤
│ 存储引擎层(插件式,Handler API 对接服务层)             │
│   InnoDB / MyISAM / Memory / Archive / ...              │
│   redo log、undo、表空间、Buffer Pool 都在引擎内部       │
├────────────────────────────────────────────────────────┤
│ 文件系统 / 裸设备                                        │
└────────────────────────────────────────────────────────┘

注意:很多流传很广的架构图把 Query Cache 画在服务层,那是 5.7 及以前的旧图——8.0 已彻底移除查询缓存(它失效粒度太粗、高并发下mutex 热点,官方宁可不要)。看到带 Query Cache 的图,先对一下版本。

1.2 连接层:每连接一线程的代价

连接器负责三件事:网络握手、认证、把会话挂到权限体系上。8.0 起默认认证插件是 caching_sha2_password——完整 SHA256 + 内存缓存快路径,首次非 TLS 连接走 RSA 公钥交换(这也是 00 章反复提醒 allowPublicKeyRetrieval 的原因)。

线程模型值得单独说一句:MySQL 社区版是每个连接独占一个线程,一千个连接就是一千个潜在活跃线程,栈内存加上下文切换开销可观。企业版/Percona 的 Thread Pool 才是治本方案;社区版靠应用侧连接池把活跃并发压下来(max_connections 配多大,见 00-安装与配置.md 第 7 节)。

1.3 服务层:从 SQL 到执行计划

一条 SQL 进来后依次经过:

  1. 解析器:词法、语法分析生成 AST,语法错误在这一步报 1064。
  2. 预处理器:检查表/列是否存在、解析别名和星号展开——1146 Table doesn't exist 是这里报的,不是解析器。
  3. 优化器:基于代价模型选择索引、决定 join 顺序与算法(8.0.18+ 引入 hash join;CTE、窗口函数自 8.0 起支持)。它是"建议者"不是"决定者",统计信息过时就会选错——纠偏手段见 08-EXPLAIN与查询优化.md。
  4. 执行器:按执行计划调用引擎的 Handler API 逐行取数、回表、聚合、排序,把结果集写回客户端。

1.4 为什么要有存储引擎层

服务层和引擎层之间隔着一套 Handler API:引擎只需实现"打开表、按索引取下一行、写入一行"这类接口,就能即插即用。历史动机很简单——SQL 处理与数据存取是两类完全不同的工程问题,分开演化;现实收益是按场景选型:事务型行存储交给 InnoDB,只追加的归档可以交给 Archive,甚至 MyRocks(LSM 引擎,Percona 系)为写密集场景另辟蹊径。引擎选择在 03-数据类型与建表.md 有落地写法。

分层还解释了一个关键事实:binlog 是 Server 层写的,redo 是 InnoDB 写的。两套日志分属两层、服务两个目标,这是 MySQL 必须做两阶段提交的根因,第 2 节和 07-日志体系.md 都会反复用到这个前提。

1.5 这张三层图怎么用来排障

拿到一个问题,先判断它属于哪一层,能省掉一半瞎试:

  • 连不上、认证失败、ERROR 1040 Too many connections → 连接层。查 max_connections、应用连接池、skip_name_resolve(见 00-安装与配置.md 第 5、7 节)。
  • 1064(语法)、1146(表不存在)、1044/1045(权限)→ 服务层的解析/预处理/权限检查,和引擎无关,和"表数据坏没坏"也无关。
  • SQL 没错但慢 → 先看服务层的执行计划(08-EXPLAIN与查询优化.md),再看引擎层的物理读与锁。计划错了,堆多少内存都救不回来。
  • 1205(锁等待超时)、1213(死锁)、DDL 卡住 → InnoDB 的账,去 05-事务与锁.md;别去重启数据库,重启只会把问题换个姿势再来。

2. 一条 UPDATE 语句的一生

以这条语句为主线:

UPDATE users SET balance = balance - 100 WHERE id = 42;

它的完整旅程:

① 连接器收包(认证、权限在连接建立时就绪)
② 解析器:词法/语法 → AST
③ 预处理器:确认 users 表、balance/id 列存在
④ 优化器:WHERE id = 42 命中主键,代价最低方案 = 走 PRIMARY,const 查找
⑤ 执行器调 handler:读 id=42 所在数据页
     └ Buffer Pool 命中?未命中则从 .ibd 读页(读盘路径靠双写保证页完整,见 5.4)
⑥ InnoDB 修改内存中的行
     ├ 先写 undo 记录(旧值,供回滚与 MVCC 版本链)
     ├ 再写 redo(内存 redo log buffer),并打"prepare"标记 —— 两阶段提交第一步
⑦ Server 层写 binlog(statement/event → binlog cache → 文件,按 sync_binlog 决定 fsync)
⑧ redo 打 commit 标记;返回客户端 OK
     └ 脏页留在 Buffer Pool,由后台线程按水位/checkpoint 择机刷盘(07 章)

2.1 为什么必须两份日志、两阶段提交

redo 管InnoDB 的崩溃恢复(保证脏页没刷盘也能重放),binlog 管归档与主从复制(Server 层、所有引擎共享)。两份日志各写各的,就有一致性裂缝:假设 redo 提交了、binlog 没落盘时宕机——主库重启后数据在,从库却没收到这条变更,主从不一致;反过来 binlog 写了、redo 没提交,这条变更在引擎里不存在却会复制到从库,凭空多出一行。

两阶段提交就是把裂缝钉死:redo prepare → 写 binlog → redo commit。恢复时以"binlog 是否完整写入“为准——redo 里有 prepare 但 binlog 缺对应事务的,回滚;prepare 完整且 binlog 有的,补提交。完整的组提交、binlog_group_commit_sync_delay、崩溃恢复矩阵见 07-日志体系.md,这里先建立"谁配合谁"的骨架。

2.2 SELECT 的路径短得多

只读语句没有 undo/redo/binlog 这一段:走 ①~④ 后,执行器直接读 Buffer Pool(MVCC 决定它"看见哪个版本”,见 06-MVCC.md)。如果命中覆盖索引,连回表都省了——这就是 04-索引原理与实战.md 里"覆盖索引"性能的来源。

2.3 如果这条 UPDATE 没走索引

把 WHERE 换成一个没索引的列,旅程的第 ④⑤⑥ 步会立刻变脸:

-- status 列上没有索引:优化器只能选全表扫描
UPDATE users SET status = 1 WHERE status = 0;

InnoDB 在可重复读隔离级别下,UPDATE 扫描过的每一行都要加锁(next-key lock,原理在 05-事务与锁.md),即使大部分行不满足最终修改条件——这条语句事实上就是一把覆盖全表的写锁,高峰期执行等于给业务埋雷。锁实况可以这样看:

SELECT engine_transaction_id, object_name, lock_type, lock_mode, lock_data
  FROM performance_schema.data_locks
 WHERE object_name = 'users' LIMIT 10;

结论先行:写语句的执行计划质量比读更致命。发布前用 EXPLAIN UPDATE ... 确认走了索引,是 08-EXPLAIN与查询优化.md 会展开的日常动作。


3. InnoDB vs MyISAM:一个时代的分界线

MyISAM 是 5.5 之前 MySQL 的默认引擎,今天只在存量老库里见到。它和 InnoDB 的差距不是"少个事务"这么简单,而是整套数据哲学不同。

维度 InnoDB MyISAM
事务 完整 ACID 无
锁粒度 行锁 + 间隙锁/临键锁(05-事务与锁.md) 表锁:一个 UPDATE 挡全表读写
崩溃恢复 redo + 双写,crash-safe 非 crash-safe,重启靠 REPAIR TABLE,大表修一次业务停半天
存储组织 聚簇索引(数据和主键索引一体) 数据和索引分离(.MYD/.MYI)
COUNT(*) 无 WHERE 每次实际统计(走最小索引) 维护了全表计数,O(1) 返回
全文索引 5.6 起支持 很早就有(历史包袱的来源)
自增列行为 事务回滚不回退自增值 重启后自增值可能"回退"(8.0 之前)

为什么 5.5 起 InnoDB 成为默认:2008 年 Sun 收购 MySQL AB 后做的第一个重大决定,本质是承认"MySQL 想从网页计数器走向核心业务库,崩溃安全和事务是底线"。后来 Oracle 接手继续加码,到今天 InnoDB 几乎等于 MySQL 本身——8.0 连系统表全部换成 InnoDB(5.7 时代 mysql.user 等授权表还是 MyISAM),数据字典事务化也落在 InnoDB 头上(第 6 节)。

注意:MyISAM 的表锁 + 非事务,在并发写入下不只是慢——一条 UPDATE ... SET x = x + 1 撞上读改写混合负载时没有原子性保障,丢更新是真实会发生的。任何新项目看到 ENGINE=MyISAM 都该在 review 里拦下来(存量迁移注意 03-数据类型与建表.md)。

3.1 盘点与迁移存量 MyISAM

先看家里还剩多少:

SELECT table_schema, table_name FROM information_schema.tables
 WHERE engine = 'MyISAM'
   AND table_schema NOT IN ('mysql','sys','performance_schema','information_schema');

迁移动 InnoDB 的标准做法是 ALTER TABLE ... ENGINE = InnoDB,它本质是一次整表重建,锁表时长和数据量同阶;大表要走 09-Online-DDL与大表变更.md 的通道(Online DDL 或 pt-online-schema-change)。迁移后有两个行为差异要提前告诉业务方:COUNT(*) 无 WHERE 的 O(1) 没了,大表计数要么缓存要么走汇总表;全文索引语法不变但打分模型不同,相关搜索结果可能有出入。


4. InnoDB 内存结构

InnoDB 的性能一半取决于内存里那四样东西。开关与容量参数在 11-备份恢复与性能调优.md 细调,这里讲清楚各自"解决什么问题"。

4.1 Buffer Pool:数据页缓存

磁盘以 16KB 页为最小读写单位,InnoDB 不缓存"行",缓存的是整页(数据页 + 索引页)。所有读写先落到 Buffer Pool,脏页异步回刷。

  • innodb_buffer_pool_size 是实例最重要的单个参数;专用服务器给物理内存的 50%~70%。缓存命中率直接决定磁盘读量:SHOW ENGINE INNODB STATUS 里 Buffer pool hit rate 低于 990/1000 就值得警惕。
  • 8.0 起 Buffer Pool 按 chunk(默认 128MB)组织,支持在线扩容/缩容,扩容时逐个 chunk 迁移页,不锁库。
  • 冷热 LRU 分区——防全表扫描污染的关键设计:普通 LRU 下,一次全表扫描会把百万冷页刷进缓存、把真正的热页全部挤出去,扫完之后缓存等于清零重来,业务延迟断崖。InnoDB 把 LRU 链拆成 young(约 3/8,innodb_old_blocks_pct 控制)和 old(约 5/8)两段:新读入的页先插到 old 段头部;只有当它在 old 段停留超过 innodb_old_blocks_time(默认 1000ms)之后再次被访问,才晋升 young 段。全表扫描的页来得快去得也快,根本挤不进热区。

两个实用补充:Buffer Pool 会按 instance 切分(内存够大时默认 8 个)以降低页哈希 latch 争用,8.0 在线调整容量时也是按 chunk 逐个迁移、不锁库;命中率不要凭感觉,直接问计数器:

SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- Innodb_buffer_pool_read_requests    48000000   # 逻辑读:在 Buffer Pool 里找到页的次数
-- Innodb_buffer_pool_reads               42000   # 物理读:没命中、被迫读盘的次数

物理读/逻辑读超过约 1% 时,先查是不是缺索引导致多余的页加载,再考虑加大 innodb_buffer_pool_size——顺序很重要,加内存是治标,修 SQL 才是治本。

4.2 Change Buffer:二级索引写的延迟合并

场景:非唯一二级索引的页不在 Buffer Pool 时,对其的 INSERT/DELETE/UPDATE 不必先读盘取页,而是把改动记进 Change Buffer,等该页下次被读上来时再合并。它把大量随机 IO 攒成少量顺序 IO,机械盘时代收益巨大。

  • 只对非唯一二级索引有效——唯一索引必须读页确认唯一性,绕不过去。
  • 8.4 起默认停用:innodb_change_buffering 的默认值从 all 改为 none(官方文档实测口径),相当于事实上关闭;官方方向是逐步收缩这条路径。原因不神秘:SSD/NVMe 让随机读不再是主要矛盾,并行读和更大的 Buffer Pool 挤压了它的收益,而它自身的恢复路径很复杂(崩溃时要重放未合并项)。新实例不必为它调参(维持默认即可)。观测入口(默认停用后这些计数会长期停在 0 附近):
SHOW GLOBAL STATUS LIKE 'Innodb_ibuf%';
-- Innodb_ibuf_size            0    # 当前 change buffer 占用的页
-- Innodb_ibuf_merges       1523    # 累计完成合并次数

4.3 Adaptive Hash Index:给热点等值查询抄近路

InnoDB 的查询沿 B+ 树自顶向下比较,每层一次页查找。AHI 会监控被频繁访问的索引页,自动在内存里建一份"索引键 → 页内记录位置"的哈希表,等值查询直接命中,跳过整条 B+ 树路径。

  • innodb_adaptive_hash_index 默认 ON;8.0 起按 innodb_adaptive_hash_index_parts(默认 8)分分区锁,缓解了历史上的单 latch 热点。
  • 适合点查密集负载;纯范围扫描帮不上忙。写入压力大、页频繁被删改时 AHI 维护成本反而拖累,部分团队高并发写场景直接关掉它——这是需要实测的调优项,方法论见 11-备份恢复与性能调优.md。

4.4 Log Buffer:redo 的出境中转站

事务产生的 redo 先写 innodb_log_buffer_size(默认 16MB)的内存缓冲,在这些时机落到磁盘 redo 文件:事务提交、缓冲写满过半、后台定时刷新。日志体量远大于缓冲时(大批量导入),调大它可以减少提交前的强制 fsync 排队。容量与刷盘节奏如何配合 innodb_flush_log_at_trx_commit,属于 07-日志体系.md 的主题。


5. InnoDB 磁盘结构

5.1 表空间体系

表空间 文件 装什么
系统表空间 ibdata1 8.0 起仅剩内部对象;老版本里数据和回滚段都在这
数据字典 mysql.ibd 8.0 事务性字典本体(第 6 节)
file-per-table <库>/<表>.ibd 每张表一个文件,8.0 默认且推荐
undo 表空间 undo_001/002 独立 undo,支持在线截断回收(06-MVCC.md)
redo #ib_redo1... 崩溃恢复日志,环形使用;8.0.30+ 由 innodb_redo_log_capacity 统一管容量
双写缓冲 #ib_16384_0.dblwr* 8.0.20 起独立文件(见 5.4)

file-per-table 不只是组织洁癖:单表能单独搬移(ALTER TABLE ... DISCARD/IMPORT TABLESPACE,跨实例迁移大表比逻辑导入快一个数量级)、truncate 秒级回收空间、误删表的抢救范围也小——11-备份恢复与性能调优.md 的恢复剧本受益于此。

注意:innodb_file_per_table=OFF 且数据落在 ibdata1 的实例,空间只增不减——DROP TABLE 不会收缩 ibdata1。碰到这种历史配置要做空间治理,方案基本是重建实例(导出再导入),详见 11-备份恢复与性能调优.md。

查表空间对应哪些文件,用 8.0 的数据字典视图而不是 ls(列名以本实例 DESC 为准):

SELECT name, ROUND(file_size/1024/1024, 1) AS mb, row_format
  FROM information_schema.innodb_tablespaces
 WHERE name LIKE 'order_db/%';
-- order_db/orders    217.3    Dynamic

SELECT path FROM information_schema.innodb_datafiles
 WHERE space = (SELECT space FROM information_schema.innodb_tablespaces
                WHERE name = 'order_db/orders');
-- ./order_db/orders.ibd

5.2 页、区、段:数据的组织粒度

自下而上三层(8.0 后还有更上层的表空间对象,先不管):

页(Page,默认 16KB,innodb_page_size 初始化时定)
 └─ 区(Extent,64 个连续页 = 1MB):分配以区为单位,减少碎片
     └─ 段(Segment):一张表的 B+ 树通常拆两个段——
        叶子段(整区分配)与非叶子段(碎片页起步,够用再升级整区)

一张 16KB 数据页内部:

┌─ File Header 38B(页号、前后页指针)
├─ File Trailer 8B(checksum + LSN —— 坏页检测靠它,配合双写)
├─ Page Header 56B(页类型、记录数、空闲空间指针)
├─ Infimum / Supremum(两条边界伪记录)
├─ User Records(真实行,按主键顺序用 next 指针串成单向链表)
├─ Free Space(新插入行在此切分)
└─ Page Directory(槽目录:每 4~8 条记录一个槽,页内定位靠二分)

理解"行在页内是链表、页间是双向链表、整体挂在 B+ 树上",04-索引原理与实战.md 讲页分裂、随机 IO 时就有画面了。

5.3 redo:环形文件与 checkpoint

redo 按顺序写满环形区(8.0.30+ 由 innodb_redo_log_capacity 决定总容量,文件数量自动管理)。写满时被迫推进 checkpoint、强制刷脏腾出空间,表现为周期性吞吐断崖——刷脏跟不上 redo 生成速度时,调大容量是第一个旋钮,机制细节在 07-日志体系.md。

5.4 双写缓冲:对抗"半页写"

数据库页 16KB,磁盘/文件系统的原子单位往往只有 4KB 扇区甚至 512B。刷脏页写到一半断电,页就"半新半旧",而 redo 重放又依赖页头信息——经典死结。双写的解法朴素:刷脏之前先把页顺序写入双写文件(一次全写完成才继续),恢复时若页 checksum 对不上(Trailer 校验失败),先从双写区取完整副本,再叠加 redo。代价是约 2 倍写 IO,innodb_doublewrite=OFF 能关(裸盘原子写 + 断电保护时可考虑),但生产默认开——07-日志体系.md 有完整恢复流程。

5.5 binlog 不在 InnoDB 的地盘上

再强调一次这个分层事实:binlog 是 Server 层文件,记录的是语句/事件的逻辑变更,任何引擎的写都会产生 binlog;redo 只属于 InnoDB。两者通过 2.1 的两阶段提交对齐——很多"MySQL 为什么写两次日志"的困惑,根源就是把这两层混为一谈。


6. 8.0 数据字典:元数据终于事务化了

8.0 之前,建一张表要在库里留下 .frm 文件(表结构定义),而表数据在 .ibd/.MYD——结构在文件系统,数据在引擎,两套东西没有原子性。结果就是著名的"幽灵表":建表到一半宕机,留下有 .frm 没数据的残骸,DROP TABLE 都可能报错,只能手工删文件。information_schema 的元数据来自现场扫文件,和真实状态可以不一致。

8.0 把一切搬进跑在 InnoDB 上的事务性数据字典(mysql.ibd):

  • 表、列、分区、表空间、统计信息、权限……全部是字典表里的行;.frm 彻底消失。
  • DDL 因此变得原子:一条 CREATE TABLE 要么整体成功要么整体回滚,不再留残骸。
  • information_schema 变成字典的实时视图,不再"扫文件猜状态";这也是 SHOW CREATE TABLE 更快更准的原因。
  • 统计信息持久化(innodb_persistent_stats,8.0 默认):重启后优化器还记得你的表长什么样,不再靠临时采样。

对运维的直接影响:备份清单里 mysql.ibd 和双写/redo/undo 文件同等级别重要(旧教程里"备份只拷库目录、忽略系统文件"的写法在 8.0 下必死);跨版本回退不可能也源于字典格式只升不降——升级注意事项见 00-安装与配置.md 第 9 节。

注意:数据字典表(mysql.tables、mysql.innodb_tables 等)被保护,直接 UPDATE/INSERT 会报错且绝对不要尝试。元数据操作一律走 DDL 语句。


7. 记录格式速览

行在页里怎么摆放,由行格式决定。8.0 默认 DYNAMIC(innodb_default_row_format),COMPACT 是 5.0~7.x 时代存量,REDUNDANT 只活在考古里。

COMPACT/DYNAMIC 的记录布局:

├─ 变长字段长度列表(倒序)
├─ NULL 值位图
├─ 记录头 5B(next_record 偏移等)
├─ DB_ROW_ID 6B      ← 表没有合适主键时 InnoDB 自动生成的隐藏 id
├─ DB_TRX_ID 6B      ← 最近修改这行的事务号
├─ DB_ROLL_PTR 7B    ← 指向这行的上一个版本(undo 版本链,见 06-MVCC.md)
└─ 各列数据

两个隐藏列是 MVCC 的物理基础:每行自带"谁改的、上一版在哪",读不加锁的版本可见性全靠它们串起来。

行溢出与 20 字节前缀:一行的目标体积不能超过页的一半左右(16KB 页,约束在 InnoDB 层);server 层另有"所有变长列合计 65535 字节"的表级限制(计算写法见 03-数据类型与建表.md)。超限时把最长的变长列外存到溢出页(BLOB 页链):

  • COMPACT:本地保留该列前 768 字节 + 一个 20 字节的指针。前缀留在行内,扫描时可能省一次溢出页读取,但也挤占本地空间。
  • DYNAMIC:本地只留 20 字节指针,整列搬去溢出页。指针含 space id、页号、偏移,够用了。

这就是为什么一张 VARCHAR(20000) 的大字段表在 DYNAMIC 下依然能工作:真正存本地的只是指针。但别误会成免费午餐——大字段外存意味着取整列要多一次随机 IO,08-EXPLAIN与查询优化.md 里"SELECT * 拖垮大字段表"的分析从这里出发。

现状与两层"行长限制"的验证:

SELECT table_name, row_format FROM information_schema.tables
 WHERE table_schema = 'order_db' AND table_name = 'orders';
-- orders    Dynamic

-- server 层 65535 按"字节"计,utf8mb4 最坏 4 字节/字符:
-- 建一张 4 个 VARCHAR(20000) 的表:20000×4×4 = 320000 > 65535,报 1074
-- 出路:缩短列长度,或把长文本转 TEXT/BLOB(不计入 65535,走溢出页外存)

65535 的精确算法与规避写法在 03-数据类型与建表.md;“半页限制"决定的是溢出策略,两者常被混为一谈,分清楚能少踩很多建表报错。


8. 为什么最好有主键:聚簇组织一句话版

InnoDB 的表就是一棵主键 B+ 树——叶子节点直接放整行数据(索引组织表)。因此:

  • 主键越短越小,二级索引的叶子就越省:每个二级索引项存的"回表指针"就是主键值。
  • 没有显式主键时,InnoDB 依次尝试:第一个全列 NOT NULL 的唯一索引 → 实在没有就造隐藏 DB_ROW_ID(单调自增)。后者功能上能用,代价是这张表失去可预期的物理顺序、主从复制定位行效率下降、排障时"按 id 定位"的心智模型消失。

主键怎么选(自增 vs UUID、UUID_TO_BIN 的时间有序存储、页分裂代价)在 04-索引原理与实战.md 展开,结论先行一句:自增 BIGINT 主键是默认正确答案,UUID 直接做主键是默认错误答案。

没有主键时 InnoDB 的"补救"有可验证的物理痕迹:

CREATE TABLE test.t_no_pk (c1 INT) ENGINE=InnoDB;
SELECT i.name FROM information_schema.innodb_tables t
  JOIN information_schema.innodb_indexes i ON i.table_id = t.table_id
 WHERE t.name = 'test/t_no_pk';
-- GEN_CLUST_INDEX    # InnoDB 用隐藏 DB_ROW_ID 建的聚簇索引(列名以本实例 DESC 为准)

还有一笔复制账:ROW 格式 binlog 下,无主键表的变更在从库回放要靠全列 WHERE 定位行,表越大从库越慢、延迟越容易炸(10-主从复制与高可用.md 会再敲一次这个黑板)。存量无主键大表补主键(注意 Online DDL 通道,见 09-Online-DDL与大表变更.md)几乎永远是正确答案。


9. 存储引擎选择与例外场景

查看可用引擎:

SHOW ENGINES;                      -- Support 列:DEFAULT / YES / NO
SELECT engine, COUNT(*) FROM information_schema.tables
 WHERE table_schema NOT IN ('mysql','sys','performance_schema','information_schema')
 GROUP BY engine;                  -- 盘点存量库的引擎分布

日常原则:默认 InnoDB,不要换。例外场景值得知道:

  • Memory(HEAP):数据全内存、表锁、重启即空。用于可重算的临时映射表(字典码表、会话计数器)可以接受;当业务表用是事故预定。
  • TempTable:不是给你建表用的——5.7.6 起 MySQL 内部临时表(GROUP BY、union 等场景)默认引擎,比老的 Memory 更省内存。知道它存在,是为了读懂 EXPLAIN 里的 Create_tmp_table 信息。
  • Archive:只追加 + zlib 压缩,无索引可查性可言;日志/审计归档到 MySQL 内的极端场景才用,多数团队宁可上对象存储。
  • Blackhole:接受写入即丢弃(binlog 可留)。历史上用作复制分发的"黑洞代理"和压测挡板,一笔带过。
  • CSV / MERGE:工具性质(数据交换、把多张同构表拼一张查询),不承担业务。
  • NDB:分布式引擎,独立架构体系,和 InnoDB 是两条世界线,本手册不展开。
  • MyRocks(Percona Server 插件):LSM 写密集/高压缩场景,选它前先接受生态成本。

换引擎不是在线操作:ALTER TABLE ... ENGINE=xxx 是一次整表重建,代价与数据量同阶——这也是一开始就该用 InnoDB 的原因,“先建完再说"在这里没有后悔药。


10. 版本演进简表

版本 年份 标志性变化(对本手册相关章节)
5.0 2005 存储过程、触发器、游标、信息架构雏形
5.5 2010 InnoDB 成为默认引擎;semi-sync 复制;AHI/分区表增强
5.6 2013 GTID;基于代价的优化器;Online DDL 登场;InnoDB 全文索引
5.7 2015 JSON 类型、生成列;多源复制;sys schema;内部临时表换 TempTable
8.0 2018 事务性数据字典(去 .frm);CTE/窗口函数;utf8mb4 默认;caching_sha2 默认;instant ADD COLUMN(8.0.12);hash join(8.0.18);redo 动态容量(8.0.30);Clone plugin(8.0.17)。存量 8.0.46 自 2026-04-21 起转入 Sustaining Support,不再有常规补丁
8.4 LTS 2024 长期支持线(当前 8.4.13);mysql_native_password 默认禁用;Change Buffer 默认值改为 none(默认停用);一批旧参数清理
9.x → 26.x 2024-07 起 创新线 9.0~9.6(9.0 引入 VECTOR);9.7 升格为 LTS(9.7.2),与 8.4 并列两条受支持线;2026-07 起日期式版本 26.7/26.10,仍为 Innovation 非 LTS

11. 从这一章出发的学习路线

后续章节与本节概念的对位关系,按这个顺序读最省力:

  • 02-SQL基础、03-数据类型与建表:把本章的行格式、字符集、65535 限制落成能跑的 DDL。
  • 04-索引原理与实战:B+ 树、聚簇与覆盖——第 2.2、7、8 节的直接应用。
  • 05-事务与锁 → 06-MVCC:并发写怎么互不踩脚、并发读怎么不加锁看见正确版本;第 2、7 节埋的 undo 与隐藏列就是它们的入场券。
  • 07-日志体系:把第 2.1、5.3、5.4 节展开成完整的崩溃恢复矩阵与组提交机制。
  • 08-EXPLAIN与查询优化:第 1.3 节的优化器进入实战。
  • 09-Online-DDL与大表变更、10-主从复制与高可用、11-备份恢复与性能调优、12-分库分表、13-常见陷阱与最佳实践:运维进阶线,全部建立在第 1 节这张分层地图之上。

实战建议

把这一章收成一个可操作的心智模型:

  • 排障先定位层级:连不上/认证失败 → 连接层;1064/1146/1044 → 服务层;慢但没报错 → 优化器选路或引擎 IO/锁,对应 08-EXPLAIN与查询优化.md 和 05-事务与锁.md。分层清楚,报错才不会像天书。
  • 记住两条跨层关系:binlog 属于 Server 层、redo 属于 InnoDB,两阶段提交对齐两者(07-日志体系.md);undo 属于 InnoDB、服务回滚与 MVCC(06-MVCC.md)。MySQL 的日志体系只有一句话总纲:三层各管各的恢复对象。
  • SHOW ENGINE INNODB STATUS 是这一章的实物对照:Buffer pool hit rate、pending IO、最新事务与锁、log 容量,逐段读法放在 08-EXPLAIN与查询优化.md 与 11-备份恢复与性能调优.md,读到它们时回想本图的内存四件套。
  • 建表前的三个架构级决定(一旦初始化/建表很难改):字符集(utf8mb4)、行格式(默认 DYNAMIC 即可)、file-per-table(默认开)。细节落到 03-数据类型与建表.md。
  • 别人问"MySQL 为什么快/为什么慢"时,用三层图回答:快在 Buffer Pool 命中 + 顺序 redo;慢多半是物理读、锁等待、优化器误选——三者分别对应第 4、5 节与本手册后四章。