MySQL 深度教学(一):整体架构与一条 SQL 的旅程
先走一遍连接层到引擎层,弄清一条 SELECT / UPDATE 在 MySQL 里怎么走完。
系列总览(共 10 篇) ① 整体架构与一条 SQL 的旅程(本篇) ② InnoDB 存储引擎核心原理 ③ 索引深度解析与高性能索引设计 ④ 事务与 MVCC 多版本并发控制 ⑤ 锁机制全解 ⑥ 日志系统:Redo / Undo / Binlog 三件套 ⑦ SQL 优化与执行计划(EXPLAIN) ⑧ 查询性能调优实战 ⑨ 高可用与复制架构 ⑩ 备份恢复、分库分表与运维实战
写在前面:为什么第一篇先讲架构
Duang: AI时代,我愈发觉得基本功的重要性了,有些同学ai coding的起飞,连最基础的计算机知识也不熟悉,这样会碰到很多问题,“简历”写的花里胡哨,自己几斤几两自己清楚,我一直觉得,懂技术的才能真正玩好agent。不多说了,我拉上pz和大家一起进步和学习。
MySQL 是一个典型的”分层 + 插件化”系统,一条 SQL 从你发出,到数据真正落盘或返回,要依次穿过连接层、服务层、存储引擎层,最后落到文件系统。后面九篇(索引、事务、锁、日志、优化)讲的,全都是这四层里某一层的内部机制。如果不先建立全局认知,学到的每个知识点都是孤立的,比如常见的面试问题遇到慢查询时,你仍然不知道该去哪一层排查。
所以第一篇我们先把整条链路走一遍,让你清楚知道”每一块在哪里、干什么、和后面的篇章有什么关系”。但和一般”地图篇”不同的是,我会在每个关键节点都给你一个能亲手在命令行敲出来的观察手段(比如 SHOW PROCESSLIST、EXPLAIN、SHOW ENGINES),这样你不只是”知道有这回事”,而是”真的见过它长什么样”。
学习建议:本篇是地图篇,目标是建立整体印象,不要求背下每个参数。读的时候重点把握两条线:一条是”SQL 走过的路径”,一条是”MySQL 分了几层、每层归谁管”。后面遇到不懂的概念,随时回来看它在地图的哪个位置。文末的自测题建议先自己想一遍再对照。 Duang: 可以不用每个点都那么清楚,不要扣牛角尖,很多东西学到后面你自然就理解了!很多名词,缩写不懂没关系,这篇主要是了解一下架构!! 只需要记住这些:
- MySQL 是分层 + 插件化 系统:连接层、服务层、引擎层、文件层各司其职;
- 一条 SQL 的主线是 连接 → 解析 → 预处理 → 优化 → 执行 → 引擎取数 → 返回,UPDATE 还会触发锁 / undo / redo / binlog / 两阶段提交;
- InnoDB 是默认引擎,事务、行锁、崩溃恢复都靠它,后续深度内容都围绕它展开。
一、MySQL 在系统中的位置
在最常见的后端架构里,MySQL 处在”业务应用”和”磁盘”之间,是关系型数据的持久化中枢。它不生产数据,也不直接面对最终用户,而是站在中间,把上层的增删改查请求翻译成对磁盘的读写。
把它想象成一家餐厅:上层是顾客(业务应用),下层是后厨和仓库(磁盘)。MySQL 就是前台加传菜员——它接收顾客点的单(SQL),判断这道菜怎么做、该让哪个厨师做(调度引擎),最后把做好的菜(数据)端回去。它本身不种菜、不炒菜,但整个出餐流程都由它组织。
这个位置决定了三件你写代码时天天会碰的登西:
- 应用怎么找到它:业务代码不会直接读磁盘,而是通过驱动(JDBC、Go 的 database/sql、Python 的 PyMySQL 等)连到 MySQL 的 3306 端口,用 SQL 协议对话。你项目里的数据库连接串,本质就是”餐厅地址 + 包厢号 + 你的工牌”。
- 它自己不存数据:MySQL 把”数据怎么存、怎么取”这件最底层的事,委托给了可插拔的存储引擎(默认 InnoDB)。这决定了 MySQL 大量设计的根基——Server 层(调度中心)和引擎层(仓储部门)职责严格分离,两者用一套固定接口对话。
- 它夹在中间,所以两头都可能成为瓶颈:上面应用连得太猛会打满连接,下面磁盘太慢会让所有查询排队。理解这个位置,你以后排查”为什么请求卡了”才知道往哪边查。
ss -lntp | grep 3306
mysql -e "SHOW PROCESSLIST;"
如果你看到 3306 端口处于 LISTEN 状态,说明 MySQL 已经在等待客户端连接了。PROCESSLIST 的输出我们到 2.1 节详细拆解。
二、MySQL 逻辑分层架构
从逻辑上看,MySQL 服务端可以清晰地拆成四层。这也是官方架构图一直强调的模型。记住这四层的名字和职责,是理解后续所有内容的前提。我按”它负责什么 → 为什么这样拆 → 你能怎么观察到它”的顺序逐个讲。
2.1 连接层(Client / Connection Layer)
这一层负责连接客户端,相当于工厂的前台加门禁。它主要做三件事:
- 建立连接:客户端先通过 TCP(默认端口 3306)和 MySQL 完成三次握手建立连接,随后进行账号密码(或 SSL 证书)认证。只有认证通过,后续请求才被接受。可以想象成拨通电话,再报上工号和密码。
- 权限校验:认证通过后,MySQL 读取该用户的全局权限、库表权限,之后这条连接上的所有请求都受这套权限约束。
- 连接管理:MySQL 内部维护连接,为每个连接分配一个线程(或复用已有线程),负责后续的鉴权续期、超时检测、异常中断处理。
这里有个容易踩的坑:如果你中途用管理员账号修改了某用户的权限,已经存在的连接不会立即生效,必须等该用户重连才行。这是因为权限是在”建立连接那一刻”加载到连接上下文里的,不是每次执行 SQL 都重新查一遍权限表(那样太慢)。
补充一个常被混用的概念:连接(Connection) 和 会话(Session)。连接是物理的 TCP 通道;会话是连接上的一次逻辑交互上下文(比如当前打开了哪个事务、设了哪些变量、建了哪些临时表)。一般情况下,一个连接在其生命周期内对应一个会话。
Id User Host db Command Time State Info
1 root localhost:53210 NULL Sleep 142 NULL NULL
2 app 10.0.0.5:60123 shop Query 0 Sending SELECT * FROM user WHERE id=1
3 app 10.0.0.5:60155 shop Sleep 58 NULL NULL
逐列看:Id 是连接编号,杀连接用 KILL Id;;User/Host 是谁从哪来;db 当前默认库;Command 当前在干嘛,Sleep 表示空闲、Query 表示正在跑 SQL;Time 这个状态持续了多少秒;State 更细的执行阶段;Info 正在执行的 SQL 文本。线上排查”为什么卡住”时,第一眼就是看这张表——如果一堆连接 Time 很大且 State 卡在某个值,那就是瓶颈信号。
2.2 服务层(Server Layer)
这是 MySQL 的”大脑”,与具体存储引擎无关,所有引擎共用。一条 SQL 会依次经过下面这些组件。我重点讲清楚优化器,因为它是很多人觉得 MySQL”玄学”的源头。
- SQL Interface(SQL 接口):接收 SQL 命令(查询 DML、定义 DDL、存储过程调用等),并把最终结果返回客户端。你执行的每一条 SQL,第一步都先到这里报到。
- Parser(解析器):先做词法分析,把一长串字符串拆成 token(关键字、表名、列名等);再做语法分析,检查是否符合 MySQL 语法规则,生成解析树(Parse Tree)。它像语文老师,只管”句子通不通顺”,不管”内容是不是胡说八道”。
- Preprocessor(预处理器):解析器只管”语法对不对”,不管”语义合不合理”。预处理器补上语义校验:表是否存在?列是否合法?当前用户有没有访问权限?同时展开视图、把
*解析成具体列名清单。 - Optimizer(查询优化器):服务层最聪明也最容易翻车的地方,下面单独展开。
- Caches & Buffers:服务层曾经有查询缓存(Query Cache),但 MySQL 8.0 已彻底移除,原因在 2.2.5 节讲。真正存放数据页的缓存在 InnoDB 的 Buffer Pool(属于引擎层),不在服务层。
2.2.1 Parser:语法错了长什么样
解析器只认语法,不认语义。比如你把 FROM 拼成 FORM,它会在这一步直接报错,根本到不了”表存不存在”的检查:
mysql> SELECT id FORM user;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FORM user' at line 1
注意错误里的 near 'FORM user',它告诉你解析器是在哪个位置”读不下去”的。这个报错本身就能帮你快速定位拼写问题。
2.2.2 Preprocessor:语义校验举例
语法对了,语义不对照样拦下。比如表名拼错、列名不存在:
mysql> SELECT id FROM usr;
ERROR 1146 (42S02): Table 'shop.usr' doesn't exist
mysql> SELECT id, age FROM user;
ERROR 1054 (42S22): Unknown column 'age' in 'field list'
另外,SELECT * 里的 * 就是在这一步被展开成实际列清单的。所以 * 不是”运行时才知道有哪些列”,而是在预处理阶段就定死了。
2.2.3 Optimizer:它到底基于什么做决定
优化器基于统计信息(表有多少行、索引区分度、数据分布等),在语义等价的多种执行路径里挑出它认为代价(Cost)最低的一个,生成执行计划。问题是:统计信息到底是什么?
- 表的行数估计:
SHOW TABLE STATUS LIKE 'user';里的 Rows 字段,注意它是”估计值”不是精确值。 - 索引区分度:
SHOW INDEX FROM user;里的 Cardinality(基数),表示索引列有多少不同值。Cardinality 越高,索引越”有用”(能过滤掉越多行)。 - 数据分布:比如 age 列大部分是 20~30,那
WHERE age > 18几乎命中全表,优化器就可能放弃索引改走全表扫描——因为走索引反而要多一次回表,不如直接扫。
这里有个反直觉但很重要的点:有时候全表扫描比走索引更快。当优化器估算”满足条件的数据占了表的很大比例”时,走索引要反复回表(先查索引页、再查数据页),总成本高于直接顺序扫数据页。这正是”为什么我建了索引查询还是慢/还是不走索引”的常见原因之一。
CREATE TABLE t_demo (id INT PRIMARY KEY, flag INT, KEY idx_flag (flag));
INSERT INTO t_demo VALUES (1,1),(2,1),(3,1),...,(10000,1),(10001,2); -- flag 绝大多数是 1
ANALYZE TABLE t_demo; -- 让统计信息更新
EXPLAIN SELECT * FROM t_demo WHERE flag = 1; -- 大概率 type=ALL(全表扫描)
EXPLAIN SELECT * FROM t_demo WHERE flag = 2; -- 大概率 type=ref(走索引)
同一条”按 flag 查”的结构,因为 flag=1 命中几乎全表、flag=2 只命中一行,优化器给出了不同的执行方式。这就是统计信息在起作用。如果你用 FORCE INDEX(idx_flag) 强行让它走索引查 flag=1,反而会更慢——因为要回表一万次。
再强调一次:优化器选的计划只是它基于现有统计信息”以为”的最优,并不保证真的全局最优。统计信息过期(比如大批量导入后没做 ANALYZE)就容易导致它误判,进而产生慢查询。第 ⑦ 篇会专门讲怎么读 EXPLAIN 来发现这类问题。
2.2.4 为什么 MySQL 8.0 删掉了查询缓存
查询缓存的设计是”表一旦被写入,该表相关的所有缓存全部失效”。这意味着只有对几乎不写、纯只读的表才划算。可真实业务大多写操作频繁,导致缓存命中率极低,而每次读写还要额外去抢一把全局锁,反而拖慢整体性能。所以官方干脆移除它,把缓存职责交还给更合适的应用层(比如 Redis)。
一句话记住:Server 层负责”怎么想”(解析、优化、执行调度),引擎层负责”怎么存、怎么取”(读写磁盘、加锁、管理事务)。两者用一套固定接口对话,互不关心对方内部实现。
2.3 存储引擎层(Pluggable Storage Engine Layer)
这是 MySQL 最具特色的”插件化”设计。Server 层负责”怎么想”,引擎层负责”怎么存、怎么取”。你可以通过 SHOW ENGINES; 看到所有可用引擎。最常见的几种:
- InnoDB:默认引擎,支持事务、行级锁、外键、崩溃恢复,绝大多数业务的首选。
- MyISAM:老牌引擎,不支持事务和行锁,读性能略好、占用空间小,适合只读或日志类场景,但已逐步被淘汰。
- Memory:数据全在内存,重启即丢,适合临时计算或缓存。
- Archive / CSV / NDB 等:各自面向归档、数据导出、分布式等特殊场景。
引擎层向上对 Server 层暴露一套统一的 API(业内常叫 Handler API,比如”按主键读一行""插入一条记录""启动一个事务”)。Server 层完全不关心底层用的是 B+Tree 还是别的实现。这种解耦非常关键——它让 MySQL 可以不断演进引擎,而不必改动上层的 SQL 解析和优化逻辑。打个比方:Server 层像个标准插座,引擎层是各种插头,换个插头就能换一种存储能力。
2.4 物理文件系统层
最底下是操作系统文件。InnoDB 的数据、索引、undo、redo 最终都以文件形式存在于磁盘。光看概念抽象,不如直接看文件长什么样。
ibdata1 # 系统表空间
ib_logfile0 # redo log(重做日志)
ib_logfile1
mysql/ # 系统库
shop/ # 你的业务库
user.ibd # 独立表空间:user 表的数据+索引
user.frm # (5.7 及之前)表结构;8.0 起结构也存在 ibd 里
auto.cnf
看到 shop/user.ibd 这种文件,你就知道”一张表的数据和索引其实是一个文件”。这就是为什么后面讲索引时要强调”聚簇索引的叶子节点就是数据本身”——它们都在同一个 .ibd 文件里。
三、一条 SELECT 查询语句的完整旅程
理论讲完,我们用一条最常见的查询走一遍全流程,把抽象的四层变成具体的脚步:
SELECT id, name FROM user WHERE age > 18 ORDER BY create_time DESC LIMIT 10;
它在 MySQL 内部会依次经历下面这些阶段。建议边读边在脑子里跟着这条 SQL 走一遍,并在每个阶段想想”这一步我能观察到什么”。
3.1 建立连接与认证
客户端发起 TCP 连接,MySQL 验证用户名密码与权限,分配一个线程或会话。这一步属于连接层(2.1)。只有过了门禁,这条 SQL 才真正进入 Server 层处理。如果连这一步都过不了(密码错、账号被锁),后面一切都不会发生——你直接用客户端连的时候看到的 “Access denied” 就是卡在这里。
3.2 解析(Parser)
词法分析把这条 SQL 拆成 token(SELECT、id、name、FROM、user 等),语法分析确认它合法:SELECT 后面跟的是列、FROM 后面跟的是表名、整体结构正确,然后生成解析树。如果语法有错(比如把 FROM 拼成 FORM),直接在这一步报 “You have an error in your SQL syntax”(见 2.2.1)。
3.3 预处理(Preprocessor)
语义检查:user 表是否存在?age、create_time 列是否存在?当前用户有没有读这张表的权限?同时把 * 展开为实际列。不合法会报 “Unknown column” 之类错误(见 2.2.2)。只有语法和语义都过了,才进入优化阶段。这里相当于门禁之后,前台再核对一下”你要找的人确实在本公司花名册里”。
3.4 查询优化(Optimizer)
优化器基于统计信息决定”怎么执行代价最低”:
- 索引选择:是走 age 上的索引、create_time 上的索引,还是全表扫描?优化器会估算每种方案要扫描多少行(结合 2.2.3 讲的基数和分布)。
- 访问方式:范围扫描(range)、索引扫描(index)、全表扫描(ALL)?代价各不相同。
- 排序策略:能否直接利用索引天然有序来避免额外排序(filesort)?这里 ORDER BY create_time 能不能免掉排序,取决于选了哪个索引。如果选了 age 索引,数据是按 age 排的,create_time 是乱的,就必须额外排序;如果选了 (age, create_time) 联合索引,那在 age 范围内 create_time 已经有序,就能省掉排序。
- 分页策略:LIMIT 10 怎么处理,是先排序再取前 10 条,还是边扫边丢。
优化器产出的是一棵执行计划树,不是最终结果。你可以用 EXPLAIN 命令查看它(第 ⑦ 篇详解)。
EXPLAIN SELECT id, name FROM user WHERE age > 18 ORDER BY create_time DESC LIMIT 10;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
| 1 | SIMPLE | user | NULL | ALL | idx_age | NULL | NULL | NULL | 9999 | 33.33 | Using where; Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
看两列关键的:type=ALL 说明走了全表扫描(没用上索引),Extra 里的 Using filesort 说明还要额外排序。这就是优化器在”没有合适索引”时的无奈选择。等你建了 (age, create_time) 联合索引再 EXPLAIN,大概率 Extra 里的 filesort 就消失了。这个对比,正是第 ③ 篇索引设计要解决的。
3.5 执行(Executor)
执行器按优化器给的计划,逐行调用存储引擎的 API 取数据。例如:“打开 user 表""用 age 索引定位第一条满足条件的记录""取下一条”,直到取满 10 条或遍历完。执行器本身不做具体的数据查找,它只负责指挥引擎干活,相当于工头,不自己搬砖。
3.6 存储引擎取数(InnoDB)
当执行器说”给我这一行”,InnoDB 才真正动手:
- 先查 Buffer Pool(内存中的数据页缓存),命中就直接返回,这是最快的路径;
- 未命中则从
.ibd文件把对应页读入 Buffer Pool,再返回; - 数据在磁盘上以 B+Tree 组织(聚簇索引),按主键或二级索引定位到具体记录(第 ②、③ 篇展开)。
这里有个对性能至关重要的直觉:一次磁盘随机读大约比一次内存读慢 1 万到 10 万倍。所以 Buffer Pool 命中率高低,直接决定数据库快慢。你后面会反复看到”能不能命中 Buffer Pool”这个主题。
3.7 返回结果
执行器把结果整理好,沿原路返回:Server 层 → 连接层 → 客户端。如果结果较大,MySQL 采用”边算边发”的流式返回,不会等全部算完才一次性发出,这对大结果集很友好。
一句话记忆:连接 → 解析 → 预处理 → 优化(定计划)→ 执行(调引擎)→ 引擎取数(查缓存/读磁盘)→ 返回。记住这条主线,后面每篇都是在这条线的某个节点深挖。
四、一条 UPDATE 更新语句的旅程(承上启下)
查询相对单纯——只读不写。但更新语句会牵动 MySQL 最核心的几个机制,正好为后续篇章埋下伏笔。以这条为例:
UPDATE account SET balance = balance - 100 WHERE id = 1;
它同样要经过”连接 → 解析 → 预处理 → 优化 → 执行”,但到了引擎层会多出几个关键动作。理解这些动作,你就拿到了后续篇章的”预告片”:
- 加锁:InnoDB 会先对
id=1这行加** 行级排他锁(X 锁)**,保证并发安全——两个事务不能同时改同一行(第 ⑤ 篇:锁机制)。 - 写 Undo Log:把修改前的值记录下来。作用有二:事务回滚时能恢复旧值;支撑 MVCC 让其他读请求看到旧版本(第 ④ 篇:事务与 MVCC)。可以把它理解为”修改前的快照备份”。
- 改 Buffer Pool:在内存页里把 balance 改成新值。注意,此时磁盘上的数据还没变,这行数据变成了”脏页”。
- 写 Redo Log:把”哪个页、改了什么”记录到 redo log,遵循”先写日志,后刷磁盘”的 WAL 原则,保证即使崩溃,重启后也能根据日志把数据补写回去(第 ⑥ 篇:日志系统)。它是”出餐记录”,哪怕后厨着火,也能凭记录恢复进度。
- 写 Binlog:Server 层记录”逻辑操作”(改了哪行、改成什么),用于主从复制和时间点恢复(第 ⑥、⑨ 篇)。
4.1 两阶段提交:为什么 redo 和 binlog 必须”对得上”
redo(引擎层)和 binlog(Server 层)是两套独立的日志,但一条 UPDATE 要同时写它们。如果只写了一个没写另一个,主库和从库的数据就会对不上。MySQL 用”两阶段提交”保证两者最终一致,时序如下:
- 阶段一:Prepare:InnoDB 写 redo log,并标记为 prepare 状态(已写但未提交)。
- 阶段二:写 Binlog:Server 层把逻辑操作写入 binlog 并刷盘。
- 阶段三:Commit:InnoDB 把 redo log 标记为 commit 状态,事务正式完成,释放行锁,脏页等后台刷盘。
崩溃恢复时,MySQL 会这样判断:如果 redo 是 prepare 且对应的 binlog 完整,就补一个 commit(认为事务成功);如果 redo 是 prepare 但 binlog 不完整(写 binlog 时崩了),就回滚这个事务。这样无论崩在哪个点,主库恢复后的状态和”binlog 里记录了什么”始终一致,从库重放 binlog 就不会和主库对不上。
4.2 脏页与刷新:为什么改了内存不一定立刻写盘
Buffer Pool 里被改过、但还没写回磁盘的页叫”脏页”。MySQL 不会每条 UPDATE 都立刻把数据刷盘(那样磁盘扛不住),而是攒一批,由后台线程(Page Cleaner)异步刷。这就引出一个你后面会常听到的概念——Checkpoint(检查点):它记录”哪些脏页已经安全落盘了”,崩溃恢复时只需从最近的检查点之后的 redo 开始重放,不用从头来。具体机制第 ⑥ 篇展开。
看,一条 UPDATE 几乎把后面 9 篇的内容都用上了。所以你现在不需要懂细节,只要知道”它们会在哪里出现”即可——这正是地图篇的价值。
五、连接与线程模型深入
很多线上故障(比如 “Too many connections""连接数打满”)都出在连接层。这里补充几个必须掌握的点,它们直接关系你以后怎么配置数据库。
5.1 线程模型
默认情况下,MySQL 是 one-thread-per-connection:每个客户端连接分配一个独立线程。连接多时线程也多,上下文切换的开销随之上升。高并发场景可启用 线程池(thread pool) 插件,把大量连接复用到有限的工作线程上,避免线程爆炸。打个比方:短连接像每次来客人都新招个服务员,线程池像固定几个服务员轮流接待多桌客人。
5.2 关键参数
| 参数 | 含义 | 典型值/说明 |
|---|---|---|
max_connections | 最大并发连接数 | 默认 151,高并发可调 500~2000,但受内存限制,不是越大越好 |
wait_timeout / interactive_timeout | 非交互/交互连接空闲多久后断开 | 默认 28800 秒(8 小时),长连接应用建议调小,及时回收空闲连接 |
max_user_connections | 单用户最大连接数 | 防止单个账号打满全部连接 |
thread_cache_size | 线程缓存,复用断开的线程 | 减少频繁新建线程的开销 |
thread_handling | 线程处理模式 | 默认 one-thread-per-connection;企业版可设为线程池模式 |
5.3 长连接 vs 短连接
- 短连接:每次请求新建连接、用完即关。逻辑简单,但频繁建连/断连开销大,且容易把连接数打满(瞬间高并发时特别明显)。
- 长连接:连接复用,性能好。但要注意:MySQL 长连接会累积内存(比如为这个连接分配的临时表、排序缓冲区),如果长期不释放,内存会慢慢上涨。建议配合连接池加定期重连或重置(如执行
mysql_reset_connection)来释放累积的资源。
六、存储引擎对比:为什么 InnoDB 是默认
从 MySQL 5.5 起,InnoDB 成为默认引擎,这不是没有原因的。一张表看清核心差异:
| 特性 | InnoDB | MyISAM | Memory |
|---|---|---|---|
| 事务 | 支持(ACID) | 不支持 | 不支持 |
| 锁粒度 | 行级锁 | 表级锁 | 表级锁 |
| 外键 | 支持 | 不支持 | 不支持 |
| 崩溃恢复 | 强(redo/undo) | 弱(易损坏需修复) | 无(重启丢) |
| 索引结构 | 聚簇索引 B+Tree | 非聚簇 B+Tree | 哈希索引 |
| 适用场景 | 绝大多数业务(OLTP) | 只读/日志/报表 | 临时计算/缓存 |
结论很明确:除非你有非常明确的只读、且对事务零要求的特殊场景,否则一律用 InnoDB。后面所有”深度”内容,也默认以 InnoDB 为对象。MyISAM 现在主要活在老系统里,新项目基本不用考虑它。
七、逻辑架构与物理文件
把前面的”逻辑分层”对应到磁盘上的真实文件,能帮你建立”内存—磁盘”的完整认知:
| 逻辑概念 | 对应物理文件/对象 | 说明 |
|---|---|---|
| 表数据 + 索引 | *.ibd(独立表空间) | 开启 innodb_file_per_table 时每表一个 |
| 系统元数据 | ibdata1 | 数据字典、系统表空间 |
| 重做日志 | ib_logfile0/1 | 崩溃恢复,循环写 |
| 回滚日志 | undo tablespace | 事务回滚、MVCC |
| 二进制日志 | mysql-bin.000001 | Server 层,复制/恢复,追加写 |
八、本章小结与下篇预告
本篇你应当建立起三张”地图”:
- MySQL 是分层 + 插件化 系统:连接层、服务层、引擎层、文件层各司其职;
- 一条 SQL 的主线是 连接 → 解析 → 预处理 → 优化 → 执行 → 引擎取数 → 返回,UPDATE 还会触发锁 / undo / redo / binlog / 两阶段提交;
- InnoDB 是默认引擎,事务、行锁、崩溃恢复都靠它,后续深度内容都围绕它展开。
下一篇(② InnoDB 存储引擎核心原理) 我们将钻进引擎层,讲清楚:InnoDB 的内存结构(Buffer Pool、Change Buffer、Log Buffer)和磁盘结构(表空间、段/区/页),以及数据究竟是如何以 B+Tree 组织、一次”按主键查一行”在页内是如何定位的。那是一切索引与锁知识的地基。
九、自测与思考
读完本篇,试着回答下面问题(答案基本都能在文中找到,但建议先自己想一遍):
基础题
- 为什么 MySQL 把”优化”和”执行”拆成两个组件?如果优化器选错了计划会怎样?你能用什么命令看到它的决定?
- 查询缓存被移除的根本原因是什么?如果业务真的需要”热点只读缓存”,应该放在哪一层解决?
- 一条 SELECT 和一条 UPDATE,在 MySQL 内部的处理差异主要出现在哪一层、哪几个动作?
进阶题
- 既然 InnoDB 的脏页在内存里,万一服务器突然断电,已提交的事务会不会丢?靠什么保证不丢?两阶段提交在哪个环节崩了会导致主从不一致?
max_connections调得越大越好吗?长连接为什么可能导致内存上涨?你会用哪条命令观察连接压力?
本篇为系列第 ① 篇。
SHOW ENGINES; EXPLAIN SELECT * FROM 你的某张表 WHERE 某列 = 某值; SHOW VARIABLES LIKE 'max_connections';跑的时候重点看 EXPLAIN 输出的 type 和 Extra 两列——它们是你以后排查慢查询的第一现场。第 ⑦ 篇我们会专门教你怎么读这两列。