1.Mysql基础架构:一条SQL查询语句是如何查询的?
1.1 Mysql的基础架构介绍
Mysql的基础架构可以从下图来进行展示
大体来说,Mysql可以分为Server层
和存储引擎层
两部分
Server层包括:链接器
、查询缓存
、分析器
、优化器
、执行器
等,其涵盖了Mysql的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的的功能都在这一层实现,比如存储过程
、触发器
、视图
等
而存储引擎层负责的是数据的存储
和提取
。其架构模式是插件式
的,支持InnoDB
、MyISAM
、Memory
等多个存储引擎,现在最常用的存储引擎是InnoDB
,它从MYSQL
5.5.5版本开始称为了默认存储引擎。
也就是说,执行create table
建表的时候,如果不指定引擎的类型,默认使用的就是InnoDB
,同时也可以通过指定存储引擎的类型来选择别的引擎create table user engine = memory
来指定使用内存引擎来创建表。
不同的存储引擎的表数据存取方式不同,支持的功能也不同。
从图中不难看出,不同的存储引擎是公用一个Server层的,也就是从链接器到执行器的部分。
1.2 连接器(管理连接、权限认证)
要使用Mysql数据库,你要做的第一件事是连接到Mysql数据库上,这时候接待你的就是连接器。
连接器负责跟客户端建立连接
、获取权限
、维持和管理连接
。连接命令一般是这么写的:
mysql -h$ip -P$port -u$user -p
$符号表示要输入的内容限制,输完该命令后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在-p后面写在单条命令行中,但是这样可能会导致你的密码泄露。如果你连接的是生产服务器,强烈建议你不要这么做
解析:连接命令中的mysql是客户端工具,是用来跟服务端建立连接的。在完成经典的TCP
握手后,链接器就要开始认证你的身份,这时候用的就是你输入的用户名和密码
- 如果用户名或者密码不对,你就会收到一个
Access denied for user
的错误,然后客户端程序结束执行 - 如果用户名密码认证通过,链接器会到
权限表
中查出你所拥有的权限。之后,这个连接里面的权限判断逻辑,都将会依赖于此时读到的权限
连接完成之后,如果你没有后续的动作,这个连接就处于空闲状态,你可以通过show processlist
来查询当前连接的情况,如图所示,这其中的 Command列显示为Sleep的这一行就代表着当前系统中的这个连接是空闲的
如果客户端在8个小时(这个值是由参数wait_time_out
控制的,默认为8个小时)内没有执行任何操作,那么连接器就会自动将链接给断开
在连接被断开之后,如果客户端再次发送请求的话,就会收到一个错误提醒Lost connection to MySQL during query
。很明显,如果我们想要继续连接,那么必须再次发起连接请求
长连接:连接成功以后,如果客户端持续有请求
,则一直使用同一个连接
,短连接则是指每次执行完很少的几次查询就断开连接
,下次查询再重新建立一个
但是全部使用长连接之后,你可能会发现,有时候Mysql占用涨的特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的
。这些资源会在连接断开的时候才会被释放,所以如果长连接累积下来,可能导致内存占用太大了,被系统强行杀掉(OOM),从现象看就是Mysql异常重启了。
mysql执行中一些数据绑定在连接对象中,因为长连接长时间不断开导致内存占用太多。可以使用定时超时断开,或通过重新初始化链接来清空。
- 超时断开:定时断开长连接,使用一段时间,或者程序里面判断执行过一个占用内存的大查询之后,断开连接,之后要查询再重连
- 重新初始化链接:如果使用的是Mysql5.7或者更 新版本,可以在每次执行一个比较大的操作后,通过执行
mysql_reset_connection
来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将链接恢复到刚刚创建完的状态
1.3 查询缓存
在连接建立完成之后,就可以执行select
语句了。执行逻辑就会来到第二步:查询缓存
MySQL拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句,之前执行过的语句以及结果可能会以key-value
对的形式,被直接缓存到内存中,key是查询的语句
,value是查询的结果
,如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。
如果查询语句的key不在查询缓存中,就会继续后面的执行阶段,执行完成后,执行结果会被存入查询缓存中,你可以看到,如果查询命中缓存,MySQL就不必执行后面的复杂操作,就可以直接返回结果,这个效率很高(类似于备忘录吧)
但是大多数情况下不建议使用查询缓存,这是因为查询缓存往往弊大于利
原因:这是因为查询缓存的失效是非常频繁的,只要有对一个表的更新,这个表上的所有查询缓存都会被清空。因此很可能你费劲地把结果存起来,结果还没使用呢,就被一个更新全给清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。
使用场景:你的业务就是有一张静态表,很长时间才会更新一次,比如,一个系统配置表,那么这张表上的查询才适合使用查询缓存
Mysql提供了这种”按需使用”的方式,你可以将参数query_cache_type
设置为DEMAND
,这样对于默认的SQL语句都不使用查询缓存,而对于你确定要使用查询缓存的语句,可以使用SQL_CACHE
显式指定,像下面这个语句一样
select SQL_CACHE * from T where ID = 10;
注:MySQL8.0版本直接将查询缓存的整块功能删除掉了。
1.4 分析器(词法分析、语法分析)
分析器的功能
- 识别传进来的字符串
- 根据传进来的字符串用语法规则进行判断
- 如果有错误,将会收到You have an error in your SQL syntax的错误
如果没有命中查询缓存,那么就要开始真正执行语句了。首先,MySQL需要知道你想要干啥,因此需要对SQL语句做解析
分析器先会做词法分析
,你输入的是由多个字符串和空格组成的一条SQL
语句,MySQL需要识别出里面的字符串分别是什么,代表着什么。
MYSQL从你输入的select
这个关键字识别出来,哦,你想要执行一个查询语句,它也要把字符串T
识别成表名T
,把字符串ID
识别成列ID
。
做完了这些识别以后,就是做语法分析
,根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL
语法。
如果你的语句不对,就会接收到You have an error in your SQL syntax
一般语法错误会提示出第一个出现错误的位置。请关注user near
的内容
1.5 优化器(执行计划生成、索引选择)
prepare阶段
在这个阶段中,完成的是程序的预处理
- 它先要检查SQL查询语句中的表或者字段是否存在
将select * 中的* 符号
,解析为表上的所有列
optimize 阶段
经过了分析器之后,MySQL就知道你想要做什么了,在开始执行之前,还要先经过优化器的处理(数据库就要针对你的需求想一个最优的解决方案
,也就是执行计划
,这个最优方案选择的操作,这个就是优化器要做的事情了)
优化器是在表里面有多个索引的时候,决定使用哪一个索引
,或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序
,比如执行下面这样的语句:
select * from t1 join t2 using(ID) where t1.c =10 and t2.d=20;
# using 表示两张表的字段关于ID关联
- 它既可以先从表t1里面取出c=10记录的ID值,再根据ID值关联到表t2,再判断t2里面的d的值是否等于20
- 也可以先从表t2里面取出d=20的记录的ID值,在根据ID值关联到表t1,在判断t1里面c的值是否等于10
以上这两种方案的逻辑结果是一致的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案,如果想要查看它使用了哪个索引,那么你应该要使用
explain select * from t1 join t2 using(ID) where t1.c =10 and t2.d=20;
简单来说就是优化器会改写sql,包括join的连接顺序,匹配索引,找到最优的sql策略
1.6 执行器
MySQL通过分析器知道了你要做什么,通过优化器知道了怎么做,于是进入了执行器阶段,开始执行语句
开始执行的时候,会先做一个判断,判断你对这个表T有没有执行查询的权限,如果没有,那么就会返回没有权限的错误。如果命中了查询缓存了,会在查询返回结果的时候,做权限验证,查询也会在优化器之前调用precheck验证权限。而precheck是无法对运行时涉及到的表进行权限验证的。
而在执行器这一块我认为是做的一个存储过程、触发器、函数之类的验证,因为在这些模块中是可能会调用到其他的表的,MySQL需要判断当前用户是否有对这些调用到的表的权限。
如果有权限,就打开表继续执行,打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口
比如例子中的表T中,ID字段没有设置索引,那么执行器的执行流程如下
- 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存储在结果集中
- 调用引擎接口取下一行,重复相同的逻辑判断,直到取到这个表的最后一行
- 执行器将上述遍历过程中所有满足调节的行组成的记录集返回给客户端
对于有索引的表,第一次调用的是”取满足调节的第一行”这个接口,之后循环取”满足调节的下一行”接口,这些接口都是引擎已经定义好的
在数据库的慢查询日志
中会看到一个rows_examined
的字段,表示这个语句执行过程中扫描了多少行。这个值就是执行器每次调用引擎获取数据行的时候累加的
在有些场景下,执行器调用一次,在引擎内部扫描了多行,因为引擎扫描行数和row_examined
并不是完全相同的。
问题:
如果表 T 中没有字段 k,而你执行了这个语句 select * from T where k=1, 那肯定是会报“不存在这个列”的错误: “Unknown column ‘k’ in ‘where clause’”。你觉得这个错误是在我们上面提到的哪个阶段报出来的呢?
解答:应该是分析器,分析器会对传入的SQL执行词法解析和语法分析,其中词法分析会分析出SQL想要操作DB中的的哪张表,哪个字段,然后语法解析会在词法解析的基础上进一步做表名和字段名称的验证和解析。
1.7 用自己的话描述一下答案
我认为这个问题首先要从Mysql
的内部组件开始回答
Mysql
从内部服务组件分类可以分为server层
和存储引擎层
,其中跨存储引擎的功能都在server
实现,比如说触发器,视图等,而存储引擎层执行实际的I/O
操作,完成数据的存储和提取
那么当我们执行sql
语句的时候,首先我们以客户端的身份,向服务端mysql
的连接器发起TCP
连接请求,当连接建立完毕后,就建立了一条mysql
连接,就可以通过此连接交换数据了
数据库里面,长连接是指连接成功后,如果客户端持续有请求,那么就一直使用同一个连接,短连接则是指每次执行完很少的几次查询就断开连接,下次查询再建立一个
在mysql8.0
以前,执行查询的sql会先经过缓存
,查询缓存是否命中,如果缓存命中,那么就会直接返回结果,否则就会走查询流程,而查询流程会先经过分析器
,分析器要完成两个事情,第一个事情是做词法分析,词法分析这个过程完成了两个工作,第一个工作是解析器解析,这个过程是解析你的sql里面的字段,提取出来备用,第二个工作就是分析这些字段的含义,比如说select
等
然后会紧接着做第二个事情,第二个事情叫做语法分析
,语法分析的过程实际上就是分析你的sql
语句是否符合mysql的语法规定
当mysql知道你的操作意图后,会先经过优化器,这个优化器会针对你的操作意图生成一个执行计划
当mysql知道你要做什么,你要怎么做之后,就会开始执行
在执行之前会先判断你是否有操作的权限,当你没有操作的权限的时候,就会报错
在工程的实现上,对于查询缓存,也是会针对你的权限进行判断的
为什么权限检查不在优化器之前做?
因为有些操作是无法预知的,比如说在sql中执行
update
操作会触发一个触发器,但是在优化器之前是无法预知到你的这个操作的,只有在你触发的时候才会感知到,而如果你恰好触发器中有权限相关的操作,这时候需要再检查一次了
执行的过程是交给执行器的,执行器调用这张表所定义的存储引擎中所提供的服务,进行操作,最终将数据收集,返回给客户端程序。
1.8 存储引擎的特点知道多少?
存储引擎
是存储数据、建立索引、更新/查询数据等技术的具体实现,存储引擎是基于表的,而不是基于库的,所以存储引擎也可以被称为表的类型
-- 当前数据库所支持的引擎类型
show engines;
create table(
)engine = MyISAM;
InnoDB
存储引擎的特点如下
事务
:DML操作遵循ACID模型,支持事务,ACID分别是原子性、持久性、隔离性、一致性行级锁
:支持行级锁,提高并发访问的性能外键
:支持外键,保证数据的完整性和正确性
它存储的文件在磁盘上是xxx.ibd
,这个叫做表空间文件,用于对表的描述,包括有索引
、表结构
、具体数据
MyISAM
存储引擎的特点如下:
事务
:不支持事务外键
:不支持外键行级锁
:不支持行级锁,仅支持表级锁
Memory
存储引擎的特点如下:
这个存储引擎非常特殊,它的表数据是临时存储在内存中的,在读写速度快的同时,由于存储在不可靠的设备上,因此使用该引擎的表只能够作为临时表或者缓存进行使用
- 特点:访问速度快、支持哈希索引,xxx.sdi
1.9 怎么来选择存储引擎?
在合适的业务下选择合理的存储引擎
InnoDB
:对事务的完整性有要求,在并发下要求数据的一致性,数据除了插入和查询之外,还包括很多的更新、删除操作,选用此引擎MyISAM
:如果这个引擎是以插入和读取操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高的话,可以选择这个引擎Memory
:表大小有限制,太大的表无法缓存到内存中,无法保证数据的安全性