引言在上篇文章中我们以《MySQL架构篇》拉开了MySQL数据库的的序幕上篇文章中将MySQL分层架构中的每一层都进行了详细阐述。而在本篇中则会进一步站在一条SQL的角度从SQL的诞生开始到SQL执行、数据返回等全链路进行分析。在此之前呢其实也写过两篇类似的姊妹篇第一篇讲的是《一个网络请求的神奇之旅》在其中化身一个网络请求切身感受了从浏览器发出后到响应数据的返回。而在更早的时候《JVM系列》中也有一篇文章讲的是《一个Java对象从诞生到死亡的历程》其中聊到了Java对象是如何诞生的运行时会经历什么过程使用结束后又是如何回收的。在本篇中则会在站在数据库的视角再次感受“一条SQL多姿多彩的历程”你如果认真的看完了“一个请求、一个对象、一条SQL”这三部曲后相信你对于程序开发又会有一个全新的深刻认知。一、一条SQL是如何诞生的SQL语句都诞生于客户端主要有两种方式产生一条SQL一种是由开发者自己手动编写另一种则是相关的ORM框架自动生成一般情况下MySQL运行过程中收到的大部分SQL都是由ORM框架生成的比如Java中的MyBatis、Hibernate框架等。同时SQL生成的时机一般都与用户的请求有关当用户在系统中进行了某项操作一般都会产生一条SQL例如我们在浏览器上输入如下网址https://juejin.cn/user/862486453028888/posts此时就会先请求掘金的服务器然后由掘金内部实现中的ORM框架根据请求参数生成一条SQL类似于下述的伪SQLselect * from juejin_article where userid 862486453028888;这条SQL大致描述的意思就是根据用户请求的「作者ID」在掘金数据库的文章表中查询该作者的所有文章信息。从上述这个案例中可以明显感受出来用户浏览器上看到的数据一般都来自于数据库而数据库执行的SQL则源自于用户操作两者是相辅相成的关系也包括任何写操作增、删、改本质上也会被转换一条条SQL也举个简单的例子# 请求网址Request URL https://www.xxx.com/user/register # 请求参数Request Param { user_name : 竹子爱熊猫, user_pwd : 123456, user_sex : 男, user_phone : 18888888888, ...... } # 这里对于用户密码的处理不够严谨没有做加密操作不要在意~比如上述这个用户注册的案例当用户在网页上点击「注册」按钮时会向目标网站的服务器发送一个post请求紧接着同样会根据请求参数生成一条SQL如下insert into table_user (user_name,user_pwd,user_sex,user_phone,....) VALUES (竹子爱熊猫, 123456, 男, 18888888888, ....);也就是说一条SQL的诞生都源自于一个用户请求在开发程序时SQL的大体逻辑我们都会由业务层的编码决定具体的SQL语句则是根据用户的请求参数以及提前定制好的“SQL骨架”拼揍而成。当然在Java程序或其他语言编写的程序中只能生成SQL而SQL真正的执行工作是需要交给数据库去完成的。二、一条SQL执行前会经历的过程经过上一步之后一条完整的SQL就诞生了为了SQL能够正常执行首先会先去获取一个数据库连接对象上篇关于MySQL的架构篇曾聊到过MySQL连接层中会维护着一个名为「连接池」的玩意儿但相信大家也都接触过「数据库连接池」这个东西比如Java中的C3P0、Druid、DBCP....等各类连接池。那此时在这里可以思考一个问题为什么数据库自己维护了连接池的情况下在MySQL客户端中还需要再次维护一个数据库连接池呢接下来一起聊一聊。2.1、数据库连接池的必要性众所周知当要在Java中创建一个数据库连接时首先会去读取配置文件中的连接地址、账号密码等信息然后根据配置的地址信息发起网络请求获取数据库连接对象。在这个过程中由于涉及到了网络请求那此时必然会先经历TCP三次握手的过程同时获取到连接对象完成SQL操作后又要释放这个数据库连接此时又需要经历TCP四次挥手过程。从上面的描述中可以明显感知出在Java中创建、关闭数据库连接的过程过程开销其实比较大而在程序上线后又需要频繁进行数据库操作。因此如果每次操作数据库时都获取新的连接对象那整个Java程序至少会有四分之一的时间内在做TCP三次握手/四次挥手工作这对整个系统造成的后果可想而知....也正是由于上述原因因此大名鼎鼎的「数据库连接池」登场了「数据库连接池」和「线程池」的思想相同会将数据库连接这种较为珍贵的资源利用池化技术对这种资源进行维护。也就代表着之后需要进行数据库操作时不需要自己去建立连接了而是直接从「数据库连接池」中获取用完之后再归还给连接池以此达到复用的效果。当然连接池中维护的连接对象也不会一直都在当长时间未进行SQL操作时连接池也会销毁这些连接对象而后当需要时再次创建不过何时创建、何时销毁、连接数限制等等这些工作都交给了连接池去完成无需开发者自身再去关注。在Java中目前最常用的数据库连接池就是阿里的Druid一般咱们都会用它作为生产环境中的连接池Druid目前Druid已经被阿里贡献给Apache软件基金会维护了~OK~回到前面抛出的问题有了MySQL连接池为何还需要在客户端维护一个连接池对于这个问题相信大家在心里多少都有点答案了原因很简单两者都是利用池化技术去达到复用资源、节省开销、提升性能的目的只不过针对的方向不同。MySQL的连接池主要是为了实现复用线程的目的因为每个数据库连接在MySQL中都会使用一条线程维护而每次为客户端分配连接对象时都需要经历创建线程、分配栈空间....这些繁重的工作这个过程需要时间同时资源开销也不小所以MySQL利用池化技术解决了这些问题。而客户端的连接池主要是为了实现复用数据库连接的目的因为每次SQL操作都需要经过TCP三次握手/四次挥手的过程过程同样耗时且占用资源因此也利用池化技术解决了这个问题。其实也可以这样理解MySQL连接池维护的是工作线程客户端连接池则维护的是网络连接。2.2、SQL执行前会发生的事情回归本文主题当完整的SQL生成后会先去连接池中尝试获取一个连接对象那接下来会发生什么事情呢如下图获取连接当尝试从连接池中获取连接时如果此时连接池中有空闲连接可以直接拿到复用但如果没有则要先判断一下当前池中的连接数是否已达到最大连接数如果连接数已经满了当前线程则需要等待其他线程释放连接对象没满则可以直接再创建一个新的数据库连接使用。假设此时连接池中没有空闲连接需要再次创建一个新连接那么就会先发起网络请求建立连接。首先会经过《TCP的三次握手过程》对于这块就不再细聊了毕竟之前聊过很多次了。当网络连接建立成功后也就等价于在MySQL中创建了一个客户端会话然后会发生下图一系列工作SQL执行前的工作• ①首先会验证客户端的用户名和密码是否正确• 如果用户名不存在或密码错误则抛出1045的错误码及错误信息。• 如果用户名和密码验证通过则进入第②步。• ②判断MySQL连接池中是否存在空闲线程• 存在直接从连接池中分配一条空闲线程维护当前客户端的连接。• 不存在创建一条新的工作线程映射内核线程、分配栈空间....。• ③工作线程会先查询MySQL自身的用户权限表获取当前登录用户的权限信息并授权。到这里为止执行SQL前的准备工作就完成了已经打通了执行SQL的通道下一步则是准备执行SQL语句工作线程会等待客户端将SQL传递过来。三、一条SQL语句在数据库中是如何执行的经过连接层的一系列工作后接着客户端会将要执行的SQL语句通过连接发送过来然后会进行MySQL服务层进行处理不过根据用户的操作不同MySQL执行SQL语句时也会存在些许差异这里是指读操作和写操作两者SQL的执行过程并不相同下面先来看看select语句的执行过程。3.1、一条查询SQL的执行过程在分析查询SQL的执行流程之前咱们先模拟一个案例以便于后续分析-- SQL语句 SELECT user_id FROM zz_user WHERE user_sex 男 AND user_name 竹子④号; -- 表数据 ---------------------------------------------- | user_id | user_name | user_sex | user_phone | ---------------------------------------------- | 1 | 竹子①号 | 男 | 18888888888 | | 2 | 竹子②号 | 男 | 13588888888 | | 3 | 竹子③号 | 男 | 15688888888 | | 4 | 熊猫①号 | 女 | 13488888888 | | 5 | 熊猫②号 | 女 | 18588888888 | | 6 | 竹子④号 | 男 | 17777777777 | | 7 | 熊猫③号 | 女 | 16666666666 | ----------------------------------------------先上个SQL执行的完整流程图后续再逐步分析每个过程SQL执行过程• ①先将SQL发送给SQL接口SQL接口会对SQL语句进行哈希处理。• ②SQL接口在缓存中根据哈希值检索数据如果缓存中有则直接返回数据。• ③缓存中未命中时会将SQL交给解析器解析器会判断SQL语句是否正确• 错误抛出1064错误码及相关的语法错误信息。• 正确将SQL语句交给优化器处理进入第④步。• ④优化器根据SQL制定出不同的执行方案并择选出最优的执行计划。• ⑤工作线程根据执行计划调用存储引擎所提供的API获取数据。• ⑥存储引擎根据API调用方的操作去磁盘中检索数据索引、表数据....。• ⑦发生磁盘IO后对于磁盘中符合要求的数据逐条返回给SQL接口。• ⑧SQL接口会对所有的结果集进行处理剔除列、合并数据....并返回。上述是一个简单的流程概述一般情况下查询SQL的执行都会经过这些步骤下面再将每一步拆开详细聊一聊。SQL接口会干的工作当客户端将SQL发送过来之后SQL紧接着会交给SQL接口处理首先会对SQL做哈希处理也就是根据SQL语句计算出一个哈希值然后去「查询缓存」中比对如果缓存中存在相同的哈希值则代表着之前缓存过相同SQL语句的结果那此时则直接从缓存中获取结果并响应给客户端。在这里如果没有从缓存中查询到数据紧接着会将SQL语句交给解析器去处理。SQL接口除开对SQL进行上述的处理外后续还会负责处理结果集稍后分析。解析器中会干的工作解析器收到SQL后会开始检测SQL是否正确也就是做词法分析、语义分析等工作在这一步解析器会根据SQL语言的语法规则判断客户端传递的SQL语句是否合规如果不合规就会返回1064错误码及错误信息ERROR 1064 (42000): You have an error in your SQL syntax; check....但如果SQL语句没有问题此时就会对SQL语句进行关键字分析也就是根据SQL中的SELECT、UPDATE、DELETE等关键字先判断SQL语句的操作类型是读操作还是写操作然后再根据FROM关键字来确定本次SQL语句要操作的是哪张表也会根据WHERE关键字后面的内容确定本次SQL的一些结果筛选条件.....。总之经过关键字分析后一条SQL语句要干的具体工作就会被解析出来。解析了SQL语句中的关键字之后解析器会根据分析出的关键字信息生成对应的语法树然后交给优化器处理。在这一步也就相当于Java中的.java源代码变为.class字节码的过程目的就是将SQL语句翻译成数据库可以看懂的指令。优化器中会干的工作经过解析器的工作后会得到一个SQL语法树也就是知道了客户端的SQL大体要干什么事情了接着优化器会对于这条SQL给出一个最优的执行方案也就是告诉工作线程怎么执行效率最高、最节省资源以及时间。优化器最开始会根据语法树制定出多个执行计划然后从多个执行计划中选择出一个最好的计划交给工作线程去执行但这里究竟是如何选择最优执行计划的相信大家也比较好奇那此时我们结合前面给出的案例分析一下。SELECT user_id FROM zz_user WHERE user_sex 男 AND user_name 竹子④号;先来看看对于这条SQL而言总共有几种执行方案呢答案是两种。• ①先从表中将所有user_sex男的数据查出来再从结果中获取user_name竹子④号的数据。• ②先从表中寻找user_name竹子④号的数据再从结果中获得user_sex男的数据。再结合前面给出的表数据暂且分析一下上述两种执行计划哪个更好呢---------------------------------------------- | user_id | user_name | user_sex | user_phone | ---------------------------------------------- | 1 | 竹子①号 | 男 | 18888888888 | | 2 | 竹子②号 | 男 | 13588888888 | | 3 | 竹子③号 | 男 | 15688888888 | | 4 | 熊猫①号 | 女 | 13488888888 | | 5 | 熊猫②号 | 女 | 18588888888 | | 6 | 竹子④号 | 男 | 17777777777 | | 7 | 熊猫③号 | 女 | 16666666666 | ----------------------------------------------如果按照第①种方案执行此时会先得到四条user_sex男的数据然后再从四条数据中查找user_name竹子④号的数据。如果按照第②中方案执行此时会直接得到一条user_name竹子④号的数据然后再判断一下user_sex是否为男是则直接返回否则返回空。相较于两种执行方案的过程前者需要扫一次全表然后再对结果集逐条判断。而第二种方案扫一次全表后只需要再判断一次就可以了很明显可以感知出第②种执行计划是最优的因此优化器会给出第②种执行计划。经过上述案例的讲解后大家应该能够对优化器的工作进一步理解。但上述案例仅是为了帮助大家理解实际的SQL优化过程会更加复杂例如多表join查询时怎么查更合适单表复杂SQL查询时有多条索引可以走走哪条速度最快....因此一条SQL的最优执行计划需要结合多方面的优化策略来生成例如MySQL优化器的一些优化准则如下• ❶多条件查询时重排条件先后顺序将效率更好的字段条件放在前面。• ❷当表中存在多个索引时选择效率最高的索引作为本次查询的目标索引。• ❸使用分页Limit关键字时查询到对应的数据条数后终止扫表。• ❹多表join联查时对查询表的顺序重新定义同样以效率为准。• ❺对于SQL中使用函数时如count()、max()、min()...根据情况选择最优方案。•max()函数走B树最右侧的节点查询大的在右小的在左。•min()函数走B树最左侧的节点查询。•count()函数如果是MyISAM引擎直接获取引擎统计的总行数。•......• ❻对于group by分组排序会先查询所有数据后再统一排序而不是一开始就排序。• ❼......总之根据SQL不同优化器也会基于不同的优化准则选择出最佳的执行计划。但需要牢记的一点是**MySQL虽然有优化器但对于效率影响最大的还是SQL本身因此编写出一条优秀的SQL才是提升效率的最大要素**。存储引擎中会干的工作经过优化器后会得到一个最优的执行计划紧接着工作线程会根据最优计划去依次调用存储引擎提供的API在上篇文章中提到过存储引擎主要就是负责在磁盘读写数据的不同的存储引擎存储在本地磁盘中的数据结构也并不相同但这些底层实现并不需要MySQL的上层服务关心因为上层服务只需要负责调用对应的API即可存储引擎的API功能都是相同的。工作线程根据执行计划调用存储引擎的API查询指定的表最终也就是会发生磁盘IO从磁盘中检索数据当然检索的数据有可能是磁盘中的索引文件也有可能是磁盘中的表数据文件这点要根据执行计划来决定我们只需要记住经过这一步之后总能够得到执行结果即可。但有个小细节还记得最开始创建数据库连接时对登录用户的授权步骤嘛当工作线程去尝试查询某张表时会首先判断一下线程自身维护的客户端连接其登录的用户是否具备这张表的操作权限如果不具备则会直接返回权限不足的错误信息。不过存储引擎从磁盘中检索出目标数据后并不会将所有数据全部得到后再返回而是会逐条返回给SQL接口然后会由SQL接口完成最后的数据聚合工作对于这点稍后会详细分析。下来再来看看写入SQL的执行过程因为读取和写入操作之间也会存在些许差异。3.2、一条写入SQL的执行过程假设此时要执行下述这一条写入类型的SQL语句还是基于之前的表数据UPDATE zz_user SET user_sex 女 WHERE user_id 6;上面这条SQL是一条典型的修改SQL但除开修改操作外新增、删除等操作也属于写操作写操作的意思是指会对表中的数据进行更改。同样先上一个完整的流程图写SQL执行流程从上图来看相较于查询SQL写操作的SQL执行流程明显会更复杂一些这里也先简单总结一下每一步流程然后再详细分析一下其中一些与查询SQL中不同的步骤• ①先将SQL发送给SQL接口SQL接口会对SQL语句进行哈希处理。• ②在缓存中根据哈希值检索数据如果缓存中有则将对应表的所有缓存全部删除。• ③经过缓存后会将SQL交给解析器解析器会判断SQL语句是否正确• 错误抛出1064错误码及相关的语法错误信息。• 正确将SQL语句交给优化器处理进入第④步。• ④优化器根据SQL制定出不同的执行方案并择选出最优的执行计划。• ⑤在执行开始之前先记录一下undo-log日志和redo-log(prepare状态)日志。• ⑥在缓冲区中查找是否存在当前要操作的行记录或表数据内存中• ⑦根据执行计划调用存储引擎的API。• ⑧发生磁盘IO对磁盘中的数据做写操作。• ⑦直接对缓冲区中的数据进行写操作。• ⑧然后利用Checkpoint机制刷写到磁盘。• 存在• 不存在• ⑨写操作完成后记录bin-log日志同时将redo-log日志中的记录改为commit状态。• ⑩将SQL执行耗时及操作成功的结果返回给SQL接口再由SQL接口返回给客户端。整个写SQL的执行过程前面的一些步骤与查SQL执行的过程没太大差异唯一一点不同的在于缓存那里原本查询时是从缓存中尝试获取数据。而写操作时由于要对表数据发生更改因此如果在缓存中发现了要操作的表存在缓存则需要将整个表的所有缓存清空确保缓存的强一致性。OK~除开上述这点区别外另外多出了唯一性判断、一个缓冲区写入以及几个写入日志的步骤接下来一起来聊聊这些。唯一性判断主要是针对插入、修改语句来说的因为如果表中的某个字段建立了唯一约束或唯一索引后当插入/修改一条数据时就会先检测一下目前插入/修改的值是否与表中的唯一字段存在冲突如果表中已经存在相同的值则会直接抛出异常反之会继续执行。很简单哈~接着再来聊聊缓冲区和日志其实在上篇中聊到过由于CPU和磁盘之间的性能差距实在过大因此MySQL中会在内存中设计一个「缓冲区」的概念主要目的是在于弥补CPU与磁盘之间的性能差距。缓冲区中会做的工作在真正调用存储引擎的API操作磁盘之前首先会在「缓冲区」中查找有没有要操作的目标数据/目标表如果存在则直接对缓冲区中的数据进行操作然后MySQL会在后台以一种名为Checkpoint的机制将缓冲区中更新的数据刷回到磁盘。只有当缓冲区没有找到目标数据时才会去真正调用存储引擎的API然后发生磁盘IO去对应磁盘中的表数据进行修改。不过值得注意的一点是虽然缓冲区中有数据时会先操作缓冲区然后再通过Checkpoint机制刷写磁盘但这两个过程不是连续的也就是说当线程对缓冲区中的数据操作完成后会直接往下走数据落盘的工作则会交给后台线程。不过虽然两者之间是异步的但对于人而言这个过程不会有太大的感知毕竟CPU在运行的时候都是按纳秒、微秒级作为单位。但不管数据是在缓冲区还是磁盘本质上数据更改的动作都是发生在内存的就算是修改磁盘数据也是将数据读到内存中操作然后再将数据写回磁盘。不过在「写SQL」执行的前后都会记录日志这点下面详细聊聊这也是写SQL与读SQL最大的区别。写操作时的日志执行「读SQL」一般都不会有状态也就是说MySQL执行一条select语句几乎不会留下什么痕迹。但这里为什么用几乎这个词呢因为查询时也有些特殊情况会留下“痕迹”就是慢查询SQL**慢查询SQL**查询执行过程耗时较长的SQL记录。在执行查询SQL时大多数的普通查询MySQL并不关心但慢查询SQL除外这类SQL一般是引起响应缓慢问题的“始作俑者”所以当一条查询SQL的执行时长超过规定的时间限制就会被“记录在案”也就是会记录到慢查询日志中。与「查询SQL」恰恰相反任何一条写入类型的SQL都是有状态的也就代表着只要是会对数据库发生更改的SQL执行时都会被记录在日志中。首先所有的写SQL在执行之前都会生成对应的撤销SQL撤销SQL也就是相反的操作比如现在执行的是insert语句那这里就生成对应的delete语句....然后记录在undo-log撤销/回滚日志中。但除此之外还会记录redo-log日志。redo-log日志是InnoDB引擎专属的主要是为了保证事务的原子性和持久性这里会将写SQL的事务过程记录在案如果服务器或者MySQL宕机重启时就可以通过redo_log日志恢复更新的数据。在「写SQL」正式执行之前就会先记录一条prepare状态的日志表示当前「写SQL」准备执行然后当执行完成并且事务提交后这条日志记录的状态才会更改为commit状态。除开上述的redo-log、undo-log日志外同时还会记录bin-log日志这个日志和redo-log日志很像都是记录对数据库发生更改的SQL只不过redo-log是InnoDB引擎专属的而bin-log日志则是MySQL自带的日志。不过无论是什么日志都需要在磁盘中存储而本身「写SQL」在磁盘中写表数据效率就较低了此时还需写入多种日志效率定然会更低。对于这个问题MySQL以及存储引擎的设计者自然也想到了所以大部分日志记录也是采用先写到缓冲区中然后再异步刷写到磁盘中。比如redo-log日志在内存中会有一个redo_log缓冲区中bin-log日志也同理当需要记录日志时都是先写到内存中的缓冲区。那内存中的日志数据何时会刷写到磁盘呢对于这点则是由刷盘策略来决定的redo-log日志的刷盘策略由innodb_flush_log_at_trx_commit参数控制而bin-log日志的刷盘策略则可以通过sync_binlog参数控制•innodb_flush_log_at_trx_commit•0有事务提交的情况下每间隔一秒时间刷写一次日志到磁盘。•1每次提交事务时都刷写一次日志到磁盘性能最差最安全默认策略。•2每当事务提交时把日志记录放到内核缓冲区刷写的时机交给OS控制性能最佳。•sync_binlog•0同上述innodb_flush_log_at_trx_commit参数的2交给OS控制默认。•1同上述innodb_flush_log_at_trx_commit参数的1每次提交事务都会刷盘。到这里就大致阐述了一下「写SQL」执行时会写的一些日志记录这些日志在后续做数据恢复、迁移、线下排查时都较为重要因此后续也会单开一篇详细讲解。四、一条SQL执行完成后是如何返回的一条「读SQL」或「写SQL」执行完成后由于SQL操作的属性不同两者之间也会存在差异性4.1、读类型的SQL返回前面聊到过MySQL执行一条查询SQL时数据是逐条返回的模式因为如果等待所有数据全部查出来之后再一次性返回必然会导致撑满内存。不过这里的返回并不是指返回客户端而是指返回SQL接口因为从磁盘中检索出目标数据时一般还需要对这些数据进行再次处理举个例子理解一下。SELECT user_id FROM zz_user WHERE user_sex 男 AND user_name 竹子④号;还是之前那条查询SQL这条SQL要求返回的结果字段仅有一个user_id但在磁盘中检索数据时会直接将这个字段单独查询出来吗并不是的而是会将整条行数据全部查询出来如下---------------------------------------------- | user_id | user_name | user_sex | user_phone | ---------------------------------------------- | 6 | 竹子④号 | 男 | 17777777777 | ----------------------------------------------从行记录中筛选出最终所需的结果字段这个工作是在SQL接口中完成的也包括多表联查时数据的合并工作同样也是在SQL接口完成其他SQL亦是同理。还有一点需要牢记就算没有查询到数据也会将执行状态、执行耗时这些信息返回给SQL接口然后由SQL接口向客户端返回NULL。不过当查询到数据后在正式向客户端返回之前还会顺手将结果集放入到缓存中。4.2、写类型的SQL返回写SQL执行的过程会比读SQL复杂但写SQL的结果返回却很简单写类型的操作执行完成之后仅会返回执行状态、受影响的行数以及执行耗时比如UPDATE zz_user SET user_sex 女 WHERE user_id 6;这条SQL执行成功后会返回Query OK, 1 row affected (0.00 sec)这组结果最终返回给客户端的则只有「受影响的行数」如果写SQL执行成功这个值一般都会大于0反之则会等于0。4.3、执行结果是如何返回给客户端的对于这个问题的答案其实很简单由于执行当前SQL的工作线程本身也维护着一个数据库连接这个数据库连接实际上也维持着客户端的网络连接如下线程查询当结果集处理好了之后直接通过Host中记录的地址将结果集封装成TCP数据报然后返回即可。数据返回给客户端之后除非客户端主动输入exit等退出连接的命令否则连接不会立马断开。如果要断开客户端连接时又会经过TCP四次挥手的过程。不过就算与客户端断开了连接MySQL中创建的线程并不会销毁而是会放入到MySQL的连接池中等待其他客户端复用当前连接。一般情况下一条线程在八小时内未被复用才会触发MySQL的销毁工作。五、SQL执行篇总结看到这里SQL执行原理篇也走进了尾声其实SQL语句的执行过程实际上也就是MySQL的架构中一层层对其进行处理理解了MySQL架构篇的内容后相信看SQL执行篇也不会太难经过这篇文章的学习后相信大家对数据库的原理知识也能够进一步掌握那我们下篇再见~