当前位置: 首页 > 科技观察

一步步深入MySQL:架构--查询执行流程--SQL解析顺序

时间:2023-03-14 08:35:50 科技观察

1.在前言中,我一直想知道一个SQL语句是如何执行的,执行的顺序,然后回顾和总结各方信息,有以下博文。本文将从MySQL的整体架构--->查询执行流程--->语句执行顺序来探讨知识点。二、MySQL架构概述Architecture***看图,然后加上必要的说明文字。下图是根据参考书上的原图,然后在上面加上自己的理解。从上图我们可以看出整个架构分为两层,上层称为MySQLD的'SQL层',下层是向上层提供接口的各种存储引擎,称为'存储引擎层''。其他的模块和组件,从名字上就可以很容易地理解它们的功能,这里就不再赘述了。3、查询执行过程再往前说一点,我根据自己的知识告诉大家查询执行过程是怎样的:1.连接1.1.客户端发起Query请求,监听客户端的“连接管理模块”接收请求;1.2.将请求转发给“连接/线程模块”;1.3.调用'用户模块'检查权限;1.4检查通过后,'Connection/ThreadModule'取出空闲已使用缓存的连接线程与客户端请求对接,如果失败则创建新的连接请求;2、处理2.1、先查询缓存,检查Query语句是否完全匹配,再检查是否有权限。成功则直接取数据返回;2.2。如果上一步失败,就会转到“命令解析器”。经过词法分析和句法分析,会生成解析树;2.3.接下来是预处理阶段,处理解析器无法解析的语义,检查权限等,生成新的2.4,然后传递给相应的模块进行处理;2.5、如果是SELECT查询,会通过'queryoptimizer'做很多优化,生成执行计划;2.6、模块收到请求后,会通过'访问控制模块'检查连接的用户是否有访问目标表和目标字段的权限;2.7、如果有,调用'表管理模块',首先检查表缓存是否存在,如果有,直接对应表并获取锁,否则重新打开表文件;2.8.根据表的元数据,获取表的存储引擎类型等信息,通过接口调用相应的存储引擎进行处理;2.9.当上述过程中数据发生变化时,如果开启了日志功能,则会记录到相应的二进制日志文件中;3.结果3.1。Query请求完成后,将结果集返回给‘连接/线程模块’;3.2.返回也可以是相应的状态标识,比如成功或者失败;3.3.‘Connection/Thread模块’进行后续清理工作,继续等待请求或者断开客户端连接;4.一张图的小总结4.SQL解析序列接下来,让我们来看看一条SQL语句的前世今生。先看例子语句:SELECTDISTINCTFROMJOINONWHEREGROUPBYHAVINGORDERBYLIMIT然而,其执行order是这样的:虽然没想到会是这样,但是一看还是很自然和谐的。从哪里获取,不断筛选条件。您必须选择相同或不同的并按顺序排列。然后你就知道如何获得前几项了。呢绒布。既然如此,让我们一步步来看细节。一、准备工作1.1.创建测试数据库createdatabasetestQuery1.2。创建测试表CREATETABLEtable1(uidVARCHAR(10)NOTNULL,nameVARCHAR(10)NOTNULL,PRIMARYKEY(uid))ENGINE=INNODBDEFAULTCHARSET=UTF8;CREATETABLEtable2(oidINTNOTNULLauto_increment(10VAR)PRIMARYKEY(oid))ENGINE=INNODBDEFAULTCHARSET=UTF8;1.3、插入数据INSERTINTTOtable1(uid,name)VALUES('aaa','mike'),('bbb','jack'),('ccc','mike'),('ddd','mike');INSERTINTOtable2(uid)VALUES('aaa'),('aaa'),('bbb'),('bbb'),('bbb'),('ccc'),(NULL);1.4、***想要的结果SELECTa.uid,count(b.oid)AStotalFROMtable1ASaLEFTJOINtable2ASbONa.uid=b.uidWHEREa.NAME='mike'GROUPBYa.uidHAVINGcount(b.oid)<2ORDERBYtotalDESCLIMIT1;现在就开始SQL分析之旅吧!2、当FROM涉及到多张表时,会把左表的输出作为右表的输入,然后生成一个虚表VT1。2.1、(1-J1)笛卡尔积计算两个关联表的两个笛卡尔积(CROSSJOIN)生成虚表VT1-J1。mysql>select*fromtable1,table2;+-----+-----+-----+-----+|uid|name|oid|uid|+-----+-----+-----+-----+|aaa|mike|1|aaa||bbb|jack|1|aaa||ccc|mike|1|aaa||ddd|mike|1|aaa||aaa|mike|2|aaa||bbb|jack|2|aaa||ccc|mike|2|aaa||ddd|mike|2|aaa||aaa|mike|3|bbb||bbb|jack|3|bbb||ccc|mike|3|bbb||ddd|mike|3|bbb||aaa|mike|4|bbb||bbb|jack|4|bbb||ccc|mike|4|bbb||ddd|mike|4|bbb||aaa|mike|5|bbb||bbb|jack|5|bbb||ccc|mike|5|bbb||ddd|mike|5|bbb||aaa|mike|6|ccc||bbb|jack|6|ccc||ccc|mike|6|ccc||ddd|mike|6|ccc||aaa|mike|7|NULL||bbb|jack|7|NULL||ccc|mike|7|NULL||ddd|mike|7|NULL|+-----+-----+-----+-----+28rowsinset(0.00sec)2.2、(1-J2)ON过滤是基于虚拟表VT1-J1,过滤掉所有满足ON谓词条件的列,生成虚拟表VT1-J2。注:由于语法限制,改用'WHERE',读者也能从中感受到两者微妙的关系;mysql>SELECT->*->FROM->table1,->table2->WHERE->table1.uid=table2.uid->;+-----+------+-----+------+|uid|name|oid|uid|+-----+------+-----+------+|aaa|mike|1|aaa||aaa|迈克|2|aaa||bbb|杰克|3|bbb||bbb|jack|4|bbb||bbb|jack|5|bbb||ccc|mike|6|ccc|+-----+-----+-----+------+6rowsinset(0.00sec)2.3、(1-J3)添加外部列如果使用外部连接(LEFT、RIGHT、FULL),主表(保留表)中不满足ON条件的列也会添加到VT1-J2中,生成一个虚拟表VT1-J3作为外部行。mysql>SELECT->*->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid;+-----+-----+-----+------+|uid|名称|oid|uid|+-----+---+-----+-----+|aaa|mike|1|aaa||aaa|mike|2|aaa||bbb|jack|3|bbb||bbb|jack|4|bbb||bbb|jack|5|bbb||ccc|mike|6|ccc||ddd|mike|NULL|NULL|+-----+-----+-----+-----+7rowsinset(0.00sec)下面我从网上找了一个很形象的关于'SQLJOINS'的解释图,如果侵犯了您的权益,请告诉我删除,谢谢。2、WHERE过滤VT1过程中生成的临时表,将满足WHERE子句的列插入到VT2表中。注意:此时因为分组,不能使用聚合操作;也不能使用在SELECT中创建的别名;与ON的区别:如果有外部列,ON是过滤关联表,主表(保留表)会返回所有列;如果不加外部列,两者效果一样;Application:主表的filter应该放在WHERE;对于关联表,如果连接前查询条件使用ON,连接后条件查询应该使用WHERE;mysql>SELECT->*->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike';+-----+-----+------+-----+|uid|name|oid|uid|+-----+-----+-----+-----+|aaa|mike|1|aaa||aaa|mike|2|aaa||ccc|mike|6|ccc||ddd|mike|NULL|NULL|+-----+-----+------+-----+4rowsinset(0.00sec)3.GROUPBY子句将VT2中生成的表根据GROUPBY中的列进行分组。生成VT3表。注意:在后处理语句中使用的列,如SELECT、HAVING,必须包含在GROUPBY中。对于没有出现的,必须使用聚合函数;原因:GROUPBY改变了对表的引用,转换为新的引用方式,可用于下一级逻辑运算的列数会减少;我的理解是:根据分组字段,分组字段相同的记录会合并为一条记录,因为每次分组只能返回一条记录,除非过滤掉,否则不在分组字段中的字段可能有多个值,并且多个值不能放在一个记录中,所以这些多值列必须通过聚合函数转换为单个值;mysql>SELECT->*->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike'->GROUPBY->a.uid;+-----+------+------+------+|uid|name|oid|uid|+-----+-----+-----+------+|aaa|mike|1|aaa||ccc|mike|6|ccc||ddd|mike|NULL|NULL|+-----+-----+------+-----+3rowsinset(0.00sec)4.HAVING子句过滤VT3表中的不同分组,只作用于分组后的数据。满足HAVING条件的子句被添加到IntheVT4表中。mysql>SELECT->*->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike'->GROUPBY->a.uid->HAVING->count(b.oid)<2;+-----+-----+-----+-----+|uid|name|oid|uid|+-----+------+-----+-----+|ccc|mike|6|ccc||ddd|mike|NULL|NULL|+-----+------+------+------+2rowsinset(0.00sec)5、SELECT子句对SELECT子句中的元素进行处理,生成VT5表。(5-J1)计算表达式计算SELECT子句中的表达式,生成VT5-J1(5-J2)DISTINCT查找VT5-1中重复的列,并删除,如果在查询DISTINCT子句中指定,则生成VT5-J2,会创建一个内存临时表(如果内存放不下,需要存入硬盘)。这个临时表的表结构和上一步生成的虚拟表VT5是一样的。不同的是在DISTINCT操作的列上增加了唯一索引,去除重复数据。mysql>SELECT->a.uid,->count(b.oid)AStotal->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike'->GROUPBY->a.uid->HAVING->count(b.oid)<2;+-----+--------+|uid|total|+-----+-------+|ccc|1||ddd|0|+-----+------+2rowsinset(0.00sec)6.ORDERBY来自VT5-J2中的表,根据ORDERBY子句根据条件对结果进行排序,生成VT6表。注意:SELECT中唯一可以使用别名的地方;mysql>SELECT->a.uid,->count(b.oid)AStotal->FROM->table1ASa->LEFTOUTERJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike'->GROUPBY->a.uid->HAVING->count(b.oid)<2->ORDERBY->totalDESC;+-----+------+|uid|total|+-----+------+|ccc|1||ddd|0|+-----+------+2rowsinset(0.00sec)7LIMITLIMIT子句从上一步得到的VT6虚表中选择指定位置开始的指定行数据。注意:offset和rows的正负影响;当offset很大,效率很低,可以这样做:使用子查询优化,先从子查询中的索引中获取***id,然后倒序排序,然后取N行结果设置并使用INNERJOIN优化。在JOIN子句中,也优先从索引中获取ID列表,然后最终结果直接相关查询mysql>SELECT->a.uid,->count(b.oid)AStotal->FROM->table1ASa->LEFTJOINtable2ASbONa.uid=b.uid->WHERE->a.NAME='mike'->GROUPBY->a.uid->HAVING->count(b.oid)<2->ORDERBY->totalDESC->LIMIT1;+-----+------+|uid|总计|+-----+------+|ccc|1|+-----+-------+1rowinset(0.00sec)至此,SQL解析的旅程就结束了。上图总结: