前言
很多人不知道为什么要阅读源码?为什么要知道底层知识?
我也思考了这个问题好些年。最近慢慢有些懂了。
在工作中,实际使用中间件的时候,如果不知道中间件的内部原理,对于其外在的表象是很难理解的。只能做到粗浅的记住,要达到理解,在不懂底层原理的时候,是很难的。
随着业务的复杂度和体量变大,对于中间件的要求会更加严苛,往往会出现很多难以理解的现象。在这些现象背后是由于不合理的使用造成的。而要解释并解决奇怪的问题是需要内部的实现原理支撑的。
对于Mysql而言,日常CRUD当然是没有任何问题的,但是也会遇到CPU突然飙升,内存突然飙升的问题,那么这个时候肯定是不能束手待毙,难道要告诉老板,我只会CRUD?
解决CPU和内存问题的基础就是要懂得Mysql或者InnoDB引擎内部到底在做些什么事情。只有懂了底层,才能够去分析原因。最终才能完成性能调优工作。
本篇文章就带大家来看看InooDB引擎内部的原理。
Mysql
Mysql的历史
Mysql诞生于1995年,在一家瑞典的公司中。
Mysql之所以叫做My,不是因为我的sql,而是因为Mysql之父-Michael Widenius的女儿叫做My。意寓是Mysql就好像是自己的女儿一样。Hadoop项目名称的由来是Hadoop项目创建者Doug Cutting儿子的一个黄色玩具大象。其实关于项目的名称有很多有意思的事情,之后有时间整理,可以详细讲讲。下面是Msyql之父的照片,大佬啊。
总体概览
直接看一张Mysql的内部架构图。
如果你之前没有接触过Mysql内部相关内容,可以先大致过一下。下面我们来细细地来讲解下。
首先,我们可以看到Mysql内部是分为两大块的。分别是上面的服务层,下面的Innodb引擎层。服务层用来处理一些通用的逻辑,而引擎层处理核心的存储逻辑。
那么为什么Mysql内部要分为两个模块呢?因为不同的业务场景会对数据库有不同的要求。
比如有些需要数据库性能好,延迟低,而不在乎数据的持久化。另外一些需要数据能够持久化,有事务操作,但是对于性能要求会低一些。如果开发者对于Msyql足够了解,Mysql甚至可以让开发者自己实现存储引擎来满足特定业务场景下对于数据库特定的需求。这也许就是开源的魅力之一吧。
Mysql有数种存储引擎,包括InnoDB,MyISAM,Memory等
当然,经过25年的发展,业界内部基本上都使用默认的InnoDB引擎,而不再使用其他存储引擎了。有点像什么规律,最后只会有一个或者两个胜出。(这个规律得找到然后讲出来)
比如下面这个建表语句
create table user {
id int(10),
name varchar(10)
}
创建一张名称为user的表,里面存在两个字段分别是name和age。
那么他的存储引擎是什么?有人会说,刚才刚说过默认情况下是Innodb,所以user表的引擎是Innodb。
那么这就引发另外一个问题?Mysql也支持很多存储引擎,我该如何切换到其他存储引擎?
这个问题也许很多人就不知道了,因为Mysql发展到现在,业界基本上只会使用Innodb来存储数据。其实是这样切换的。
create table user{
id int(10),
name varchar(10)
} Engine = MyISAM
大家又可以从这种语法中可以发现,存储引擎是针对表的,每张表都必须定义自己的存储引擎,默认为Innodb。
组件概览
Server层中包含5个模块,分别是连接器,解析器,优化器,执行器和缓存。
- 连接器:其实内部是一个连接池,对外提供连接。每个客户端首先都得和Mysql建立连接才能正常工作。
- 解析器:Mysql不能直接理解sql语句,必须经过解析器。解析器会将sql解析为一颗语法树。语法树最终会被转化为一个个函数调用。只有函数调用才能被Mysql所执行。
- 优化器:程序员编写的sql语法,不一定是最优的。Mysql会对其进行优化,成为执行效率相对来说比较高的语法树。
- 执行器:执行器会对优化后的语法树进行执行。
- 缓存:Mysql会缓存sql执行的结果在内存中,之后相同的请求进来,可以直接读取缓存中的内容。但是,由于缓存命中率低,高版本的Mysql已经移除了缓存。
Buffer Pool
buffer pool是一个大的内存空间,用来存放mysql中的数据。在mysql中,任何的查询语句都会从buffer pool中获取,如果buffer pool中获取不到,会先从磁盘中加载数据到buffer pool中。换句话说,所有从mysql中获取的数据都不可能直接去读取磁盘中的数据,必然经过buffer pool。
比如下面这样一条查询语句
select * from user where id = 100
上述查询语句需要在user表中查询到id为100的记录。我们知道所有的查询语句都是通过执行器执行的,执行器会先查询buffer pool,如果buffer pool中存在,那么会直接获取到数据并返回。如下图所示。
id为10的记录已经存在于buffer pool中,那么就会将id为10的数据直接返回。
buffer pool毕竟是内存,Mysql不可能把所有的数据都加载到内存中来,必定存在一部分数据还在磁盘。
那么如果buffer pool中没有id为10的记录呢?buffer pool就会先从磁盘上面加载id为10的记录到buffer pool中,之后再次返回。如下图所示。
讲到这里,buffer pool的简单运作原理想必大家都明白了。那么可能会有人有疑问,为什么要有buffer pool?不能直接读取磁盘吗?
这是因为直接读取磁盘太慢了,如果所有的请求都需要重新读取磁盘,那么Mysql是不可能会有现在这么高的性能的。而通过buffer pool的形式来读取,可以保证大部分的请求都是落在内存上面的,只有小部分请求会落在磁盘上面,大大提升了性能
Redo Log
Redo Log很多也会叫做重做日志。重做日志记录的是每一个数据库的操作。比如更新user表中id=10的记录中的name字段是zhangsan或者是删除id=11的记录。
在Mysql异常宕机的时候,由于buffer pool中的数据可能还未刷入到磁盘中,导致数据丢失。通过Redo Log,在Mysql重新启动的时候,再次执行相关操作来恢复数据到宕机之前的状态。
比如下面这条更新语句。
update user SET name = zhangsan where id = 10
每个更新数据的操作都会在Mysql更新完Buffer Pool之后,去在Redo log buffer中写入一条操作日志,如下图所示。
要注意的时,只有在事务提交之后,才会将Redo log buffer中的日志,刷入到磁盘文件中。
Undo Log
如果Redo Log被叫做重做日志,那么undo log应该可以被叫做未做日志。记录的是未开始执行事务之前的原始数据。
比如还是这条更新语句。
update user SET name = zhangsan where id = 10
在这条更新语句执行之前,id为10的记录name字段是lisi。那么会在未执行更新语句之前将id为10记录的原始字段值记录在undo log中。内容可以认为是id=10,name=lisi。
万一事务需要回滚,由于undo log中记录了数据的原始值,可以根据undo log来将数据回滚到未执行事务之前的状态。
Bin Log
除了Redo log记录了每次数据变更操作之后,Bin Log也会记录每次的数据库变更操作。如下图所示。
可以看到在写入redo log之后,执行器也会在bin log中写入一条日志,并在提交事务的时候,将bin log日志刷入到磁盘中,之后将本次日志的在bin log的位置和commit标识写入到redo log中。保证redo log和bin log日志的一致性。
那么Redo Log和Bin Log之间的区别是什么呢?在于以下两点。
- 形式不同。Redo Log记录的形式偏向于物理日志,比如某个数据页中的A数据更新为B数据,而Bin Log记录的形式偏向于逻辑日志,比如某条记录的A数据变更为B数据。
- 记录者不同。Redo Log由Innodb引擎来记录,而Bin Log又Mysql Server来进行记录。比如你更换了存储引擎,那么就不会有Redo Log,而只有Bin Log。
在业界往往使用Bin Log来进行数据库同步,比如Mysql主从复制,Mysql数据同步到其他数据源。
最后
这篇文章先建立Innodb引擎的大体的感受,知道里面有了几个模块,各自模块的基本作用是怎么样的。之后再详细讲讲里面的重要模块的一些运作细节,对于Innodb引擎有一个全面的了解和认识。
看到最后的小哥哥小姐姐点个赞呗。写篇文章也挺不容易的。
我是shane。写于2020年9月14日,早上。