逻辑结构
由 上文 提到的:
Uses multi-layered server design with independent modules. 同样是分层思想的体现
我们就来看看 mysql 的逻辑分层是怎样的?如下图(引自):
这张图描绘的非常清晰,服务端的主要模块有:
- Connections/Thread handling:这就是处理连接的线程(池)
- Query Cache:查询缓存
- Parser:解析器
- Optimizer:优化器
- Storage Engines:存储引擎
- 其他的 cache/buffer 缓存
我们一项项来看下
Connection handling
这里对应我们 JDBC 代码中的如下部分,JDBC相关操作可以参见 java后端开发(六):后端如何理解数据库的数据 来理解:
// 从DriverManager处获取数据库连接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://数据库ip/数据库名称",
"账号",
"密码" );
客户端通过JDBC驱动向 Mysql 服务端发起连接,并携带上账号密码以及需要访问的数据库IP和名称,并且可以携带一些URL参数来传递其他信息,最终服务端将连接建立好以后,我们就获得了表示这个连接通路的 Connetction
对象。
那么服务端的 Connection handling 这个模块的功能有哪些呢?
核心功能:
- 建立并管理与客户端的连接。
- 验证:验证账号密码,验证URL中的其他配置等,比如开启了SSL认证,则需要进行证书认证
这部分最终的线程模型后面会再次提及,这也是非常重要的。
Query Cache
查询缓存比较尴尬,因为官方消息如下:
Note
The query cache is deprecated as of MySQL 5.7.20, and is removed in MySQL 8.0.
也就是5.7.20之后的版本被废弃了。为什么呢?
首先看下其原本的设计功能:
对于 select 语句的查询结果进行缓存,当再次收到相同的 select 语句后,直接将结果返回,不进行下面的步骤。
这功能看起来不是很好嘛?但是有诸多限制和缺点,分类如下:
- Queries must be exactly the same (byte for byte) to be seen as identical,也就是 字符串需要完全相同,并且语句中所有信息是确定的,比如包含了函数 NOW() 的语句其查询结果就不会被缓存下来 。下面这两个语句就会判断成两个查询语句:
SELECT * FROM tbl_name
Select * from tbl_name
- 另外一种限制是缓存的失效情景非常多,比如增删改等各种操作。这就导致了缓存命中率非常低,也就是程序花费了额外的时间来执行查询缓存,结果命中率低,导致性价比不高。
- 查询缓存是存储在内存中的,所以对于内存的影响也很大;而且从存储、更新到删除都会消耗性能
更多详细的限制可以参考 《高性能Mysql》第七章。
其实,为什么一定要在服务端缓存数据呢?客户端照样可以,所以大家都会接触到比如 mybatis 的二级缓存、redis缓存等。
Parser
这里的解析器实际上起到了我们编程中编译器的作用。对于 SELECT * FROM tbl_name
这样一个字符串,需要经过下面的几个过程,程序才能完整的理解:
- 词法分析:比如 SELECT 是关键字, tb1_name 是表名等
- 语法分析:比如这个是查询语句,其格式满足 sql 格式
- 语义分析:这里的语义分析实际上等同于
预处理器
,这个预处理器也可归纳到解析器中。预处理器会检查数据列等是否存在,别名是否有歧义,并进行权限验证(是否有执行 select 的权限等)
在 语法分析后,就会生成一棵解析树,类似于编译原理中的 AST(抽象语法树)。比如下面的SQL语句:
select username, ismale from userinfo where age > 20 and level > 5 and 1 = 1
其解析树参考如下,有个感性的认识:图引自
Optimizer
优化器的最终目的是按照一定的指标(优化目标)生成它认为最佳的执行计划。这里的执行计划指的是,比如 A 表和 B 表关联查询,则先查询A表中符合条件的还是先查询B表中符合条件的,这就相当于两种不同的执行计划。
这里面有三个关键点:
- 优化指标如何考量?
- 对什么进行优化?
- 优化指标中的一些信息如何获取?
并且,还需要注意,优化器实际上很多信息是不知道的,比如需要查询的数据在磁盘中还是已经在内存中了;也不知道某个表具体有多少行。
对于一些表中具体有多少行这样子的信息,或者每个表占用了多少个页,这些信息都需要从存储引擎中获取;对于其他无法获知的信息,比如数据在磁盘还是内存,只能约定。比如就认为读取任何数据都需要一次磁盘IO操作。
基于上述情况,优化指标可以简单理解为每个查询语句需要做多少个数据页的随机访问,随机访问的页数越多,所需要的时间也越长。
那么对什么进行优化,即优化策略。可以类比为 JAVA中的编译,我们在程序运行前会执行一次编译,这种叫做静态编译,会执行一些常量替换或者代码逻辑优化等操作;当JAVA程序运行的时候,比如执行反射语句,就是动态编译。
回到 Mysql,其优化策略也分为两种:
- 编译时优化(静态优化):就是对于解析树分析并优化;
- 运行时优化(动态优化):比如查询数据时索引的选择;
具体到优化器的优化点,后续再进行研究。只是:
不要自以为比优化器更聪明 – 《高性能MySQL》
当然,如果有十足的把握,也可以在SQL语句中提示优化器该如何进行优化。
(查询)执行引擎和存储引擎
首图中没有标明执行引擎,因为是逻辑结构,所以也可以将执行引擎和存储引擎归为一类,他们都属于执行阶段。
在这个阶段,就非常简单了。好比设计师规划好了图纸和方案,工人们只需要按部就班执行即可。
存储引擎对外提供了一些基本的接口,通过这些功能接口的调用组合,可以实现任何数据的增删改查等操作。而执行引擎就是根据优化器提供的执行计划来逐步执行。
处理流程
有了上面的铺垫,逻辑处理流程可以用下图(参考高性能 mysql)表示:
其他过程不再赘述,只提一下数据返回。 Mysql 将结果集返回给客户端是增量、逐步返回的过程,并不是等到全部结果查好再一起返回。提到关联查询时,我们会再次说到这点。
本图中也没展示出Connection handling,后面也会再次提及。
物理结构
物理结构先不考虑存储引擎下面是如何管理的,只是来看下 mysql 软件的文件分布。
mysql软件相关的文件:
- my.cnf/mysql.cnf:这是mysql server 的配置文件
- auto.cnf:这是mysql server 的uuid文件
- 其他后续遇到了再说
重点来看下创建一个数据库以及数据表以后,其文件有哪些?
- .frm:是表结构文件
- .ibd:mysql数据和索引文件
这两个是基于 InnoDB存储引擎,这也是现在默认的存储引擎,以后也基于这个进行研究。
创建好一个数据库如 testdb 后,会在对应的路径下创建好 testdb 目录,表示这个数据库。testdb目录中会有一个 db.opt 文件,表示这个数据库的配置信息,比如字符集是什么等。
当创建一个 user 表后,testdb目录下会出现两个文件,就是 user.frm 和 user.ibd
下面一篇文章,就来研究下Connection handling 这个模块,也就是重点研究下 Mysql 的线程模型。