MySQL 单点详解(01)

MySQL 单点详解(01):一条 SQL 的一生,从连接器到存储引擎过了几道手

写在前面

面试官问这题,通常有两句:

  • 一条查询语句在 MySQL 里是怎么执行的?
  • 那 8.0 还有查询缓存吗?

第二句是坑。很多人第一句答"先查缓存",正好撞上。

8.0 已经把查询缓存整个删了,5.7 里默认也是关的。

所以这条链路的第一步不是查缓存,是验身份。

整条链路只需要抓住一条分界线:

server 层管"这条 SQL 该怎么干",引擎层管"数据在哪、怎么搬"。

五道手里只有优化器在做选择,其余四道都在照做。慢查询、选错索引,追到根上几乎都出在优化器那一步。

一、五道手总览

环节 干什么 所在层
连接器 验身份、分配线程、管连接数 server
解析器 词法 + 语法分析,产出语法树 server
预处理器 查表、查列、展开 select * server
优化器 选索引、定 join 顺序 server
执行器 按计划向引擎要行、逐行判断条件 server
存储引擎 读页、改页、写日志 引擎

前五站全在 server 层,碰磁盘是最后一站的事。

面试时先甩这张表,再挑重点展开,比一口气背流程更稳。

二、连接器:先验身份

客户端先和 MySQL 建 TCP 连接,连上之后第一件事不是执行 SQL,是验身份。

用户名密码对不上,后面的活一点都干不了。

验证通过,服务端分一个线程给这条连接,之后这条连接上的语句都归它管。

一个连接一个线程,所以连接数一多就顶不住,默认最多 151 个。

空闲连接也不会一直留着,默认八小时没动静就被断开。

这里有个常被说反的点:权限不是连接时一次性读进来的。

改权限的方式 是否立即生效 要不要 flush
GRANT / REVOKE 立即生效,含已连接会话 不需要
直接 UPDATE mysql.user 不生效 需要 FLUSH PRIVILEGES

权限是每条语句执行时现查的,所以用 GRANT 改完,已经连着的会话下一条语句就生效。

只有绕过 GRANT 直接改表,内存里的权限缓存不知道,才需要手动刷。

这两件事说混,很容易被判成背的。

三、解析器和预处理器:先看语法,再看表和列

解析器只干一件事:把 SQL 切成词,再按语法拼成一棵树。

它只认语法,不认表。

关键字写错才归它管,报错 1064,还会指出 near 哪个词。

表不存在不是它报的。

表在不在、列在不在,是紧接着的预处理器干的。

列名写错报 1054 Unknown column,就是这一站。

select * 也有代价,星号要先展开成具体列,表多的时候这一步不免费。

两站合起来只做一件事:把人话翻译成机器能读的树。

翻译完,语法对、语义也对,但还不知道怎么取数据。

四、优化器:全链路唯一做选择的地方

同一条 SQL,可以有很多种取法:走哪个索引、两张表先读谁、子查询要不要先算出来。

选哪种,是优化器的事,也是整条链路上唯一做选择的一站。

它按成本选,成本大致等于"要读多少页"加"要比多少行"。

走索引读得少,但可能要多回几次表;全表扫读得多,但一遍就过。

关键问题:这个成本是估的,不是算的。

估的依据是统计信息,而统计信息是抽样出来的。

一张几千万行的表,可能只看了二十来个页。

样本这么少,偏差自然大,这就是"明明有索引,它偏要全表扫"最常见的原因。

统计信息还会过期,数据翻了几倍,它记的还是上一版的样子。

所以线上突然变慢,先看执行计划变没变,再看统计信息多久没更新。

打个比方:优化器像导航。

路线按历史路况估出来,真上路堵不堵,它并不知道。

导航估错不代表没用,但得知道它会估错。

救急可以强制指定索引,不过那是止血,不是治病。

五、执行器:要一行,判一行

优化器给出一份计划,执行器照着做。

有索引,就顺着索引往下找;没索引,就一行一行扫。

每拿到一行,它自己判一次 where,符合就留下,不符合丢掉,接着要下一行。

注意方向:是执行器主动去要,不是引擎把结果推上来。

于是慢日志里那两个数字有了明确含义:

指标 含义 异常时的判断
扫描行数 执行器一共向引擎要了多少行 远大于返回行数,说明索引没帮上忙
返回行数 最终发给客户端的行数 与扫描行数接近,说明过滤靠的是索引

扫描一千行、只返回十行,说明索引基本白建。

还有一点:结果不是攒齐了才发,是边取边发。

所以 limit 10 的时候,引擎可能只读了几十行就停了。

反过来,客户端收得慢,服务端就卡在发送上。

这种"慢"不是查得慢,是等得慢。

顺带一句:select * 除了多传数据,还容易让覆盖索引失效。

六、存储引擎:真正碰磁盘的地方

到这一站才碰磁盘。

InnoDB 不是按行读,是按页读,一页 16KB。

要一行,先看这行所在的页在不在内存里。

在,直接取;不在,从磁盘把这一页读进内存,再取。

所以查一行的真实开销,往往不是"读一行",是"读一页"。

同一条数据,第一次查要跑一趟磁盘,第二次就快了,差别全在缓存。

InnoDB 还有一点要记:数据本身就挂在主键索引的叶子节点上。

按主键查,走到叶子就拿到整行,一次到位。

按普通索引查,叶子节点上只有主键值。

要拿主键再回主键索引找一遍,这就是回表。

所以"尽量用主键查""尽量走覆盖索引",根子都在这里。

七、update 多出来的两步

select 到这儿结束,update 还没完。

它先按同样方式找到那一行,然后改内存里的页,不直接改磁盘。

改之前先把旧值记到 undo log,回滚要用。

改完这页成了脏页,同时这次修改记进 redo log。

三种日志各管一摊:

日志 记什么 谁写的 用来干什么
undo log 修改前的旧值 InnoDB 回滚、MVCC
redo log 页改成了什么样 InnoDB 崩溃恢复
binlog 执行了什么语句 server 层 主从同步、备份

到这儿内存里的事做完了,但事务还没提交。

提交时要保证 redo 和 binlog 对得上,靠的是两阶段提交:先写 redo 并标成准备,再写 binlog,最后把 redo 标成完成。

顺序不能反:

写入顺序 中途断电的后果
先 binlog 后 redo 从库按 binlog 重放拿到了修改,主库却回滚了,主从不一致
先 redo(准备)后 binlog 主库恢复时去 binlog 核对:有就补提交,没有就回滚

所以"提交成功"和"数据已经落盘"是两件事,靠两个刷盘参数把它们绑在一起。

八、面试官会往下问的三层

第一层:优化器凭什么觉得走索引更便宜?

按成本算,比的是读多少页、比多少行。

它估错,是因为统计信息是抽样的,样本少,还会过期。

第二层:扫描行数比返回行数大很多,说明什么?

索引没帮上忙。

常见四种:条件上套了函数、类型对不上发生隐式转换、用了 like '%x'、联合索引没用上第一列。

第三层:update 什么时候返回给客户端?

两阶段提交走完、redo 标成完成之后才返回。

返回成功就代表这次修改已经能恢复,崩了也不会丢。

还可能追问一句:binlog 能不能不要?

不能。从库靠它重放,备份、闪回也都靠它,redo 出了这台机器就没用了。

收尾

五道手里只有优化器在猜,其余都在照做。它猜错,多半不是不会算,是看到的信息不准。

下一期讲它猜得最频繁的那件事:为什么用 B+ 树,不用 B 树、哈希、跳表。

相关推荐
ShineWinsu1 小时前
对于Qt:QCheckBox常见属性的详细解析
c++·qt·面试·笔试·gui·qcheckbox·多选框
LaughingZhu1 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
师夷长技以自愈1 小时前
第 3 讲:多 Agent——Supervisor 分诊 + 专职 Worker
面试
niucloud-admin1 小时前
NIUCLOUD PHP SAAS上门家政使用文档:服务保障
java·开发语言·php
standxy1 小时前
即时库存同步实战:从旺店通拉取库存详情落库 MySQL 的完整策略
数据库·集成·电商
麦壳饼1 小时前
SonnetDB:工业 IoT 中的 PID 历史分析与 PLC 协作
数据库·sonnetdb
吹什么轩2 小时前
MySQL 事务管理:从 ACID 到 MVCC,一篇讲透
数据库·mysql
怕浪猫2 小时前
我有个朋友,工作八年,公司从 300 人减到 80 人
面试·程序员
IT枫斗者枫哥2 小时前
CompletableFuture失败后返回空列表:异常没了,业务含义也变了
java·后端