前面 8 篇拆开讲了一个个零件索引怎么组织、Buffer Pool 怎么缓存、事务靠 redo/undo、并发靠 MVCC 和锁、崩溃安全靠 binlog 和两阶段提交。这一篇把零件装回整机——一条 SQL 从客户端敲下回车到结果返回中间到底经过了哪些环节。这既是理解 MySQL 的一张总图也是面试的经典开场题“一条查询语句是怎么执行的”顺带把两个高频八股收进来InnoDB 和 MyISAM 到底怎么选、建表时数据类型怎么挑。目录MySQL 的两层架构一条查询 SQL 的一生一条更新 SQL 多做了什么存储引擎InnoDB vs MyISAM建表时的数据类型选择全系列地图一、MySQL 的两层架构理解执行流程先得知道 MySQL 内部分成上下两层这条分界线贯穿了整个系列Server 层连接管理、SQL 解析、优化、执行以及所有跨引擎的通用功能存储过程、触发器、视图、函数、binlog也在这一层。存储引擎层真正负责数据的存储和读写是插件式的——InnoDB、MyISAM、Memory 等可以按表选择。MySQL 5.5 起默认是InnoDB。客户端 │ ┌─┴────────────── Server 层 ──────────────┐ │ 连接器 → 分析器 → 优化器 → 执行器 │ └─────────────────┬───────────────────────┘ │ 调用引擎接口 ┌─────────────────┴──── 存储引擎层 ────────┐ │ InnoDB / MyISAM / Memory ... │ └────────────────────────────────────────┘ │ 磁盘前面几篇的内容正好分布在这两层索引、Buffer Pool、redo/undo、锁、MVCC、页/表空间全在 InnoDB 引擎层优化器、EXPLAIN、binlog在 Server 层。记住这条分界线很多问题就有了坐标——比如为什么需要两阶段提交本质就是 Server 层的 binlog 和引擎层的 redo 要保持一致详见《binlog 与两阶段提交》那篇。二、一条查询 SQL 的一生以最简单的一条查询为例跟着它走完全程SELECT*FROMordersWHEREid1;第 1 站·连接器负责和客户端建立连接、验证账号密码、获取权限。连接建立后权限就在这一刻确定了——之后哪怕管理员改了你的权限也要等你重连才生效。连接分长连接和短连接生产一般用长连接省去反复建连的开销但长时间不释放会占内存靠wait_timeout兜底断开空闲连接。第 2 站·查询缓存8.0 已删除早期版本这里有个查询缓存——把SQL 文本 → 结果缓存起来命中就直接返回。但它弊大于利只要表有任何一条数据被改这张表的所有缓存全部失效命中率极低反而拖累性能。所以 MySQL8.0 直接把查询缓存整个移除了5.7.20 起就标记为废弃。现在这一站可以忽略。第 3 站·分析器对 SQL 做词法分析 语法分析。词法分析识别出这串字符里哪些是关键字SELECT、哪些是表名orders、列名、条件语法分析检查这些东西拼起来符不符合 SQL 语法。写错关键字、少个括号就是在这一步报的You have an error in your SQL syntax。第 4 站·优化器分析器知道了要干什么优化器决定怎么干最省。同一条 SQL 往往有多种执行方式——走哪个索引、多表 join 的先后顺序——优化器基于成本估算选出最便宜的方案生成执行计划。这一步的产物就是EXPLAIN能看到的东西详见《EXPLAIN 执行计划》和《SQL 优化实战》两篇。第 5 站·执行器拿着执行计划真正干活。它先再验一次权限有没有权限查这张表然后调用存储引擎提供的接口一行行地取数据、按条件过滤、组装结果。EXPLAIN里的rows和慢日志里的rows_examined反映的就是执行器和引擎之间要了多少行。第 6 站·存储引擎以 InnoDB 为例执行器要一条id 1的记录InnoDB 顺着聚簇索引的 B 树从根页一层层往下定位页内用页目录的槽二分查找找到记录返回给执行器详见《B 树索引》《InnoDB 存储结构》。当然读任何页之前都要先把它从磁盘加载进 Buffer Pool详见《Buffer Pool》那篇。执行器拿到记录、组装好结果集返回给客户端——一条查询的一生就走完了。串起来就是连接器(建连鉴权) → [查询缓存,8.0删] → 分析器(词法语法) → 优化器(选执行计划) → 执行器(调引擎接口) → 存储引擎(B树取数) → 返回三、一条更新 SQL 多做了什么更新语句INSERT/UPDATE/DELETE走的是同一套流程——一样要经过连接器、分析器、优化器、执行器——但因为它改了数据在执行器调用引擎的那一步多出了一大堆日志工作改数据前先写undo 日志为了能回滚、为了 MVCC改内存页的同时写redo 日志崩溃后不丢WAL 预写日志语句执行完写binlog给主从复制和恢复用事务提交时走两阶段提交redo prepare → binlog → redo commit保证 redo 和 binlog 一致。这部分是《事务与 MVCC》和《binlog 与两阶段提交》两篇的主题这里不重复展开。一句话记住区别查询是只读地取数据更新是改数据 写一堆日志保证不丢不乱。这也是为什么查询不写 redo/undo/binlog而更新要走那么长一条链路。四、存储引擎InnoDB vs MyISAM到了引擎层就绕不开这个经典对比。虽然现在基本都用 InnoDB但为什么用 InnoDB 不用 MyISAM仍是高频题维度InnoDBMyISAM事务✅ 支持❌ 不支持锁粒度行锁并发高表锁并发低外键✅ 支持❌ 不支持崩溃恢复✅ 靠 redo 日志❌ 不支持索引结构聚簇索引数据和主键索引存在一起非聚簇数据和索引分开存索引叶子存的是行的地址count(*)无条件要实际扫描统计存了总行数O(1) 直接返回MVCC✅ 支持❌ 不支持适用场景绝大多数 OLTP 业务默认只读、读多写少、不要求事务的场景为什么默认 InnoDB现代业务几乎都要事务、要高并发写、要崩溃后数据不丢——这三点正好是 InnoDB 的强项事务 行锁 redo 崩溃恢复而 MyISAM 一个都不占。MyISAM 唯一的亮点是count(*)快和结构简单省空间只在一些纯读的场景才有意义。聚簇 vs 非聚簇这一点在《B 树索引》里详细讲过InnoDB 的叶子节点直接存整行数据索引即数据MyISAM 的索引叶子只存一个指向数据文件的地址所以 MyISAM 的所有索引都相当于二级索引查数据都要根据地址再去数据文件捞一次。五、建表时的数据类型选择表设计是每天都在做、面试也常问的基本功。几条最实用的原则① 主键用自增BIGINT别用 UUID。自增主键是顺序插入新记录总是追加到 B 树最右边不会引起页分裂UUID 是随机值插入位置忽左忽右频繁触发页分裂和记录移动还因为更长而让所有二级索引都变大二级索引的叶子都带着主键值详见《B 树索引》里主键插入顺序和索引列类型尽量小两点。②char还是varchar。char(n)定长不管实际多长都占 n 个字符——适合长度固定的列MD5 值、手机号、状态码varchar(n)变长按实际长度存外加 1~2 字节记长度——适合长度不定的列。定长列用char略快不用算长度但长度不定还用char会浪费空间。③ 时间类型datetimevstimestamp。timestamp占 4 字节、带时区、范围到 2038 年datetime占 8 字节、不带时区、范围大得多。需要跨时区、且时间在 2038 年前用timestamp省空间否则用datetime更省心。④ 金额一定用decimal别用float/double。浮点数是二进制近似存储会丢精度0.1 0.2 ≠ 0.3那套钱的计算错一分都是事故。decimal是精确的定点数。⑤ 尽量别用 NULL。NULL 列要额外的标记位、会让索引统计和聚合函数count、SUM的行为变得别扭能用NOT NULL 默认值就别留 NULL。⑥ 单表多大要考虑拆分。常见的经验值是单表行数别超过 2000 万左右。这个数字和 B 树有关一棵 3 层的 B 树大约能存两千万到上亿行超过之后树可能长到 4 层每次查询多一次磁盘 I/O详见《B 树索引》的层数估算。真到这个量级就该考虑分库分表或归档了。六、全系列地图到这里整个 MySQL 系列可以收成一张图了。跟着一条UPDATE语句的一生把 9 篇串起来一条 SQL 进来 │ ├─ 连接器鉴权 → 分析器解析 → 优化器选执行计划 ← [《EXPLAIN》](https://blog.csdn.net/weixin_37358308/article/details/163074561)[《SQL 优化实战》](https://blog.csdn.net/weixin_37358308/article/details/163143705) │ ├─ 执行器调 InnoDB 接口 │ │ │ ├─ 在 B 树里定位记录 ← [《B 树索引》](https://blog.csdn.net/weixin_37358308/article/details/163057075)[《InnoDB 存储结构》](https://blog.csdn.net/weixin_37358308/article/details/163143082) │ ├─ 页加载进 Buffer Pool ← [《Buffer Pool》](https://blog.csdn.net/weixin_37358308/article/details/163087687) │ ├─ 加锁当前读防脏写 ← [《锁》](https://blog.csdn.net/weixin_37358308/article/details/163116573) │ ├─ 写 undo可回滚 MVCC ← [《事务与 MVCC》](https://blog.csdn.net/weixin_37358308/article/details/163088123) │ ├─ 改页 写 redoWAL崩溃不丢 ← [《事务与 MVCC》](https://blog.csdn.net/weixin_37358308/article/details/163088123) │ └─ 快照读靠 ReadView 找版本 ← [《事务与 MVCC》](https://blog.csdn.net/weixin_37358308/article/details/163088123) │ └─ 提交redo prepare → binlog → redo commit ← [《binlog 与两阶段提交》](https://blog.csdn.net/weixin_37358308/article/details/163143500) 两阶段提交保证主从一致binlog 供复制/恢复这九篇合起来回答的其实是同一个问题的不同侧面MySQL 是怎么把数据又快、又稳、又能并发地存取在磁盘上的——快靠索引减少扫描、靠 Buffer Pool 减少磁盘 I/O、靠优化器选最优路径稳靠 redo 崩溃不丢、undo 支持回滚、两阶段提交保证 redo 和 binlog 一致并发靠 MVCC 让读写不互相阻塞、靠锁保证写的正确性。把这条主线和每篇的细节都揣在心里面试时无论从哪个点切进去——索引、事务、锁、日志、优化——你都能顺着这张图往上下游延伸讲出一个完整的体系而不是一个个孤立的知识点。