MySQL逻辑架构及执行过程
Posted 蚂蚁小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL逻辑架构及执行过程相关的知识,希望对你有一定的参考价值。
一:MySQL逻辑架构
1:MySQL逻辑架构
客户端进程发送请求后服务器进程对接受的客户端请求做了什么处理,才能产生最后的处理结果呢?下面就看看大致的逻辑架构图
上图基本组件介绍: 连接层: Connectors【连接器】:MySQL服务之外的客户端程序请求MySQL或进行权限验证 Connection Pool【连接池】:提供了多个用于客户端与服务器端交互的线程 服务层: SQL Interface【SQL接口】:接收SQL指令,返回处理结构(其实就是执行命令的入口) Parser【SQL解析器】:SQL语法语义的解析,生成语法树 Optimizer【优化器】:核心组件,对生成的语法树进行优化,生成最优方式执行 Caches & Buffers【查询缓存】:MySQL8.0前组件,以SQL为Key,以结果为Value的方式缓存查询结果 引擎层: Pluggable Storage Engines【可插拔存式储引擎】:与底层文件系统进行交互 磁盘文件层: File System【文件系统】:磁盘,存储着MySQL数据的地方 Files & Logs【日志文件】:记录着日志信息 Ⅰ:连接层: 客户端(应用)访问MySQL服务器前,做的第一件事就是建立 TCP 连接,当客户端(应用)连接到MySQL服务器时, 服务器需要对其进行认证。认证基于用户名、原始主机信息和密码。经过三次握手建立连接成功后,MySQL服务器对TCP 传输过来的账号密码做身份认证、权限获取。 ①:当用户名或密码不对,会收到一个Access denied for user错误,客户端程序结束执行 ②:用户名密码认证通过,会从权限表查出账号拥有的权限与连接关联,之后的权限判断逻辑,都将依赖于此时读到的权限 注:TCP连接收到请求后,必须要分配给一个线程专门与这个客户端的交互。所以还会有个线程池,去走后面的流程。 每一个连接从线程池中获取线程,断开连接则归还线程池,这样省去了创建和销毁线程的开销。 Ⅱ:服务层: ①:SQL Interface: SQL接口 接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT ... FROM就是调用SQL Interface MySQL支持DML(数据操作语言)、DDL(数据定义语言)、存储过程、视图、触发器、自定义函数等多种SQL语言接口 注:SQL接口就像我们Java的接口一样,如DML、DDL都存在抽象接口,调用后再由接口的具体实现来处理此次操作。 ②:Parser: 解析器 ▶ 在解析器中对 SQL 语句进行语法语义分析。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和 处理就是基于这个结构的。如果在分解语句中遇到错误,那么就说明这个SQL语句是不合理的(如语法错误)。 ▶ 其实解析器先会做“词法解析”。解析我们输入的多个字符串和空格组成的一条SQL语句,MySQL需要识别出里面的字符串分别是 什么,代表什么。最终会被解析器验证和解析,并为其创建语法树,并根据数据字典丰富查询语法树,会验证该客户端是否具 有执行该查询的权限。创建好语法树后,MySQL优化器还会对SQL查询进行语法上的优化,进行查询重写。 ③:Optimizer: 查询优化器 ▶ 执行完解析器后,MySQL就知道要干什么了。但接下来并不是直接执行解析好的语法树,而是根据语法树生成一个具体执行计划。 ▶ 优化器拿到生成的语法树后会在优化器这一层进行优化,优化器是个非常复杂的部件,它会帮我去使用它自己认为的最好的方式 去优化这条SQL语句,执行计划会指明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连接顺序如何, 并生成一条条的执行计划。最后按照执行计划中的步骤使用执行器调用存储引擎提供的方法来真正的执行操作, 并将查询结果返回给用户。 例如:在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。 比如你执行下面这样的语句,这个语句是执行两个表的join: SELECT * FROM t1 JOIN t2 USING(ID) WHERE t1.c=10 AND t2.d=20; 上述SQL语句既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20。 也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到t1,再判断t1里面c的值是否等于10。 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。 ④:Caches & Buffers: 查询缓存组件 ▶ MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结果,在解析查询之前,服务器会 先检查查询缓存,如果能够在其中找到对应的查询,服务器就不必再执行查询解析、优化和执行的整个过程,而是直接返回查 询缓存中的结果集后通过SQL接口将结果反馈给客户端。 ▶ 其实不推荐使用查询缓存,因为查询缓存往往弊大于利。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的 查询缓存都会被清空。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长 时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。 ▶ 其实MySQL也提供了这种“按需使用”的方式。可以将参数query_cache_type设置成DEMAND,这样对于默认的SQL语句都不使用 查询缓存。而对于确定要使用查询缓存的语句,可以用SQL_CACHE显式指定,(后面介绍缓存)如下: SELECT SQL_CACHE * FROM T WHERE ID=10; ▶ 从MySQL 5.7.20开始,不推荐使用查询缓存,并在 MySQL 8.0中删除。 ⑤:执行器(在查询优化器之后) MySQL通过解析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,执行器会根据一系列的执行计划去 调用存储引擎的接口去完成SQL的执行。开始执行的时候,要先判断一下当前账户对这个表有没有执行的权限,如果没有,就会返 回没有权限的错误, 如下所示(如果命中查询缓存,会在查询缓存放回结果的时候,做权限验证。查询也会在优化器之前调用precheck验证权限)。 select * from T where ID=10; 如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。 比如我们这个例子中的表T中,ID字段没有索引,那么执行器的执行流程是这样的: 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中; 然后再调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。执行器将上述遍历过程中所有 满足条件的行组成的记录集作为结果集返回给客户端。至此,这个语句就执行完成了。 Ⅲ:引擎层: 插件式存储引擎层(Storage Engines),真正的负责了MySQL中数据的存储和提取,对物理服务器级别维护的底层数据执行操作, 服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。 MySQL8.0.31默认支持的存储引擎如下: SHOW engines; +--------------------+---------+---------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints | +--------------------+---------+---------------------+--------------+------+------------+ | ndbcluster | NO | Clustered, fault... | NULL | NULL | NULL | | FEDERATED | NO | Federated MySQL ... | NULL | NULL | NULL | | MEMORY | YES | Hash based, stor... | NO | NO | NO | | InnoDB | DEFAULT | Supports transac... | YES | YES | YES | | PERFORMANCE_SCHEMA | YES | Performance Sche... | NO | NO | NO | | MyISAM | YES | MyISAM storage e... | NO | NO | NO | | ndbinfo | NO | MySQL Cluster sy... | NULL | NULL | NULL | | MRG_MYISAM | YES | Collection of id... | NO | NO | NO | | BLACKHOLE | YES | /dev/null storag... | NO | NO | NO | | CSV | YES | CSV storage engi... | NO | NO | NO | | ARCHIVE | YES | Archive storage ... | NO | NO | NO | +--------------------+---------+---------------------+--------------+------+------------+ 注:MySQL的存储引擎设计这么多是因为它可以随场景变化的,它可以是MYISAM、InnoDB、Memory等,所以它被设计成了插入式。 Ⅳ:存储层: 所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在 文件系统 上,以 文件 的方式存在的,并完成与存储引擎的 交互。当然有些存储引擎比如InnoDB,也支持不使用文件系统直接管理裸设备,但现代文件系统的实现使得这样做没有必要了。 在文件系统之下,可以使用本地磁盘,可以使用DAS、NAS、SAN等各种存储系统
2:MySQL逻辑架构总结
MySQL逻辑架构大概可以分为三层:
连接层:客户端服务并不是MySQL所独有的,大多数基于网络的客户端/服务器的工具或者服务都有类似的架构。客户端发送请求后就由连接层
进行客户端去请求做处理,比如连接处理、授权认证、安全等等。
Server层:大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有的内置函数(例如,日期、时间、数学和加
密函数),所有跨存储引擎的功能都在这一层实现:存储过程、触发器、视图等。
存储引擎层:第三层包含了存储引擎。存储引擎负责MySQL中数据的存储和提取。Server层通过API与存储引擎进行通信。这些接口屏蔽了不
同存储引擎之间的差异,使得这些差异对上层的查询过程透明。
值得一提的是在MySQL8.0中取消了查询缓存,大概的理由是查询缓存存在严重的可伸缩性问题,并且很容易成为严重的瓶颈缓存,将缓存移动到
客户端能收获更好的性能(后面具体说明)。
二:MySQL执行流程
1:MySQL中的执行流程
要搞清楚MySQL的执行流程之前,我们需要先搞明白几个重要的组件,一共六个组件,其实上一节有说明,具体分为:
连接器:连接MySQL服务器,进行权限验证
缓存:保存上次查询的结果,提高性能
解析器:词法与语法分析
优化器:对你的查询语句做出适当的优化
执行器:操作存储引擎,读写数据
存储引擎:存储数据
MySQL的一个查询操作的完整流程:
客户端通过连接器建立TCP连接,这个操作会先分配一个线程进行权限验证,通过验证后调用对应的SQL接口,SQL接口实现会先去查询
缓存,根据SQL作为Key去查询,查询到则直接返回结果,否则调用解析器,解析器对SQL语句的语法语义解析,得到一个MySQL可以理
解的语法,随后进入优化器,MySQL会根据你的查询条件适当的优化,之后经过执行器,执行器就开始真正的调用对应的存储引擎查询
数据,最后将查询到的数据通过SQL接口返回给客户端,并且还会把查询结果写入缓存
Ⅰ:连接器:建立连接 Ⅱ:查询缓存:Server如果在查询缓存中发现了这条 SQL 语句,就会直接将结果返回给客户端;如果没有,就进入到解析器阶段。 需要说明的是,因为查询缓存往往效率不高,所以在 MySQL8.0 之后就抛弃了这个功能。 注:大多数情况查询缓存就是个鸡肋,为什么呢? SELECT employee_id,last_name FROM employees WHERE employee_id = 101; 查询缓存是提前把查询结果缓存起来,这样下次不需要执行就可以直接拿到结果。需要说明的是,在MySQL 中的查询缓存, 不是缓存查询计划,而是查询对应的结果。这就意味着查询匹配的 鲁棒性大大降低 ,只有 相同的查询操作才会命中查询缓存。 两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。因此MySQL的查询缓存命中率不高。 同时,如果查询请求中包含某些系统函数、用户自定义变量和函数、一些系统表,如mysql、information_schema、 performance_schema 数据库中的表,那这个请求就不会被缓存。以某些系统函数举例,可能同样的函数的两次调用会产生不一 样的结果,比如函数 NOW ,每次调用都会产生最新的当前时间,如果在一个查询请求中调用了这个函数,那即使查询请求的文本 信息都一样,那不同时间的两次查询也应该得到不同的结果,如果在第一次查询时就缓存了,那第二次查询的时候直接使用第一次查询 的结果就是错误的! 此外,既然是缓存,那就有它 缓存失效的时候 。MySQL的缓存系统会监测涉及到的每张表,只要该表的结构或者数据被修改, 如对该表使用了INSERT 、 UPDATE 、 DELETE、 TRUNCATE TABLE 、 ALTER速缓存中删除!对于更新压力大的数据库来说, 查询缓存的命中率会非常低。 Ⅲ:解析器:在解析器中对 SQL 语句进行语法分析、语义分析。
分析器先做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL需要识别出里面的字符串分别是什么, 代表什么。MySQL从你输入的"select"这个关键字识别出来,这是一个查询语分析器先做“词法分析”。你输入的是由多个字符串和 空格组成的一条 SQL 语句,MySQL需要识别出里面的字符串分别是什么,代表什么。MySQL从你输入的"select"这个关键字 识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名T”,把字符串“ID”识别成“列 ID”。句。接着,要做“语法分析”。 根据词法分析的结果,语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否 满足 MySQL 语法 。 select department_id,job_id,avg(salary) from employees group by department_id; 如果SQL语句正确,则会生成一个这样的语法树:
Ⅳ:优化器:在优化器中会确定 SQL 语句的执行路径,比如是根据 全表检索 ,还是根据 索引检索 等。 举例:如下语句是执行两个表的 join: select * from test1 join test2 using(ID) where test1.name=\'zhangwei\' and test2.name=\'mysql高级课程\' 方案1:可以先从表 test1 里面取出 name=\'zhangwei\'的记录的ID值,再根据ID值关联到表test2,再判断test2里 面name的值是否等于 \'mysql高级课程\'。 方案2:可以先从表 test2 里面取出 name=\'mysql高级课程\'的记录的ID值,再根据ID值关联到test1再判断test1里 面name的值是否等于zhangwei。 这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化 器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段(后面讲到索引我们再谈)。 在查询优化器中,可以分为 逻辑查询 优化阶段和 物理查询 优化阶段。 Ⅴ:执行器 截止到现在,还没有真正去读写真实的表,仅仅只是产出了一个执行计划。于是就进入了执行器阶段。
在执行之前需要判断该用户是否具备权限。如果没有,就会返回权限错误。如果具备权限,就执行SQL查询并返回结果。 在MySQL8.0以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。 select * from test where id=1; 比如:表test中,ID字段没有索引,那么执行器的执行流程是这样的: 调用 InnoDB 引擎接口取这个表的第一行,判断ID值是不是1,如果不是则跳过,如果是则将这行存在结果集中;调用引擎 接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。执行器将上述遍历过程中所有满足条件的行组成的记录 集作为结果集返回给客户端。 至此,这个语句就执行完成了。对于有索引的表,执行的逻辑也差不多。SQL语句在MySQL中的流程是: SQL语句→查询缓存→解析器→优化器→执行器。
.
Mysql 执行一条语句的过程
Mysql的逻辑架构
Mysql的逻辑架构如下所示,整体分为两部分,Server层和存储引擎层。
与存储引擎无关的操作都是在Server层完成的,存储引擎层负责数据的存取。
下面将会按照上图的过程分别介绍每一步的作用,这里以查询一条记录为例。
连接器
这一步主要是管理连接和权限验证。
负责管理客户端的连接,比如mysql -u root -p
,就是客户端与连接器之间完成的,连接分为长连接和短连接,建议使用长连接,因为建立连接是相对复杂的一个过程。但是长连接也有优化的空间,即长连接过多,随着执行大的查询操作,会占用较多的内存。
建立连接完成之后,连接器会判断该用户的权限,之后用户的操作都会基于权限来判定是否允许。
分析器
这一步主要是词法分析和语法分析。
词法分析主要是判定用户想做什么,比如select 就是想要查询。
语法分析主要是判定用户输入的SQL是否符合Mysql的语法。
优化器
经过分析器,Mysql已经知道用户想要干什么了,但是对于相同的一条SQL语句,对于Mysql具体实施的时候,可能有多种方法去实现,效率也就不一样。
在优化器这一步,mysql需要判定怎么样执行才是最优的效率。
执行器
这一步主要是操作引擎和返回结果。通过操作存储引擎层来遍历一张数据表,去寻找符合条件的数据,并且返回给客户端。
Mysql执行一条更新语句的过程
与一条SQL查询语句相同的是,在Mysql中同样要经过连接器、分析器、优化器、执行器,也要用到存储引擎来进行数据的存取。
不同的是,更新语句需要涉及到两个重要的日志模块,redo log
和binlog
redo log
一家饭店生意红火,但作为一家饭店来说,免不了每天都有赊账和来还账的。
如果赊账和还账的人不多还好,老板可以直接用一块粉板来记录,如果人多,粉板根本写不下一个月的赊账记录。
所以老板想到可以把所有的赊账记录写到账本上,而粉板上写的都是短时间的,等下班了之后,拿粉板上的和账本进行对账。
在这个例子中,粉板就是redo log,账本就是mysql中的记录,我们用还账类比一下mysql的更新过程,如果每次有人更新,我们都去mysql中找到这条记录,效率很低,所以mysql的思路和这个老板一致,更新操作先放到redo log中,过一段时间再慢慢消化。
这种思路叫WAL技术,即Write Ahead Logging技术,先写日志,再写磁盘。
需要注意的是,粉板如果满了,老板还没下班,老板就必须停下来手中的活。mysql中的redo log总共可以记录4GB的操作
当write pos 追上check point,mysql这个老板就要去处理一下redo log了。
除此之外,有了redo log持久化,数据库即使异常重启也不会丢日志,这是crash safe
机制,但仍需要注意的是,redo log是innodb存储引擎特有的。
bin log
binlog是Server层的日志,可以适用于所有存储引擎。
那既然有binlog了,前文为什么要搞一个innodb存储引擎专属的redo log呢?
因为mysql最开始没有innodb引擎,而是myisam引擎,使用的是binlog,但是binlog仅限于归档,没有crash safe
机制,于是自己补充了一个redo log。
- redo log是innodb存储引擎特有的,而binlog是server层特有的
- redo log存储的是物理日志,binlog是逻辑日志
- redo log像上面讲的那样,支持4GB大小,多了就得处理并且去覆盖,binlog日志当写满一个日志文件之后,会新建一个新的日志文件。
下面以更新ID为2的一行数据为例:
图中浅色框表示是在 InnoDB 内部执行的,深色框表示是在执行器中执行的。
- 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
参考
极客时间 丁奇
以上是关于MySQL逻辑架构及执行过程的主要内容,如果未能解决你的问题,请参考以下文章