当我们执行一条MySQL查询语句时,对于客户端而言是一个很简单的过程,但对于服务端来说其内部却会涉及到一些复杂的组件和处理逻辑。了解MySQL语句的内部执行原理,有助于我们更好地去处理一些复杂的SQL语句,帮助我们定位和解决问题。

MySQL体系结构


Connector:用来支持各种语言和SQL的交互,比如PHP、Python、Java的JDBC等;
Management Serveices & Utilities:系统管理和控制工具,包括备份恢复、MySQL复制、集群等;
Connection Pool:连接池,管理需要缓冲的资源,包括用户密码、权限、线程等;
SQL Interface:用来接收用户的 SQL 命令,返回用户需要的查询结果;
Parser:用来解析SQL语句;
Optimizer:查询优化器;
Cache and Buffer:查询缓存,除了行记录的缓存之外,还有表缓存、Key 缓存、权限缓存等;
Pluggable Storage Engines:插件式存储引擎,它提供API给服务层使用,跟具体的文件打交道。

在上述结构的基础上,我们可以大致将MySQL分为三层,分别是连接层、服务层以及存储引擎层。

连接层:
负责管理客户端与MySQL服务器的所有连接,包含验证客户端的身份和权限。
服务层:
实现了MySQL的大多数核心服务功能,查询解析、分析、优化、缓存以及其他所有的内置函数、所有的跨存储引擎的功能都在这一层实现(比如存储过程、触发器、视图等)。
当连接层将SQL语句转交给服务层时,服务层会做出进一步的处理,对我们的SQL语句进行词法分析和语法分析(比如关键字识别、别名识别、语法检查等),然后就是优化器根据一定的规则对我们的SQL语句进行优化,最后再交给执行器去执行。
存储引擎层:
数据真正存放的地方,再往下就是内存或者磁盘。
MySQL支持不同的存储引擎(包含MYISAM、INNODB、MEMORY等),负责数据的存储和提取。现在最常用的存储引擎是INNODB,它从MySQL 5.5.5版本开始就成为了默认的存储引擎。也就是说,当我们执行create table语句创建数据表时,如果没有显示使用"engine=memory"来指定什么类型的存储引擎,那么MySQL默认为我们使用的就是INNODB。
不同存储引擎的表数据存取方式各有不同,支持的功能也不相同,但它们都共用一个Server层。Server层通过存储引擎API来与它们进行交互,这些接口屏蔽了不同存储引擎之间的差异,使得这些差异对于上层的查询而言尽可能地透明。这些API包含几十个底层函数,用于执行诸如“开始一个事务”或者“根据主键提取一行记录”等操作。
存储引擎不能解析SQL,互相之间也不能通信,只是单纯地响应上层服务器的请求罢了。

SQL语句的执行过程

step1:使用连接器与客户端建立连接

执行SQL语句操作数据库的前提是要先建立连接,连接器在这里负责与客户端建立连接、获取权限、维持和管理连接。
在完成了经典的TCP握手之后,连接器就要开始认证你的身份了,这时候用的就是你输入的用户名和密码:

  1. 如果用户名或密码不正确,MySQL会报"Access denied for user"的错误,然后客户端程序结束执行;
  2. 如果用户名密码都认证通过,连接器就会到权限表里去查询你所拥有的权限。之后在这个连接里用到的权限判断逻辑都是基于此时读到的权限。也就是说,即使你用管理员账号对该用户的权限作出了修改,也不会影响到已经存在连接的权限,只会影响到后续新建的连接。

连接完成后,如果你没有后续的操作,那么这个连接就会处于空闲状态,而当客户端如果太长时间没有操作,连接器就会自动将它断开,这个时间由参数"wait_timeout"来控制,默认值是8小时,我们也可以用SQL语句来查看默认时间"show global variables like ‘wait_timeout’;"。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提示"Lost connection to MySQL server during query",这时候就需要重连再执行请求了。
在数据库中有两种连接:

  1. 短连接:操作完毕后马上close掉,下次查询再重新建立一个;
  2. 长连接:保持打开,减少服务端创建和释放连接的消耗,后面的程序访问的时候还可以使用这个连接。

由于建立连接的过程通常是比较复杂的,首先要发送请求,然后要去验证账号密码,验证通过后还得去查看你所拥有的权限,因此建议在使用中尽量要减少连接建立的动作,也就是尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候MySQL的占用内存涨得特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放,所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MySQL异常重启了。
如何解决这个问题呢,可以从以下方面考虑:

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连;
  2. 如果你用的是MySQL 5.7或更新的版本,可以在每次执行一个比较大的操作后,通过执行"mysql_reset_connection"来重新初始化连接资源,这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

step2:查询缓存,有则直接返回

连接建立完成之后,你就可以开始执行SQL语句了,这里便会进入第二步:查询缓存
MySQL内部自带了一个缓存模块,MySQL在拿到一个查询请求后,首先会先到查询缓存中看看之前是不是执行过这条语句,之前执行过的语句及其结果可能会以"key-value"键值对的形式被直接缓存在内存中(key是查询的语句,value是查询的结果)。如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL甚至不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
我们可以将参数"query_cache_type"设置成DEMAND,这样对于默认的SQL语句就都不会使用查询缓存,而对于你确定要使用查询缓存的语句,可以用"SQL_CACHE"显示指定,比如"select SQL_CACHE * from table where id = 1;"。
但是大多数的情况下并不建议怎么做,为什么呢?因为查询缓存往往弊大于利。首先查询缓存的失效非常频繁,只要有对一个表的更新,那么这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用就被另一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低,除非你的业务就是有一张静态表,很长时间才会更新一次,比如一个系统配置表,那这张表上的查询才适合使用查询缓存。其次,查询缓存要求SQL语句必须是一模一样,中间多一个空格、字母大小写不同都会被认为是不同的SQL。所以缓存还是交给ORM框架(比如MyBatis默认是开启了一级缓存)或者独立的缓存服务(比如Redis)来处理更加合适。
注意:在MySQL 8.0版本中,查询缓存的整个功能已经被移除了!

step3:语法解析和预处理

这一步要做的事情就是MySQL的解析器和预处理模块对语句进行基于SQL语法的词法、语法分析和语义的解析
解析器如果没有命中查询缓存,就要开始真正执行语句了。MySQL首先需要知道你要做什么,因此需要对SQL语句做解析。
解析器先会做“词法分析”,把一个完整的SQL语句打碎成一个个的单词。假设你输入的是由多个字符串和空格组成的一条SQL语句,对于MySQL而言,它需要识别出里面的字符串分别是什么、代表什么。MySQL从你输入的"select"这个关键字识别出来这是一个查询语句,同时它也要把字符串"T"识别成“表名T”,把字符串"ID"识别成“主键id”。
做完上述这些识别分析以后,就要开始做“语法分析”,根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法(比如单引号有没有闭合等),然后就会根据SQL语句生成一个数据结构,这个数据结构我们一般称之为解析树(select_lex)。如果你的语句不对,就会收到"You have an error in your SQL syntax"的错误提示。
完成了上述两步的分析识别后,解析环节中的预处理器会检查生成的解析树,解决解析器无法解析的语义,比如它会检查表名或者字段是否存在、检查名字和别名是否有歧义等,预处理过后会得到一个新的解析树。

step4:查询优化器确定SQL语句的执行方案

经过了解析环节后,MySQL就知道你要做什么了,但在真正开始执行SQL前,我们还需要先经过优化器的处理。
一条SQL语句可以有很多种执行方式,它们最终返回的结果是相同等价的。但是如果有这么多种执行方式,这些执行方式怎么得到的?最终选择哪一种去执行?根据什么样的判断标准去选择?这个就是MySQL的查询优化器的模块(Optimizer)需要做的事了。
查询优化器的目的就是根据解析树生成不同的执行计划(Execution Plan),然后选择一种最优的执行计划,MySQL里面使用的是基于开销(cost)的优化器,即哪种执行计划开销最小就用哪种。我们可以使用命令"show status like ‘Last_query_cost’;“来查看查询的开销。
对于每一种数据库来说,优化器的模块都是必不可少的,它们通过复杂的算法实现尽可能地优化查询效率的目标。优化器在数据表中存在多个索引的时候,决定使用哪个索引,或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。但优化器也不是万能的,并不是再垃圾的SQL语句都能自动优化,也不是每次都能选择到最优的执行计划,所以大家在编写SQL语句的时候还是要注意。
优化器最终会把解析树变成一个执行计划(execution_plans),执行计划是一个数据结构。当然这个执行计划不一定是最优的执行计划,因为MySQL也有可能覆盖不到所有的执行计划。那么我们怎么查看MySQL的执行计划呢?比如多张表关联查询,先查询哪张表?在执行查询的时候可能用到哪些索引,实际上用到了什么索引?这里MySQL为我们提供了一个执行计划的工具,我们可以在SQL语句前加上EXPLAIN,就能看到执行计划的信息了,比如"EXPLAIN select * from table;”。
在优化器阶段完成后,这个语句的执行方案就确定下来了。

step5:执行器执行SQL语句

MySQL通过解析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句
开始执行的时候,要先判断你对这个表有没有执行查询的权限:

  1. 如果没有,就会返回没有权限的错误。在工程实现上,如果命中了查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用precheck验证权限。
  2. 如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的存储引擎定义,去使用这个存储引擎所提供的API接口。

在数据库的慢查询日志中我们可以看到一个"rows_examined"的字段,表示这个语句在执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。但在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟"rows_examined"并不是完全相同的。
比如执行SQL语句"select * from table where age = 20;",age字段没有索引时,执行器的执行流程是这样的:

  1. 调用存储引擎API接口取这个数据表的第一行,判断age值是不是等于20,如果不是则跳过,如果是则将这行存在结果集里;
  2. 调用存储引擎API接口取下一行,重复进行相同的逻辑判断,直到取到这个数据表的最后一行;
  3. 执行器将上述遍历得到的符合条件的行组成的结果集返回给客户端。

而对于有索引的情况,执行的逻辑也大同小异:

  1. 第一次调用的是“取符合条件的第一行”这个API接口;
  2. 之后再循环调用这个API接口,这些接口在存储引擎中都是事先定义好的。

tip1:存储引擎

在关系型数据库里面,数据是放在数据表里面的,我们可以把这个表理解成Excel电子表格的形式。所以我们的表在存储数据的同时还要组织数据的存储结构,这个存储结构就是由我们的存储引擎所决定的,所以我们也可以把存储引擎叫做表类型。
在MySQL里支持多种存储引擎,他们是可以替换的,所以也叫做插件式的存储引擎。那为什么要支持这么多存储引擎呢?一种还不够用吗?
在MySQL里每一张数据表都可以指定它的存储引擎,而不是一个数据库只能使用一个存储引擎。存储引擎的使用是以数据表为单位的,而且即使在创建完表之后还可以修改它的存储引擎。
如何选择存储引擎:

  1. 如果对数据一致性要求比较高,需要事务支持,可以选择INNODB;
  2. 如果数据查询多更新少,对查询性能要求比较高,可以选择MyISAM;
  3. 如果需要一个用于查询的临时表,可以选择MEMORY;
  4. 如果所有的存储引擎都不能满足你的需求,并且你的技术能力足够强,那么你还可以根据官网内部手册用 C语言开发一个存储引擎。(https://dev.mysql.com/doc/internals/en/custom-engine.html)

tip2:EXPALIN使用

EXPLAIN模拟优化器执行SQL语句,从而知道MySQL是如何处理SQL语句的,分析查询语句或者表结构的性能瓶颈。
作用:

  1. 表的读取顺序;
  2. 数据读取操作的操作类型;
  3. 哪些索引可以使用;
  4. 哪些索引被实际使用;
  5. 表之间的引用;
  6. 每张表有多少行被优化器查询等。

EXPLAIN + SQL语句得到的信息有10列,分别是:

  1. id:选择标识符,查询的序号,包含一组数字,表示查询中执行select子句或操作表的顺序。
    id相同,执行顺序从上往下。
    id不同,id值越大,优先级越高,越先执行
  2. select_type:表示查询的类型,主要用于区别普通查询、联合查询、子查询等的复杂查询。
    simple,简单的select查询,查询中不包含子查询或者UNION。
    primary,查询中若包含任何复杂的子部分,最外层查询被标记。
    subquery,在select或where列表中包含了子查询。
    derived,在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放到临时表中。
    union,如果第二个select出现在UNION之后,则被标记为UNION,如果union包含在from子句的子查询中,外层select被标记为derived。
    union result,UNION 的结果。
  3. table:输出的行所引用的表。
  4. type:表示表的连接类型,按照从最佳到最坏类型排序,一般保证查询至少达到"range"级别,最好能达到"ref"。
    system,表中仅有一行(等于系统表),这是const联结类型的一个特例。
    const,表示通过索引一次就找到,const用于比较"primary key"或者"unique"索引。因为只匹配一行数据,所以如果将主键置于"where"列表中,MySQL能将该查询转换为一个常量。
    eq_ref,唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于唯一索引或者主键扫描。
    ref,非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,可能会找多个符合条件的行,属于查找和扫描的混合体。
    range,只检索给定范围的行,使用一个索引来选择行。“key"列显示使用了哪个索引,一般就是"where"语句中出现了"between”、"in"等范围的查询。这种范围扫描索引扫描比全表扫描要好,因为它开始于索引的某一个点,而结束另一个点,不用全表扫描。
    index,index 与all区别为index类型只遍历索引树。通常比all快,因为索引文件比数据文件小很多。
    all,遍历全表以找到匹配的行。
  5. possible_keys:表示查询时可能使用的索引。
  6. key:表示实际使用的索引,如果没有选择索引,键是NULL,查询中如果使用覆盖索引,则该索引和查询的"select"字段重叠。
  7. key_len:索引字段的长度,该列计算查询中使用的索引的长度,在不损失精度的情况下,长度越短越好。如果键是NULL,则长度为NULL。该字段显示为索引字段的最大可能长度,并非实际使用长度。
  8. ref:列与索引的比较,显示索引的哪一列被使用了,有可能是一个常数,哪些列或常量被用于查询索引列上的值。
  9. rows:扫描出的行数(估算的行数),根据表统计信息以及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
  10. Extra:执行情况的描述和说明,包含不适合在其他列中显示,但是十分重要的额外信息。
    Using filesort,说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成排序操作称为“文件排序”。
    Using temporary,使用了临时表保存中间结果,MySQL在查询结果排序时使用临时表。常见于排序"order by"和分组查询"group by"。
    Using index,表示相应的select操作使用覆盖索引,避免访问了表的数据行。如果同时出现"using where",表名索引被用来执行索引键值的查找,如果没有同时出现"using where",表名索引则用来读取数据而非执行查询动作。
    Using where,表明使用"where"过滤。
    using join buffer,使用了连接缓存。
    impossible where,"where"子句的值总是false,不能用来获取任何元组。
    select tables optimized away,在没有"group by"子句的情况下,基于索引优化MIN、MAX操作或者对于MYISAM存储引擎优化count(*),不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
    distinct,优化"distinct"操作,在找到第一匹配的元组后即停止找同样值的动作。
    extended关键字:仅对select语句有效,在EXPLAIN后使用extended关键字,可以显示filtered列(按表条件过滤的行百分比)。

汇总:

  1. EXPLAIN不会告诉你关于触发器、存储过程的信息或用户自定义函数对查询的影响情况;
  2. EXPLAIN不考虑各种Cache;
  3. EXPLAIN不能显示MySQL在执行查询时所作的优化工作;
  4. 部分统计信息是估算的,并非精确值;
  5. EXPLAIN只能解释SELECT操作,其他操作要重写为SELECT后查看执行计划。

总结

  1. MySQL的逻辑架构大致可划分为三部分:连接层、server层和存储引擎层;
  2. SQL查询语句的执行依赖于核心组件:先通过连接器与客户端进行连接,随后查询是否可以应用缓存,可以就直接返回结果,不可以则使用解析器分析SQL,然后利用优化器确定执行方案,最终利用执行器与存储引擎执行SQL获取结果。

一条mysql查询语句的执行过程相关推荐

  1. 一条SQL查询语句的执行过程,一张图说清SQL查询语句执行过程

    一张图说明 一条SQL查询语句的执行过程 一条sql语句从发送到数据库到它执行完成并返回结果,主要经历以下几个过程: 连接器->查询缓存(如果开启了查询缓存,则会经过这一步,但是大多数情况下都是 ...

  2. 讲mysql执行流程书籍_MySQL 基础架构 1. 一条SQL查询语句的执行过程(个人学习笔记)...

    MySQL的逻辑架构图: MySQL 大体分为 "server 层" 和 "存储引擎层" 两部分: Server 层 包括 连接器.查询缓存.分析器.优化器.执 ...

  3. 一文读懂MySQL查询语句的执行过程

    需要从数据库检索某些符合要求的数据,我们很容易写出 Select A B C FROM T WHERE ID = XX  这样的SQL,那么当我们向数据库发送这样一个请求时,数据库到底做了什么? 我们 ...

  4. MySQL - MySQL查询语句的执行过程

    需要从数据库检索某些符合要求的数据,我们很容易写出 Select A B C FROM T WHERE ID = XX 这样的SQL,那么当我们向数据库发送这样一个请求时,数据库到底做了什么? 我们今 ...

  5. Oracle/mysql查询语句的执行过程

    执行顺序 from on join/pivot/unpivot(mysql没有pivot和unpivot) where group by having select distinct order by ...

  6. for语句的执行过程_深入学习MySQL 01 一条查询语句的执行过程

    在学习SpringCloud的同时,也在深入学习MySq中,听着,,看着<高性能MySQL>,本系列文章是本人学习过程的总结,水平有限,仅供参考,若有不对之处或有啥建议都可与我联系,感谢! ...

  7. mysql 处理一条语句卡死_一条MySQL查询语句,卡死机器,不知道为什么,求高手指点!...

    你的位置: 问答吧 -> MySQL -> 问题详情 一条MySQL查询语句,卡死机器,不知道为什么,求高手指点! 我的这条查询语句有什么问题吗?为什么一运行,机器就卡死了!N久查询不出结 ...

  8. left join 最后一条_一条Mysql查询语句的西天取经之路,你真的了解吗?

    数据库,大家都不陌生,这是程序员的基本技能了.当然,我们更多时候只是去了解如何使用数据库,而对数据库一些底层原理却比较陌生,今天我们来了解一下,一条数据库查询语句的取经之路. 基本分层 个人认为,My ...

  9. MySQL查询语句关键字执行的优先级问题

    系列文章目录 第一章 MySQL概述 第二章 MySQL的常用命令 第三章 MySQL中的常用数据类型 第四章 MySQL单行处理函数 第五章 MySQL多行处理函数 前言 前五章我们学习了查询语句的 ...

最新文章

  1. 2017届蓝桥杯java_2017第八届蓝桥杯JavaC组决赛(国赛)试题汇总及试题详解-Go语言中文社区...
  2. java代码示例(6-3)
  3. 20145319 第五周学习总结
  4. 31、SAM文件中flag含义解释工具--转载
  5. PXC集群常见错误(一)
  6. Windows via C/C++ 学习(8)CreateProcess 函数
  7. 针对不同pandas版本进行列名的修改
  8. Java 读写txt文件 中文乱码问题
  9. 代码健壮性的获得 —— 借助编译器及语言的语法特性
  10. python自学网站-自学Python网站推荐 从入门到精通
  11. 拓端tecdat|R语言ARMA GARCH COPULA模型拟合股票收益率时间序列和模拟可视化
  12. 向datagrid中加横向 纵向的合计 (在datatable中实现,datatable间倒数据)
  13. visio2010安装
  14. Skype 去广告安装方法
  15. DB2 SQLCODE 异常大全编辑(四)
  16. 三菱伺服驱动器MR-J2S 70A伺服驱动器电源驱动板图纸
  17. varchar2 汉字长度问题
  18. (一)初识Echarts之柱状图
  19. 计算机光盘无法格式化,怎么格式化光盘啊??求解!!
  20. arctanx麦克劳林公式推导过程_【数学】「专题」初识泰勒级数(Taylor Series)与泰勒公式(Taylor#x27;s Formula)...

热门文章

  1. factorio蓝图代码_任何复杂系统都是由简单构建起来的——Factorio 和软件工程
  2. 满月——有技巧的暴力
  3. 知乎专栏-水面的满月
  4. 下列字符是c语言的保留字是,下列字符序列中,是C语言保留字的是().
  5. 挖掘城市ip_用文化创意挖掘城市文脉,“哈舅”助力打造城市文化IP
  6. python建立窗口并美化_【python项目实战】BBS论坛(4)尝试页面美化
  7. vivoy73s和oppoa11哪个好 vivoy73s和oppoa11哪个更值得入手
  8. 服务器多网卡配置文件,服务器中网卡配置文件
  9. 常用的Map遍历方式
  10. CSGO配置很高但是FPS很低怎么办?