一条SQL语句是如何执行的?
一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。
- 看一个事儿千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题
在过去的七年里,我带过十几个应届毕业生,看着他们成长,要求他们原理先行,再实践验证。
请问MySQL的基础架构图?
大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。
不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。
请说出连接器的作用?
查询缓存的作用?
连接建立后,就会执行sql语句,但是在执行前先到缓存中查看是否有缓存,缓存中是将sql和结果以key-value的形式存放的!如果命中缓存,就不需要再执行后续操作,效率高!
但是我们大部分时候不建议使用缓存,因为弊大于利,因为表中任意一条数据变化,缓存就会失效,这样缓存命中率实在太低,在mysql8.0时候,已经彻底没有缓存了。
- 分析器的作用是什么?
如果没有命中缓存,就开始真正地执行sql语句了。MySQL需要知道你要做什么,因此需要对sql语句做解析!
首先做词法分析,就是分析出select(是查询),表名和列名等。然后是语法分析,判断是否符合sql语法,如果语法不对,则收到“You have an error in your SQL syntax”错误。报错关注的是紧接“use near”的内容。
- 优化器的作用?
经过分析器,MySQL已经知道你要做什么了,在开始执行前,要先用优化器优化一下sql。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
可以选择先t1.c = 10 得到t1表的数据,然后根据ID关联到t2表;当然也可以根据t2.d = 20得到t2表的数据,然后根据id关联到t1表。
- 执行器的作用?
MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。
权限验证不仅仅在执行器这部分会做,在分析器之后,也就是知道了该语句要“干什么”之后,也会先做一次权限验证。叫做precheck。而precheck是无法对运行时涉及到的表进行权限验证的,比如使用了触发器的情况。因此在执行器这里也要做一次执行时的权限验证。
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:
1.调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
2.调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3.执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。
在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。我们后面会专门有一篇文章来讲存储引擎的内部机制,里面会有详细的说明。
(大佬这儿写的执行器和存储引擎的操作都是简化了的。自己思考的一点儿总结: 1、存储引擎层的各种数据获取方法都是已经定义好了的,是静态的方法; 2、优化器 生成的执行计划,决定了 执行器会选择 存储引擎的哪个方法去获取数据,而这也决定了一个操作是否能快速执行完成; 3、InnoDB 存储引擎层的优化措施还有好多,对执行器来说只是一个黑箱,可以再深入了解。)
- 如果表 T 中没有字段 k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?
课后答案:分析器。Oracle会在分析阶段判断语句是否正确,表是否存在,列是否存在等。猜测MySQL也这样。
02 | 日志系统:一条SQL更新语句是如何执行的?
这个表有一个主键 ID 和一个整型字段 c
如果要将 ID=2 这一行的值加 1,SQL 语句就会这么写:
mysql> update T set c=c+1 where ID=2;
- 什么是redo log?有什么作用?
对于一次更新操作,我们要将磁盘数据读出,查找到对应的数据,更新,写会磁盘,整个过程 IO 成本、查找成本都很高。那么我们可以不用每次更新都如此,可以先用一个文件记录下这条更新数据,然后将内存中的内容更新,之后在说。
InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面。
什么时候写回磁盘?当系统比较闲的时候或者是redo log写满的时候。
redo log是固定大小的。InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
- 为什么redo log比将该记录直接写在磁盘快?
原因有很多,应该,但是我想说一个,redo log也是在磁盘上的,数据也是在磁盘上,但是你操纵redo log是顺序IO,而数据很可能是随机IO。
- 说一下redo log。
redo log 是 InnoDB引擎所特有的,所以我们如果再使用InnoDB引擎创建表时,如果数据库发生异常重启,之前提交的记录都不会丢失。 InnoDB正因为有了 redo log(重做日志),才有了 crash-safe 的能力(即使mysql服务宕机,也不会丢失数据的能力)。
- binlog的源头?
前面我们讲过,MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
- 两阶段提交??