Mysql篇之架构设计
Mysql是我们在日常开发中最常使用的一种数据库,当我们利用Mysql实现各种业务增删改查时,都是将其当做一个黑盒在使用,我们的系统只需要从数据库的连接池中获取一个连接就可以执行sql工作。执行一条insert语句,表里会多出来一条数据;执行一条select语句,就能从表里找出满足条件的数据。至于这个过程是怎么实现的,对我们来说,底层的事情都交给了数据库,内部工作和运行机制完全透明,以至于在学校学了一个学期的数据库课程,只学到了表层和皮毛,以为会写超长复杂的Sql语句就算掌握了数据库,直到进入职场被面试官吊打,在面对线上超大数据体量的Sql优化时两眼懵逼,我们对数据库的了解还太少,底层原理和深层优化有待提升,因此在这里开一个Mysql专题进行爆肝与死磕,将学习心得与总结和大家共享。
一. Mysql的架构设计
1.连接器
Mysql作为服务器,一个客户端的Sql连接过来就需要分配一个线程进行处理,这个线程会专门负责监听请求并读取数据。这部分的线程和连接管理都是有一个连接器,专门负责跟客户端建立连接、权限认证、维持和管理连接。
2.解析器
SQL解析,就是按照SQL语法,把我们编写的SQL语句进行词法分析,理解这个SQL需要做什么事情,比如如下SQL语句,就会被解析器给拆成三步逻辑:
- 查询user表;
- 寻找到userId=007的那条数据;
- 将数据中的name、age、country字段信息提取出来;
select name,age,country from user where userId=007;
3.优化器
当解析器把SQL语句解析出来步骤以后,紧接着会通过优化器来选择一个最优的查询路径,这个是对于我们编写的超大型复杂SQL时十分有用,一条SQL语句可能会对应多个不同的查询路径树,优化器会从中选择一个最优路径出来,返回一个这个语句的执行方案交给下一步进行继续处理。
4.执行器
这一步就是根据上一步骤优化器生成的SQL执行方案,去调用存储引擎的接口(InnoDB、mysam)完成SQL语句的执行计划,这个SQL引擎操作的有可能是内存数据,也有可能是磁盘文件。
于是把上面一条SQL语句的执行抽取出来形成下面这个Mysql的逻辑架构图:
Mysq架构设计补充:MySQL 8.0 版本直接将查询缓存的整块功能删掉了,日常也不建议开启使用。
二. InnoDB存储引擎设计
上面流程介绍了一条SQL语句在数据库中的执行流程,当SQL执行到存储引擎这里,因为不同的引擎的实现机制有所不同,现在就以使用最广泛的为InnoDB引擎再来细化说明Innodb在数据查询和更新流程的细节。
1. 内存缓冲池
InnoDB中有一个非常重要的内存组件——缓冲池(Buffer Pool),这里会缓存很多数据便于查询时,直接从读取缓存数据,而不需要访问磁盘。在执行更新操作时,如对“id=007”数据进行更新,会先查询BufferPool中是否存在,不存在的话,就从磁盘中加载到缓冲池中来,然后还要对这行记录加独占锁。
2.undo日志
如果执行一个更新语句,且这个语句还在事务里的话,在事务提交以前,我们都可以选择回滚,而这部分回滚的数据,就是未更新以前的数据,它是保存在undo日志里的。
3.redo日志
在更新操作时,会先更新Buffer Pool中的数据然后再去操作磁盘,但是在极端情况下会出现系统宕机或者断电导致磁盘还未更新就丢失了数据,此时需要把对内存所做的修改写入到一个redo log buffer里去,这里也是一个内存缓冲区,用于存放redo日志的。在事务提交策略上,有一个关键配置:
innodb_flush_log_at_trx_commit=1
commit=0时,事务提交成功,redo buffer不会写入redo log
commit=1时,只要事务提交成功,redo buffer一定会写入redo log(推荐)
commit=2时,事务提交成功,redo buffer先写入os cache,然后过段时间才刷入redo log
采用了commit=1的配置,就能保证提交事务的时候,redo日志会刷入磁盘,数据不丢失。
4.binlog日志
前面说的redo日志是偏向物理性质的日志,记录的是对数据页中某一个数据进行了什么修改,和引擎有关。而Binlog日志则偏向于逻辑层面的一个归档日志,记录的是对表中某行数据做了什么操作,且修改后的值为多少,是Mysql Server自己的日志文件。
binlog日志的落盘是在上面redo日志落盘以后才会去执行的,而且落盘策略也是可以选择直接刷盘还是先刷到OS cache中,这个配置项取决于sync_binlog
。sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘,这样可以保证 MySQL 异常重启之后 binlog 不丢失(一般都推荐这个值)。当Mysql把binlog写入了磁盘文件以后,就完成了最终的事务提交,这次提交就会把本次更新对应的binlog文件名+binlog的位置都写入redo log的日志文件中,并同时写入一个commit标记。到这里,才最终完成了一次事务的提交,总的一个流程图如下:
5.IO线程随机刷盘
当事务提交完毕以后,所有的日志文件都已经更新到最新了,此时系统不惧任何宕机断电行为了,后台的IO线程会随机把内存中的那个更新后的脏数据刷入到磁盘文件中,此时内存和磁盘中的数据已经保持一致。
更新流程核心总结
基于InnoDB的数据更新做一个流程总结如下:
- 1、加载磁盘文件到buffer Pool中;
- 2、更新数据之前,写入旧数据到undo日志,便于回退;
- 3、更新内存中的buffer pool数据;
- 4、将更新部分的redo log写入到redo log buffer中;
- 5、redo日志刷入磁盘
- 6、binlog日志刷入磁盘
- 7、将binlog文件和位置写入到redo日志文件中,并写入commit。
- 8、后台的IO线程某个时间随机将buffer pool中的脏数据同步到磁盘文件。
InnoDB引擎中主要就是包含了buffer pool、redo log buffer等内存数据,同时也包含了undo日志、redo日志等磁盘文件数据,另外Mysql也会有自己的binlog日志。buffer pool是Mysql里面的一个核心内存组件,所有的增删改查操作都是针对buffer pool来进行的,然后才是去配合写undo、redo、binlog等操作,这些都是组件的设计既可以用于提高数据库的并发性能,同时更重要的可以设计保证事务的四个特性(ACID),譬如redo日志可以保证持久性,undo可以用于回滚保证原子性等等。这一节就到这里,后面一节的会重点安排一下InnoDB索引及数据结构的内容。