MySql(二)——查询执行步骤

前言

上一篇博客直接介绍了MySQL中的索引,这篇博客进一步总结MySQL中的查询机制。

MySQL体系结构简介

MySQL整体逻辑结构图如下:(这张图片来自:https://www.cnblogs.com/zhoubaojian/articles/7866292.html

 基本模块如下:

(1) Connectors指的是不同语言中与SQL的交互 
(2)Management Serveices & Utilities: 系统管理和控制工具,例如备份恢复、Mysql复制、集群等 
(3)Connection Pool: 连接池:管理缓冲用户连接、用户名、密码、权限校验、线程处理等需要缓存的需求 
(4)SQL Interface: SQL接口:接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface 
(5)Parser: 解析器,SQL命令传递到解析器的时候会被解析器验证和解析。
(6)Optimizer: 查询优化器,SQL语句在查询之前会使用查询优化器对查询进行优化。
(7) Cache和Buffer(高速缓存区): 查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。 
(8)Engine :存储引擎。存储引擎是MySql中具体的与文件打交道的子系统。也是Mysql最具有特色的一个地方。 
Mysql的存储引擎是插件式的。它根据MySql AB公司提供的文件访问层的一个抽象接口来定制一种文件访问机制(这种访问机制就叫存储引擎) 

各种存储引擎简介

这里说明一下,存储引擎是可以建立在表级别的,同一个数据库实例中,不同的表可以使用不同的存储引擎,但是一般实际中不建议这样做。下面我们开始简单介绍一下各个存储引擎。

CSV存储引擎

从名字可以看出来,如果仔细的可以意识到,实际的excel文件是CSV文件格式结尾的。其实该存储引擎的数据就是以CSV文件存储数据。

特点:

1、不能定义没有索引,所有的列必须为not null,不能设置自增列。因此无法适用于大表或者数据的在线处理。

2、可以直接对数据文件进行编辑,因此数据安全性比较低。

使用场景:通常,我们使用各种客户端软件,导入导出数据的时候,就会用到该引擎。因此该引擎常用于数据的快速导入导出。

Archive存储引擎

该引擎中,数据存储为ARZ文件格式,会采用相关的压缩协议进行数据的存储。

特点:

1、只能支持insert和select两种操作,不支持update和delete操作。

2、只允许自增id列建立索引

3、支持行级锁,但是不支持事务

4、数据占用磁盘较少(因为都进行了压缩处理)

使用场景:通常使用与日志系统和大量的用户设备指纹采集部分。

Memory存储引擎

从名字来看,无疑是内存级存储引擎,数据均存储在内存中,IO效率要比其他引擎高很多,服务器重启之后数据丢失,内存数据表默认只有16M。一般在实际场景中并不常用。

特点:

1、支持hash索引,B树索引,默认为hash索引

2、字段长度都是固定的

3、不支持大数据存储类型字段如blog,text。在创建临时表的时候,如果发现表中存在blog和text类型的字段,mysql会选择使用myisam引擎

4、支持表级锁,不支持行级锁

使用场景:等值查找热度较高的数据,查询结果内存中的计算大多都采用这种存储引擎,同时也作为临时表存储需要计算的数据。

MyIsam引擎

MySQL5.5版本之前的默认存储引擎。

特点:

1、在该存储引擎中select count(*) 无需进行数据的扫描

2、索引和数据分离存储,支持表级锁,不支持事务

3、偶尔也会作为临时表的存储引擎

InnoDB引擎

使用的最为广泛的引擎,mysql5.7 之后已经成为其默认存储引擎

特点:

1、支持事务ACID,具有数据操作的回滚和崩溃恢复。

2、支持行级锁,使用聚集索引的方式进行数据存储(上一篇博客中提到过),索引和数据存储在一起。

3、支持外键关系保证数据完整性(虽然现在外键用的很少了)

应用场景:这个就太多了,并发控制,金融行业几乎都会看到它的影子。

关于存储引擎,只需要大概了解一下即可。下面开始从MySQL查询的各个阶段入手总结一下MySQL的查询优化的常见操作。

查询优化

查询执行的大致过程

一个与MySQL交互的过程,需要经过一下几个过程:

1、客户端与服务端的通信。2、查询缓存。3、语法解析器,查询优化器选择。4、执行查询计划。5、返回查询结果。

大体流程如下图所示:

1、mysql客户端与服务端的通信

MySQL客户端与服务端的通信是“半双工”。这也就意味着,卡护短一旦开始接受数据则没法发送指令。我们可以通过show processlist 查看客户端的连接状态,如果某个客户端一直卡住,我们可以直接kill。

使用show full processlist/show processlist命令查看客户端的连接状态,针对客户端的连接状态会有一些字段来表示。

 Sleep——线程正在等待客户端发送数据

Query——连接线程正在执行查询

Locked——线程正在等待表锁的释放

Sorting result ——线程正在对结果进行排序

Sending data —— 向请求端返回数据

如果看到某个客户端状态比较慢,等待比较久,可以直接通过kill {id}的操作该线程,如下所示:

2、查询缓存

mysql是有自己的缓存机制的,其实可简单理解为这个缓存的key为SQL语句,value为查询的结果集。默认情况下mysql的缓存是关闭的,可以通过命令show varibles like 'query_cache%';查看当前缓存是否开启:

通过设置query_cache_type可以开启缓存(在配置文件中配置这个属性),但是有几个属性值:

query_cache_type =0 —— 缓存关闭。

query_cache_type=1 —— 完全启用查询缓存,只符合查询缓存的要求,客户端的查询语句和记录集都可以缓存起来,供其他客户端使用,也可以直接在配置文件中指定这个属性。

query_cache_type=2 —— 按需启用查询缓存,在SQL语句中加上SQL_NO_CACHE,则该查询语句就不会被缓存。查询语句中添加了SQL_CACHE,且符合查询缓存的要求,客户端的查询语句就会被缓存起来

 上图为开启缓存后的状态字段:

query_cache_limit:限制查询缓存区最大能缓存的记录。默认为1M,查询的结果大于query_cache_limit设置的大小的时候,结果并不会被缓存

query_cahce_size:总的缓存池的大小,一般推荐设置为64M/128M

show status like 'Qcache%';可以查看当前的缓存情况。如下所示:

Qcache_hits —— 缓存被命中的次数,Qcache_inserts —— 缓存的记录条数。下面通过一条简单的查询来进行测试

 之前提到过,缓存的key可以理解为SQL语句,所以SQL语句有一点区别,都会增加一条缓存记录,如下所示:

 如果使用 select sql_no_cache * from users where id = XXX,这样这条语句并不会被缓存

同时,需要说明一点的是:表中数据的任何一个修改,都会让mysql的缓存失效,如下图所示

修改了id为5的记录的数据之后,依旧用之前缓存的查询语句,却无法命中缓存,原有就是由于表数据更新了,缓存失效。 

同时,在SQL中使用函数,mysql缓存也会失效,例如:select *,curdate() from users;缓存会失效,这个其实也比较好理解,因为函数每次执行的结果可能不同,mysql自然不会放到缓存中。除此之外,查询系统表,查询视图均不会放入缓存

ps:不会被缓存的操作用红色字体表明。

似乎mysql本身的缓存机制使用方面还是有很多不足之处,一般如果不是读请求较多的话,不建议开启mysql的缓存。我想mysql默认关闭缓存,可能也是因为这个考虑吧。

3、查询优化处理

这个部分其实分为三个部分,1、解析SQL,2、预处理阶段,3、查询优化器。前两者我们无法干预,都是mysql自动完成一些词法分析和语法检验的过程,通常写SQL抛出的语法错误信息,就是这个阶段的。我们需要了解的就是第三个阶段,查询优化器处理的阶段,需要明确一下mysql的查询优化器是如何对我们的SQL进行优化的。

使用等价变换

实例:如果where中有以下条件,a<b and a = 5的条件,这个条件会直接优化为如下—— 5<b and a = 5;(这个似乎和指令重排有点像)

将可转换的外连接查询转换成内连接

例如如下两条SQL语句,第一句为带有where条件的左外连接操作,第二条为不带where条件的内连接操作,但是通过explain命令发现,两个语句的执行步骤是一样的。

select * from users u LEFT OUTER JOIN user_address t on u.id=t.userID where u.id = 1;

select * from users u INNER JOIN user_address t on u.id=t.userID

利用explain可以查看SQL语句的执行计划

左外连接的执行计划:

内连接的执行计划

 可以明显看到,两者都转换为了两个简单查询。(关于explain中的各个选项,后续会一一总结)

 count、min、max函数会被优化处理

上一篇博客中,可以看出,B+树的叶子节点是天然有序的,因此对于InnoDB引擎,min,max就是一步的事儿,通过explain查看如下语句:

 optimized即为优化的意思。针对count的优化,主要是myisam引擎,这个引擎会单独存储count的值。

尽量会去覆盖索引

例如:某一张表有如下几列,(id,name,phone,age,userLevel);存在一个主键索引,存在一个联合索引index(name,phone);

如果使用select id,phone from table;这条语句,依旧会命中索引(因为我们需要查询的列均存在索引,不管是单例还是联合索引),最终mysql都会给我们使用覆盖索引。通过explain查询的结果如下,显示其命中了索引:

子查询优化 

个人觉得具体业务中不会真正用到,一个简单的实例:

 提前终止查询

limit中,如果查询的结果记录数满足了limit的条数,就不会继续进行查询。或者使用不存在的条件。例如:

in的优化 

传统的关系型数据库针对in都会转换成or条件来处理,但是在mysql中针对in是做了优化了的。

举个简单的实例,SQL:select * from users where id in (1,5,6,2,9,8),传统的关系型数据库会将其转换为select * from users where id=1 or id= 5 or id = 6 .......而mysql中会将其转换为select * from users where id in (1,2,5,6,8,9),然后再利用二分查找法进行比较,这种在大数据量的时候,优势是很明显的。

explain及其各个属性的含义

id

查询计划的展现,之前已经贴出了很多explain的图。有时候一条SQL语句会出现多个id的结果。其实这个id是用来标示执行的顺序的

存在如下SQL:

EXPLAIN 
SELECT * FROM users 
WHERE id IN 
(SELECT userID FROM user_address_copy WHERE address = '深圳南山科技园');

通过explain会得到以下结果:

 其实会很明显的看到,有些id相同,有些id不同。这些id其实决定了执行的顺序。

id相同,则执行顺序从上而下。id不同,如果是只查询,id的序号会递增,id值越大优先级越高,越先被执行。id即存在相同的,也存在不同的(即上图中的情况) ,id如果相同,则认为是一组,从上往下顺序执行,哎所有组中,id值越大,优先级越高,越先执行

select_type

用于指定查询的类型

select_type各种属性值
类型 含义
SIMPLE 简单的select查询, 查询中不包含子查询或者union
PRIMARY 查询中包含子部分, 最外层查询则被标记为primary
SUBQUERY/MATERIALIZED SUBQUERY表示在select 或 where列表中包含了子查询
MATERIALIZED表示where 后面in条件的子查询
UNION 若第二个select出现在union之后, 则被标记为union;
UNION RESULT 从union表获取结果的select

table

查询操作所涉及到的表,除了具体的表名之外,还有以下两个属性

<unionM,N> 由ID为M,N 查询union产生的结果
<subqueryN> 由ID为N查询生产的结果

type(执行计划)

SQL查询优化中一个很重要的指标,结果值从好到坏依次是:

system>const>eq_ref>ref>range>index>all

system 表只有一行记录(等于系统表) , const类型的特例, 基本不会出现, 可以忽略不计
const 表示通过索引一次就找到了, const用于比较primary key 或者 unique索引
eq_ref 唯一索引扫描, 对于每个索引键, 表中只有一条记录与之匹配。 常见于主键 或 唯一索引扫描
ref 非唯一性索引扫描, 返回匹配某个单独值的所有行, 本质是也是一种索引访问
range 只检索给定范围的行, 使用一个索引来选择行
index Full Index Scan, 索引全表扫描, 把索引从头到尾扫一遍
ALL Full Table Scan, 遍历全表以找到匹配的行

一般实际中需要达到range级别。

possible_keys,key,rows,filtered

possible_keys
查询过程中有可能用到的索引
key
实际使用的索引, 如果为NULL, 则没有使用索引
rows
根据表统计信息或者索引选用情况, 大致估算出找到所需的记录所需要读取的行数
filtered
它指返回结果的行占需要读到的行(rows列的值)的百分比,表示返回结果的行数占需读取行数的百分比, filtered的值越大越好

额外的信息

会通过extra指定下列属性和值

1、 Using filesort :mysql对数据使用一个外部的文件内容进行了排序, 而不是按照表内的索引进行排序读取
2、 Using temporary:使用临时表保存中间结果, 也就是说mysql在对查询结果排序时使用了临时表, 常见于order by 或 group by
3、 Using index:表示相应的select操作中使用了覆盖索引(Covering Index) , 避免了访问表的数据行, 效率高
4、 Using where :使用了where过滤条件
5、 select tables optimized away:基于索引优化MIN/MAX操作或者MyISAM存储引擎优化COUNT(*)操作, 不必等到执行阶段在进行计算, 查询执行计划生成的阶段即可完成优化。

4、查询执行引擎

其实这个操作就是调用下层的API功能完成SQL的执行。

5、返回客户端

只是将客户端的需要的数据返回给客户端而已。也没有什么可以优化的,只是需要知道的是,mysql是增量返回查询的结果,开始生成第一条记录的时候,就开始往客户端发送数据。

开启慢查询日志

通过show variables like 'slow_query_log',查看当前是否设置了慢查询日志,然后通过以下命令设置日志路径和相关属性即可。
set global slow_query_log = on(开启日志)
set global slow_query_log_file = '/var/lib/mysql/slow_query_log.log' (设置日志路径)
set global log_queries_not_using_indexes = on (记录没有使用索引的查询)
set global long_query_time = 0.1 (秒) (慢查询时长设置)

分析日志的简单命令:mysqldumpslow -t 10 -s at XXXXXXXX(指定日志路径名)

总结

本篇博客作为mysql的系列的第二篇,依旧总结了一些简单的查询操作,从查询执行过程中,总结了一些针对慢查询优化的操作和参数,总体来说有点零散。

参考资料:

https://www.cnblogs.com/williamjie/p/9132390.html

https://www.cnblogs.com/zhoubaojian/articles/7866292.html

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/102999459