一条SQL的执行过程
在日常开发中,我们无论使用JDBC或者是可视化工具,执行SQL时,首先需要建立数据库连接,以JDBC为例,第一步加载jbcd驱动程序,接着建立连接,创建preparedStatement,执行SQL,最后处理返回结果,那么SQL在MySQL中做了什么操作呢?整个流程参考下面这张图
SQL执行流程
1.建立连接
下面开始SQL执行流程的第一步,建立连接,MySQL将连接器中的连接分为长连接和短连接。
长连接是指连接成功后,客户端请求一直使用同一个连接
短连接是指每次执行完SQL请求的操作之后会断开连接,如果再有SQL请求会重新建立连接。由于短连接会反复创建连接消耗相同资源,因此多数情况下会选择长连接。但是为了保持长连接,会占用系统内存
MySQL会在接收客户端的连接请求时,对客户端的身份权限进行检查,成功建立连接后才能继续往下进行,MySQL提供了一些连接参数供我们查看连接情况。
如何查看当前MySQL的连接(并发)数?
show global status like 'Thread%';
其中Thread_connected表示当前建立的连接数量,Threads_running表示当前运行着的线程数,大家可以多开几条连接,查看变化。
通过这行SQL,可以查看MySQL支持的最大连接数
show variables like 'max_connections';
需要调整最大并发数时,只需要改变这个值即可。
2.查询缓存
MySQL中存在一个缓存池,相当于将SQL作为key,查询的数据作为value保存在缓存池中,当下一次执行的SQL存在缓存池中时,直接取出缓存的数据返回,如果执行两次下面的语句,所需要的时间会一样吗?
select * from city where city_name = '杭州'
答案是不会的,因为MySQL默认缓存是关闭的,我们可以通过下面这条语句查看缓存开启情况
show variables like 'query_cache%'
MySQL为什么默认关闭缓存呢?主要是因为MySQL自带的缓存应用场景十分有限,首先它要求执行的SQL必须一模一样,就算中间有空格,或大小写不同都会导致缓存失效。另外,当表中的数据发生变化时,也会导致缓存失效。
这对于需要对大量数据进行更新操作的系统也不适用,所以缓存这块还是叫给数据库框架处理比较合适,例如Mybatis默认就开启了一级缓存,或者采用Redis等缓存中间件来实现更好一些,所以MySQL8.0也已经移除了缓存池。
MySQL如何判断缓存命中
再想一想,假设我们已经开启了缓存,再执行两次下面的SQL,第二次执行会不会命中缓存呢?
select * from city where create_date < CURRENT_DATE()
还是不会命中缓存,因为在包含一些不确定的函数时,不会被缓存,例如NOW()、CURRENT_DATE(),因为这些不确定的函数,使SQL变得不确定,另外,包含任何用户自定义的函数、存储过程、用户变量、临时表都不会被缓存。
如果希望带有一个日期的查询,那么最好将日期提前计算好
例如:
...DATE_SUB(CURRENT_DATE, INTERVAL 1 DAY) -- 不缓存
...DATE_SUB(‘2020-08-23’, INTERVAL 1 DAY) --缓存
3.语法解析和预处理
如果上一步没有命中缓存,那么在这一步会进行词法和语法的分析
例如这条语句
select name from city where id = 1;
首先会将这条语句切分成8个字符,再进行语法分析,例如引号有没有闭合,关键字是否存在等检查,然后再根据SQL语句生成一棵解析树
到这一步说明SQL已经通过检查,那么如果我们表名写错了呢?MySQL怎么进行检查?这就要靠下一步的预处理器来检查了。
预处理器会根据生成的解析树对表名、列名、别名等进行检查,确保不会出现歧义后重新生成一棵新的解析树。
4.查询优化执行计划
通过验证的SQL,由优化器将其转换为执行计划,一条查询可以有很多种执行方式,最后都返回相同的结果,而优化器的作用就是找到其中最好的执行计划。
举两个简单的例子:1、当我们对多张表进行关联查询的时候,以哪个表的数据作为基准表。2、有多个索引可以使用的时候,选择哪个索引。实际上,对于每一种数据库来说,优化器的模块都是必不可少的,他们通过复杂的算法实现尽可能优化查询效率的目标。
如果我们想知道优化器是怎么工作的,它生成了几种执行计划,应该怎么做呢?首先,通过下面的语句开启优化器的追踪(默认是关闭的)
SHOW VARIABLES LIKE 'optimizer_trace';
set optimizer_trace='enabled=on';
接着我们执行一个 SQL 语句,优化器会生成执行计划,执行下面的语句查看执行计划
select * from information_schema.optimizer_trace
这是一个包含三个部分的 JSON 数据,分别是准备阶段、优化阶段和执行阶段。其中,优化阶段生成了经过优化的 SQL 语句,保存在 expanded_query 字段中;执行阶段则包含了所有被考虑的执行计划,保存在 considered_execution_plans 字段中。
打开优化器追踪的开关对性能影响比较大,在分析完后记得关掉它
set optimizer_trace='enabled=off';
5.执行引擎
到这一步,MySQL会通过执行引擎调用存储引擎的API来查询结果,为什么我们切换了存储引擎,却不会影响执行引擎查询数据呢?原因是因为这些存储引擎都是用相同的API来向执行引擎提供操作的,查询出最终的结果后,把结果返回给客户端,客户端再根据需要对结果进行处理或者显示