一条 SQL 从发出到返回结果,经历了哪些组件、做了什么?本文以 Server 层 + 存储引擎层为主线,拆解 MySQL 逻辑架构及查询/更新语句的完整执行路径。
目录
| 章节 | 说明 |
|---|---|
| 整体架构 | Server 层与存储引擎层的分工 |
| 查询语句执行流程 | 连接器→分析器→优化器→执行器 |
| 更新语句执行流程 | 多了 redo log 和 binlog 的写入 |
| 连接管理 | 长连接的内存问题与应对方案 |
| 查询缓存 | 为什么不建议使用 |
整体架构
关键设计:不同存储引擎共用同一个 Server 层,存储引擎以插件形式接入。
| 层级 | 职责 |
|---|---|
| Server 层 | SQL 解析、权限校验、查询优化、内置函数 |
| 存储引擎层 | 数据存储和读取,不同引擎能力不同 |
查询语句执行流程
以 SELECT * FROM T WHERE ID=10 为例:
flowchart TD
A["客户端发送查询"] --> B["连接器<br/>建立连接、认证身份、获取权限"]
B --> C{"查询缓存<br/>是否命中?"}
C -->|命中| D["直接返回结果(8.0 已删除)"]
C -->|未命中| E["分析器<br/>词法分析 + 语法分析"]
E --> F["优化器<br/>选择索引、确定 join 顺序"]
F --> G["执行器<br/>调用存储引擎接口逐行读取"]
G --> H["返回结果集"]
style D fill:#fcc,stroke:#c00
各组件职责
| 组件 | 职责 | 典型错误 |
|---|---|---|
| 连接器 | TCP 握手、用户名/密码认证、读取权限 | Access denied for user |
| 分析器 | 词法分析(识别关键字/表名/列名)+ 语法分析 | You have an error in your SQL syntax |
| 优化器 | 选择使用哪个索引、决定多表 join 顺序 | 选错索引(可用 force index 干预) |
| 执行器 | 验证表级权限,调用引擎接口逐行读取 | SELECT command denied to user |
权限校验的三层机制
为什么不在分析器阶段做权限校验?
SQL 确实先经过连接器,但连接器是透传层——只管理网络连接,收到 SQL 后原样转发给分析器,不解读 SQL 内容(职责分离)。即使连接器"看"了 SQL,仍然无法做细粒度权限校验,因为:
- 视图:
SELECT * FROM order_view要知道权限,需先展开视图得到底层表,发生在优化器阶段 - 存储过程:内部访问哪些对象,执行前不知道
- 列级权限:
SELECT *展开后的具体列,需查元数据后才能校验
三层权限校验完整链路:
① 连接建立时(连接器)
├─ 身份认证:用户名 + 密码
└─ 读取并缓存全局权限(mysql.user)→ 后续所有请求复用此缓存
② SQL 到来,执行器阶段
├─ 库级权限:有没有权限访问此 database(mysql.db)
├─ 表级权限:有没有权限操作此 table(mysql.tables_priv)
├─ 列级权限:有没有权限访问这些列(mysql.columns_priv)
└─ 视图/存储过程展开后的底层对象权限
⚠️ 重要:执行器用的是连接建立时缓存的权限快照,而非实时查权限表。因此
GRANT改权限后旧连接不会立即生效,需断开重连(或FLUSH PRIVILEGES)。
语法错误(分析器)和权限错误(执行器)是不同阶段的不同报错来源:You have an error in your SQL syntax vs SELECT command denied to user。
rows_examined 与实际扫描行数
慢查询日志中 rows_examined 表示执行器调用引擎接口的次数,不等于引擎内部实际扫描的行数(引擎一次调用可能内部扫了多行)。
更新语句执行流程
更新语句(UPDATE/INSERT/DELETE)走同样的 Server 层流程,额外多了两个日志模块:
执行器读取 ID=2 的行(通过 InnoDB 接口)
↓
执行器计算新值,调用 InnoDB 写入接口
↓
InnoDB 将操作写入 redo log(prepare 状态)
↓
执行器写入 binlog 到磁盘
↓
InnoDB 提交 redo log(commit 状态)
两阶段提交(2PC) 是为了保证 redo log 和 binlog 的一致性,详见日志系统笔记。
连接管理
长连接 vs 短连接
| 对比 | 长连接 | 短连接 |
|---|---|---|
| 定义 | 请求持续复用同一连接 | 每次查询后断开 |
| 开销 | 建连少,但内存累积 | 频繁建连,开销大 |
| 推荐 | ✅ 推荐(减少建连开销) | ❌ 不推荐 |
长连接内存累积问题
MySQL 在执行过程中的临时内存挂在连接对象上,连接断开时才释放。长连接积累下来,可能导致 OOM,现象为 MySQL 异常重启。
两种解决方案:
-- 方案1:定期断开长连接(执行大查询后主动断开)
-- 代码层主动关闭连接
-- 方案2(推荐):MySQL 5.7+ 定期重置连接资源
-- 不需要重连,不需要重新认证
mysql_reset_connection()
连接超时参数
-- 查看空闲连接超时时间(默认 8 小时)
SHOW VARIABLES LIKE 'wait_timeout';
-- 查看当前所有连接(Sleep 状态为空闲连接)
SHOW PROCESSLIST;
查询缓存
为什么不建议使用
- 失效极频繁:任何对表的更新都会清空该表所有查询缓存
- 更新频繁的表,命中率接近 0,缓存维护成本远大于收益
- MySQL 8.0 已完全删除查询缓存功能
适合使用查询缓存的唯一场景
几乎不更新的静态配置表(如系统参数表),可用 SQL_CACHE 显式指定:
-- 仅在 8.0 以下版本有效
SELECT SQL_CACHE * FROM system_config WHERE id = 1;
参考资料
- 《MySQL 实战 45 讲》— 第 01 讲:基础架构:一条 SQL 查询语句是如何执行的?
- MySQL官方文档 - The MySQL Server
评论 (0)