核心概念与架构
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 进来后依次经过:
- 解析器:词法、语法分析生成 AST,语法错误在这一步报
1064。 - 预处理器:检查表/列是否存在、解析别名和星号展开——
1146 Table doesn't exist是这里报的,不是解析器。 - 优化器:基于代价模型选择索引、决定 join 顺序与算法(8.0.18+ 引入 hash join;CTE、窗口函数自 8.0 起支持)。它是"建议者"不是"决定者",统计信息过时就会选错——纠偏手段见
08-EXPLAIN与查询优化.md。 - 执行器:按执行计划调用引擎的 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.ibd5.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 节与本手册后四章。