Hello,大家好我是极客涛😎,我最近在整理Mysql相关的知识点,所以准备开启一个Mysql的主线任务,大概耗时3周左右,整个节奏还是由浅入深,主要包括Mysql的架构、事务实现、索引组织形式、SQL优化、日志系统、锁、主从架构、无锁变更、最佳实践等等。大家可以随着我的这个路线,一起沉淀沉淀,如何检验自己的学习成果呢,刷面试题,遇到不清楚的在往深里研究。好了,那我们开始~
引言
我们知道Mysql是一个数据库系统,提供了对数据的增删改查API,我们通过一条条简单的SQL就可以实现对系统的各种操作,为了更容易理解,我们按照从整体到局部的思路,先鸟瞰Mysql整体的架构,然后再对每个细节深入研究,所以今天我们通过一条查询SQL的执行流程,看一看Mysql的应用架构是如何组成的。
本文结构
阅读体验
📚 全文字数 : 4k+
⏳ 阅读时长 : 6min
Mysql的应用架构
Mysql的应用架构遵循了职责单一、能力分层、插件化的原则。整体上看,Mysql主要分为服务层和引擎层:
服务层:Mysql的通用能力层,负责对外(客户端)暴露端口、解析SQL语法、执行SQL优化、通过调用引擎层的API对整个查询过程进行API调用的编排。
引擎层:引擎层负责对数据的存储和读取,并通过API将能力暴露给服务层使用。Mysql支持不同的存储引擎通过插件的方式接入Mysql的服务层,不同的存储引擎适用于不同的使用场景,如Memory、InnoDB、MySAM。
再进行细分的话,服务层又包括:连接器、查询缓存、分析器、优化器、执行器。
连接器
不管Mysql内部如何运行,都需要有一个与外界进行交互的桥梁,连接器就起到了桥梁的作用。连接器是Mysql的统一门面,负责与客户端建立会话连接、进行用户认证、查询用户权限、维持和管理会话连接。
mysql -h$ip -P$oprt -u$username -p$password
当我们执行mysql的连接命令时,通过ip
+ port
定位到是哪个Mysql服务,经过TCP握手之后,连接器和客户端就建立了会话连接,然后这条命令就来了连接器。连接器首先通过 -u
和 -p
参数对本次请求进行认证操作,验证用户是否在Mysql服务中注册过,密码是否正确,如果认证失败,则直接响应Access denied for user ‘xxx’@‘localhost’ (using password: YES)
。
认证通过后,连接器则会查询用户的权限列表,判断对该用户是否开放了远程连接权限。Mysql默认只允许本机(localhost)进行连接操作,如果没有远程连接权限直接响应Access denied for user ‘root’@‘%’ to database ‘mysql
。如果有远程连接权限,那么本次连接就真正意义上建立完成了。需要注意的是,查询出来的权限列表会一直向下传递,之后对表的权限校验、对操作的权限校验都依赖于这个权限列表。
正是因为先鉴权再建立会话连接,所以即使对当前连接用户的权限进行了修改,也不会立即生效,需要等到下次重新连接时才会生效。
为了提高资源的利用率,当客户端与连接器建立连接之后,如果客户端长时间没有进行任何操作,连接器会自动将这个连接断开(默认是8
小时,由wait_timeout
参数控制)。
在客户端与服务端的通信中,一般分为长连接
和短连接
:长连接
就是客户端和服务端建立连接之后会一直保持的这个连接,后续的任意操作不会重建连接,这样避免了每次操作需要重新创建连接的操作,执行速度会快很多。但是连接不释放就意味着对象不能销毁,如果连接数过多的话,可能会导致Mysql服务因内存不足而重启;短连接
相反,每次请求都需要重新创建连接,相对来说执行速度会慢一些,但是每次请求完都释放连接和对象,这样消耗的内存资源要大大降低。
从客户端的角度来说,肯定更中意使用长连接,因为可以提高响应速度;从服务端的角度来说,更中意短连接,因为逻辑简单,资源消耗少。那如何中和两个方案的优缺点呢?连接池
就顺势而生了,连接池
是存在于客户端的,应用程序会在初始化时通过连接池申请若干个长连接,而应用程序内的所有线程共享
这个连接池,因为一次请求很快就结束了,这样其它请求来的时候可以复用
这个连接,而当某个连接长时间没有被使用时,就将其释放,这样便发挥了长连接和短连接各自的优点。
查询缓存
select * from geektao where id = 2;
通过连接器的认证鉴权之后,就开始进行查询操作了。
首先会看看当前SQL是否已经在查询缓存中存在,查询缓存通过key=value
的方式存储SQL及其执行结果,如果存在则直接返回缓存中的执行结果,查询效率大大提高。
但是在实际的生产使用过程中并不建议使用查询缓存,因为生产中被查询的数据往往是不断变化的,这样就会造成缓存一直失效,每次查询完之后还要设置缓存,不仅没有提高查询效率,反而造成额外的开销,使用起来很鸡肋。Mysql8.0
版本直接把查询缓存整个模块删掉了,如果版本小于8.0
的话可以通过设置query_cache_type=DEMAND
关闭查询缓存。
分析器
如果查询缓存关闭或者缓存中没有的话,那么就真真正正的开始解析SQL了。了解编译原理
的同学应该知道,作为一个解析器不可缺少的三个功能:词法分析
、语法分析
、语义分析
。
词法分析
词法分析是SQL解析的第一步,它负责将输入的SQL语句字符串分解成一系列的Token(词元)。这些Token是SQL语法的基本组成单元,例如关键字(如SELECT
、FROM
等)、标识符(如表名、列名)、常量值等。但是此时分析器还不知道什么意思,就像我们小时候学字一样,只知道王是王,李是李,并不理解。
语法分析
语法分析阶段,MySQL根据SQL语言的语法规则,将词法分析阶段产生的Token序列转换成一个抽象语法树(Abstract Syntax Tree,AST
)。这个过程中,会检查SQL语句是否符合MySQL的语法规则。这时候就像小时候已经开始学造句了,根据特定的语法造句,但是也没有真正理解造出来句子的含义。
语义分析
在语法分析之后,接下来就是语义分析阶段。在这个阶段,MySQL的解析器会检查抽象语法树
是否有意义,即检查SQL语句在逻辑上是否正确。这包括识别表
和列名
、检查数据类型
、确定操作符
和函数
的正确使用、处理别名
和表达式
等。如果在这个阶段发现错误,比如找不到表或列、类型不匹配等,解析器会生成错误信息,此时Mysql才真正理解了这条SQL到底想干啥了。
优化器
经过分析器之后,Mysql就知道我们想干什么了,那下一步是不是就直接去干了。Mysql可没有你想的这么简单,在去干前,Mysql会”思考
“如何使用最优
的查询步骤来查询。
优化器会根据抽象语法树
生成执行计划,如果表中有多个索引的话会确定去哪个索引里查询,如果是多表关联查询的话会确定先去哪个表查询再去哪个表查询。总之,优化器会根据不同的情况制定出一个最优
的方案,这样Mysql才知道怎么做。
执行器
对执行器来说”器如其名
“,前边我们已经通过分析器知道了要做什么
,通过优化器知道了怎么做
,而执行器通过执行计划一步步的执行
。
在执行之前,执行器首先会判断当前用户是否对表有相关的操作权限,而判断的依据就是连接器当时查询出来的权限列表
。有同学可能有疑问,到执行器在进行表的权限校验是不是有些晚了,在分析器阶段不是已经知道是要操作哪张表了吗?
确实,在优化器之前会调用precheck
验证权限,但是这时候只是进行简单的权限校验,查询的表存不存在、列存不存在,对查询的表有没有操作权限,这些校验都是静态校验
。在真正执行时也需要动态校验
,因为执行时是运行态
,比如该表有个触发器,只能在执行阶段才能进行校验(有一点点像class文件加载过程中的验证阶段)。
执行器阶段的权限校验通过之后,就开始调用执行引擎的接口进行数据读取
操作了。
假设不走索引的情况
:
-
打开要查询的表;
-
调用执行引擎接口获取第一行记录,判断id是否等于2,等于则保存结果到结果集中;
-
调用执行引擎接口获取下一行记录,判断id是否等于2,等于则保存结果到结果集中;
-
…;
-
没有下一行数据,调用结束;
-
执行器将结果集返回给客户端。
至此,一条查询语句便执行完毕。
小结
今天主要讲解了Mysql的应用架构,并通过查询SQL为例,对Mysql的每个组件及其作用都进行了描述,相信大家对Mysql整个运行流程有了一个大概的了解,下边给大家留了一些问题加深大家的印象😄。
问题
- Mysql的基本架构包括什么,每部分有哪些作用?
select * from geektao where name = 'xx';
当表中没有name
字段时会怎么样?这是在哪一步判断的?- 为什么要使用连接池?工作原理是什么?一般如何进行参数调优?
- 如果Mysql服务端因升配导致连接失效,客户端该如何自动重连?
- Mysql哪些阶段会进行权限校验,目的是什么?
select sum(amount) from geektao;
,sum()
是在哪一步计算的?