一, 前言
本文将从 MySQL 总体架构 ---> 查询执行流程 ---> 语句执行顺序来探讨一下其中的知识.
二, MySQL 架构总览
架构最好看图, 再配上必要的说明文字.
下图根据参考书籍中一图为原本, 再在其上添加上了自己的理解.
从上图中我们可以看到, 整个架构分为两层, 上层是 MySQLD 的被称为的'SQL Layer', 下层是各种各样对上提供接口的存储引擎, 被称为'Storage Engine Layer'. 其它各个模块和组件, 从名字上就可以简单了解到它们的作用, 这里就不再累述了.
三, 查询执行流程
下面再向前走一些, 容我根据自己的认识说一下查询执行的流程是怎样的:
1, 连接
1.1, 客户端发起一条 Query 请求, 监听客户端的'连接管理模块'接收请求;
1.2, 将请求转发到'连接进 / 线程模块';
1.3, 调用'用户模块'来进行授权检查;
1.4 通过检查后,'连接进 / 线程模块'从'线程连接池'中取出空闲的被缓存的连接线程和客户端请求对接, 如果失败则创建一个新的连接请求;
2, 处理
2.1, 先查询缓存, 检查 Query 语句是否完全匹配, 接着再检查是否具有权限, 都成功则直接取数据返回;
2.2, 上一步有失败则转交给'命令解析器', 经过词法分析, 语法分析后生成解析树;
2.3, 接下来是预处理阶段, 处理解析器无法解决的语义, 检查权限等, 生成新的解析树;
2.4, 再转交给对应的模块处理;
2.5, 如果是 SELECT 查询还会经由'查询优化器'做大量的优化, 生成执行计划;
2.6, 模块收到请求后, 通过'访问控制模块'检查所连接的用户是否有访问目标表和目标字段的权限;
2.7, 有则调用'表管理模块', 先是查看 table cache 中是否存在, 有则直接对应的表和获取锁, 否则重新打开表文件;
2.8, 根据表的 meta 数据, 获取表的存储引擎类型等信息, 通过接口调用对应的存储引擎处理;
2.9, 上述过程中产生数据变化的时候, 若打开日志功能, 则会记录到相应二进制日志文件中;
3, 结果
3.1,Query 请求完成后, 将结果集返回给'连接进 / 线程模块';
3.2, 返回的也可以是相应的状态标识, 如成功或失败等;
3.3,'连接进 / 线程模块'进行后续的清理工作, 并继续等待请求或断开与客户端的连接;
4, 一图小总结
四, SQL 解析顺序
接下来再走一步, 让我们看看一条 SQL 语句的前世今生.
首先看一下示例语句:
然而它的执行顺序是这样的:
虽然自己没想到是这样的, 不过一看还是很自然和谐的, 从哪里获取, 不断的过滤条件, 要选择一样或不一样的, 排好序, 那才知道要取前几条呢.
既然如此了, 那就让我们一步步来看看其中的细节吧.
1, 准备工作
1.1, 创建测试数据库
1.2, 创建测试表
1.3, 插入数据
1.4, 最后想要的结果
现在开始 SQL 解析之旅吧!
2,FROM
当涉及多个表的时候, 左边表的输出会作为右边表的输入, 之后会生成一个虚拟表 VT1.
2.1,(1-J1) 笛卡尔积
计算两个相关联表的笛卡尔积 (CROSS JOIN) , 生成虚拟表 VT1-J1.
2.2,(1-J2)ON 过滤
基于虚拟表 VT1-J1 这一个虚拟表进行过滤, 过滤出所有满足 ON 谓词条件的列, 生成虚拟表 VT1-J2.
注意: 这里因为语法限制, 使用了'WHERE'代替, 从中读者也可以感受到两者之间微妙的关系;
2.3,(1-J3) 添加外部列
如果使用了外连接 (LEFT,RIGHT,FULL), 主表 (保留表) 中的不符合 ON 条件的列也会被加入到 VT1-J2 中, 作为外部行, 生成虚拟表 VT1-J3.
下面从网上找到一张很形象的关于'SQL JOINS'的解释图, 如若侵犯了你的权益, 请劳烦告知删除, 谢谢.
2,WHERE
对 VT1 过程中生成的临时表进行过滤, 满足 WHERE 子句的列被插入到 VT2 表中.
注意:
此时因为分组, 不能使用聚合运算; 也不能使用 SELECT 中创建的别名;
与 ON 的区别:
如果有外部列, ON 针对过滤的是关联表, 主表 (保留表) 会返回所有的列;
如果没有添加外部列, 两者的效果是一样的;
应用:
对主表的过滤应该放在 WHERE;
对于关联表, 先条件查询后连接则用 ON, 先连接后条件查询则用 WHERE;
3,GROUP BY
这个子句会把 VT2 中生成的表按照 GROUP BY 中的列进行分组. 生成 VT3 表.
注意:
其后处理过程的语句, 如 SELECT,HAVING, 所用到的列必须包含在 GROUP BY 中, 对于没有出现的, 得用聚合函数;
原因:
GROUP BY 改变了对表的引用, 将其转换为新的引用方式, 能够对其进行下一级逻辑操作的列会减少;
我的理解是:
根据分组字段, 将具有相同分组字段的记录归并成一条记录, 因为每一个分组只能返回一条记录, 除非是被过滤掉了, 而不在分组字段里面的字段可能会有多个值, 多个值是无法放进一条记录的, 所以必须通过聚合函数将这些具有多值的列转换成单值;
4,HAVING
这个子句对 VT3 表中的不同的组进行过滤, 只作用于分组后的数据, 满足 HAVING 条件的子句被加入到 VT4 表中.
5,SELECT
这个子句对 SELECT 子句中的元素进行处理, 生成 VT5 表.
(5-J1) 计算表达式 计算 SELECT 子句中的表达式, 生成 VT5-J1
(5-J2)DISTINCT
寻找 VT5-1 中的重复列, 并删掉, 生成 VT5-J2
如果在查询中指定了 DISTINCT 子句, 则会创建一张内存临时表 (如果内存放不下, 就需要存放在硬盘了). 这张临时表的表结构和上一步产生的虚拟表 VT5 是一样的, 不同的是对进行 DISTINCT 操作的列增加了一个唯一索引, 以此来除重复数据.
6,ORDER BY
从 VT5-J2 中的表中, 根据 ORDER BY 子句的条件对结果进行排序, 生成 VT6 表.
注意:
唯一可使用 SELECT 中别名的地方;
7,LIMIT
LIMIT 子句从上一步得到的 VT6 虚拟表中选出从指定位置开始的指定行数据.
注意:
offset 和 rows 的正负带来的影响;
当偏移量很大时效率是很低的, 可以这么做:
采用子查询的方式优化, 在子查询里先从索引获取到最大 id, 然后倒序排, 再取 N 行结果集
采用 INNER JOIN 优化, JOIN 子句里也优先从索引获取 ID 列表, 然后直接关联查询获得最终结果
总结
至此 SQL 的解析之旅就结束了, 上图总结一下:
来源: http://www.bubuko.com/infodetail-2774831.html