如下,是mysql基础架构示意图,从中你可以清楚的看到SQL语句在Mysql的各个功能模块中的执行过程。
连接器
mysql -hlocalhost -P3306 -uroot -p
连接器负责跟客户端建立连接,获取权限,维持和管理连接
查询缓存
分析器 语法分析
优化器 执行计划生成,索引选择
执行器 操作引擎,返回结果
为什么还有kill不掉的语句
1.kill无效的情况
1.线程没有执行到判断线程状态的逻辑
2.系统压力过大,读写IO的函数一直无法返回,导致不能及时判断线程的状态。
2.终止逻辑耗时较长
- 超大事务执行期间被kill。这时候,回滚操作需要对事务执行期间生成的所有新数据版本做回收 操作,耗时很长。
- 大查询回滚。如果查询过程中生成了比较大的临时文件,加上此时文件系统压力大,删除临时 文件可能需要等待IO资源,导致耗时较长。
- DDL命令执行到最后阶段,如果被kill,需要删除中间过程的临时文件,也可能受IO资源影响耗 时较久。
MySQL解析过程
术语介绍
mysql客户端和服务器通讯
mysql客户端和服务器之间的通讯协议是“半双工”的,这意味着,在任何一个时刻,要么由服务器向客户端发送数据,要么由客户端向服务器发送数据,这两个动作不能同时发生。这种协议让mysql通信简单快速,但也限制了mysql。一个明显的限制是,这意味着没办法进行流量限制。一旦一端开始发生消息,另一端要接收完整个消息才能响应他。
客户端用一个单独的数据包将查询传给服务器。一旦客户端发送了请求,他能做的事情就只是等待结果了。
相反的,一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整的接受整个返回结果,而不是简单的只收取前面几条结果,然后让服务器停止发送数据。
多数连接mysql的库函数都可以获得全部结果并缓存到内存里,还可以逐行获取所需要的数据。默认一般是获得全部结果并缓存到内存中。mysql通常需要等所有的数据都已经发送给客户端才能释放这条查询所占用的资源,所以接受全部结果并缓存通常可以减少服务器的压力,让查询能够早点结束、早点释放对应的资源。
查询状态
对于mysql连接,任何时刻都有一个状态,该状态表示了mysql当前正在做什么。使用show full processlist命令查看当前状态。在一个查询生命周期中,状态会变化很多次,下面是这些状态的解释:
sleep:线程正在等待客户端发送新的请求;
query:线程正在执行查询或者正在将结果发送给客户端;
locked:在mysql服务器层,该线程正在等待表锁。在存储引擎级别实现的锁,例如InnoDB的行锁,并不会体现在线程状态中。对于MyISAM来说这是一个比较典型的状态。
analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划;
copying to tmp table:线程在执行查询,并且将其结果集复制到一个临时表中,这种状态一般要么是做group by操作,要么是文件排序操作,或者union操作。如果这个状态后面还有on disk标记,那表示mysql正在将一个内存临时表放到磁盘上。
sorting Result:线程正在对结果集进行排序。
sending data:线程可能在多个状态间传送数据,或者在生成结果集,或者在想客户端返回数据。
1.连接器
你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连 接、获取权限、维持和管理连接。连接命令一般是这么写的:
mysql -h$ip -P$port -u$user -p
输完命令之后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在-p后面写在命令行 中,但这样可能会导致你的密码泄露。如果你连的是生产服务器,强烈建议你不要这么做。
连接命令中的mysql是客户端工具,用来跟服务端建立连接。在完成经典的TCP握手后,连接器就 要开始认证你的身份,这个时候用的就是你输入的用户名和密码。
如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权 限判断逻辑,都将依赖于此时读到的权限。
这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会 影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则 是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽 量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候MySQL占用内存涨得特别快,这是因为MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所 以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL异常重启了。
怎么解决这个问题呢?你可以考虑以下两种方案。
1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连 接,之后要查询再重连。
- 如果你用的是MySQL 5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证, 但是会将连接恢复到刚刚创建完时的状态。
2.查询缓存
MySQL拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语 句及其结果可能会以key-value对的形式,被直接缓存在内存中。key是查询的语句,value是查询
的结果。如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。(这个检查是通过一个对大小写敏感的哈希查找实现的。那么在返回查询结果之前mysql会检查一次用户权限。这仍然是无须解析查询SQL语句的,因为在查询缓存中已经存放了当前 查询需要访问的表信息。如果权限没有问题,mysql会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。) 如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存 中。你可以看到,如果查询命中缓存,MySQL不需要执行后面的复杂操作,就可以直接返回结果, 这个效率会很高。
但是大多数情况下我会建议你不要使用查询缓存,为什么呢?
因为查询缓存往往弊大于利。 但查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很 可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来 说,查询缓存的命中率会非常低。 除非你的业务就是有一张静态表,很长时间才会更新一次。比 如,一个系统配置表,那这张表上的查询才适合使用查询缓存。(SQL任何字符上的不同,如空格,注释,都会导致缓存不命中)
好在MySQL也提供了这种“按需使用”的方式。你可以将参数query_cache_type设置成 DEMAND,这样对于默认的SQL语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可 以用SQL_CACHE显式指定,像下面这个语句一样:
select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0版本直接将查询缓存的整块功能删掉了,也就是说8.0开始彻底没有这 个功能了。
3.分析器(解析器)
如果没有命中查询缓存,就要开始真正执行语句了。
首先,MySQL需要知道你要做什么,因此需要 对SQL语句做解析。 分析器先会做“词法分析”。
你输入的是由多个字符串和空格组成的一条SQL语句,MySQL需要识别 出里面的字符串分别是什么,代表什么。 MySQL从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别 成“表名T”,把字符串“ID”识别成“列ID”。
做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判 断你输入的这个SQL语句是否满足MySQL语法规范。
将请求的sql生存一颗语法树。如:select username from userinfo
先通过词法分析:
从左到右一个字符、一个字符地输入,然后根据构词规则识别单词。你将会生成4个Token,如下所示。"
接下来,进行语法解析,判断输入的这个 SQL 语句是否满足 MySQL 语法。然后生成下面这样一颗语法树:
如果语法不对,会报错。
根据一些mysql规则进一步检查解析树是否合法。如检查查询的表名、列名是否正确、是否有表的权限等。
4. 优化器
经过了分析器,MySQL就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。 优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的 时候,决定各个表的连接顺序。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使 用哪一个方案。 优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
当语法树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
执行计划:mysql不会生成查询字节码来执行查询,mysql生成查询的一棵指令树,然后通过存储引擎执行完成这棵指令树并返回结果。最终的执行计划包含了重构查询的全部信息。
5.执行器
MySQL通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开 始执行语句。
开始执行的时候,要先判断一下你对这个表有没有执行查询的权限,如果没有,就会返回没有权 限的错误,(在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验 证。查询也会在优化器之前调用precheck验证权限)。
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎 提供的接口。
执行器将结果返回给客户端
查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果给客户端,mysql仍然会返回这个查询的一些信息,如该查询影响到的行数。如果查询可以被缓存,那么mysql在这个阶段也会将结果放到查询缓存中。
mysql将结果集返回客户端是一个增量、逐步返回的过程。这样有两个好处:服务器端无须存储太多的结果,也就不会因为返回太多结果而消耗太多的内存;这样处理也让msyql客户端第一时间获得返回的结果。
结果集中的每一行都会以一个满足mysql客户端/服务器通信协议的包发送,再通过tcp协议进行传输,在tcp传输的过程中,可能对mysql的封包进行缓存然后批量传输。