前言
英文官网文档地址:https://dev.mysql.com/doc/
从官网上看,mysql现在已经更新到8.0版本,但从实际应用上来看,大部分企业在使用5.6或5.7版本
可以参考5.7版本的官方文档:https://dev.mysql.com/doc/refman/5.7/en/
MySql体系结构
mysql主要分为:
连接层:管理连接
查询缓存:查询结果缓存
解析器:sql语句解析器,包括词法分析,语法分析等,然后分解成分析树
优化器:把分析树解析成查询树,然后进行个各种优化,如重写查询,选择读取表的顺序,以及使用哪个索引等
执行器:根据优化之后的结果,调用存储引擎,并处理返回结果
存储引擎:插件式的,可插拔的
存储引擎层对于mysql来说是可插拔的,存储引擎只需要实现mysql执行器定义的接口,类似于mysql的JDBC驱动
Innodb体系结构
InnoDB主要分为两大区域,分别是内存结构和磁盘结构
MySql&Innodb整体架构脑图
Update数据写入磁盘的过程
SQL通过连接层的网络传输到达连接器
转交解析器,解析器经过词法和语法分析验证之后,检查查询缓存,如果有数据,则直接返回(查询缓存可以关闭,5.7及之后的版本默认关闭),解析器生成 分析树
-
优化器根据生成的分析树,生成对应查询树,进行逻辑优化,和物理优化
逻辑优化:
(1) 一定带来性能提升的优化:
子查询优化
等价谓词重写
条件化简
外连接消除
嵌套连接消除
语义优化
ETC
(2) 不一定带来性能提升的优化:
分组的合并
索引优化
连接条件的下推
用连接词取代集合操作
用UNIONALL取代OR操作
ETC
在查询优化器中,使用一个代价估算模型,也就是基于代价的查询执行计划估算,依赖于被查对象的各种数据,而数据是实时变化的,所以会定期统计这些数据。优化器在物理优化阶段需要对单表读取开销,两表连接开销,多表顺序开销等进行比较,比较基于的就是一些基础数据的值,这些数据通常不会被实时更新,所以优化器有时做出的计划未必是最优的
这也解释了为什么查看的执行计划和实际执行的不一定一致,因为数据在变化,两次执行代价估算模型计算结果可能不同
优化器将优化之后的执行计划交给执行器,执行器调用存储引擎
-
存储引擎接收到执行计划之后的流程]
事务开始
查询buffer pool中对应要修改的数据,如果没有,则从磁盘load
记录undo log
修改buffer pool中对应的数据
记录redo log,并将记录状态设置为prepare
返回给执行器,事务可以提交
记录bin log
提交事务
将redo log中这个事务的相关记录状态设置为commit状态
浅谈磁盘与文件系统
机械磁盘结构
磁盘结构分为主轴,柱面,盘面,磁道,扇区
磁盘的运行就是主轴以一定的速度的在不断的转动,现在通常是7200转每分钟
然后有一个磁磁头上下移动(切换盘面)或左右移动(切换磁道)来寻址
磁头通过对磁盘的加磁与消磁,分别表示0和1
磁盘的最小读写单位是扇区,大小512Byte
固态硬盘SSD结构
固态硬盘使用 浮栅晶体管 的充放电来进行读写,根据有没有电荷,来表示0和1
多个晶体管组成一个page,是读写的最小单位,通常是4K
文件系统与Innodb
硬盘使用驱动来交互,而和磁盘驱动直接交互的就是文件系统,通常是操作系统的一部分
文件系统为了提高磁盘IO的效率,通常定义最小的读写单位为4K,也就是8个扇区
经常说的4K对齐,就是为了保证一个4K大小的空间,8个扇区,也就是文件系统的一次最小IO,在同一个磁道,而不需要移动磁头去寻址,多余的扇区空间则不被编入文件系统的地址空间中,这就是4K对齐
而在Innodb中,因为是关系型数据库,相邻数据之前被同时读取的可能性很大,为了更进一步提供磁盘的IO效率,在文件系统之上进一步封装,使用16K作为读写磁盘的最小单位
内存结构
Buffer Pool
Buffer pool中缓存的是页面信息,包括数据页,索引页
默认大小128M,innodb_buffer_pool_size=128m
mysql并不会直接去修改磁盘中的数据,而是会修改内存Buffer pool中的数据((准确的说,是Buffer pool中的Change Pool),如果要修改的数据不在Buffer pool中,那么会先从磁盘中load进来,然后修改数据,然后交给后台刷盘线程去执行按页刷盘操作
如果要读取的时候,数据正好在Buffer Pool里面,那么可以直接返回
也就是说,Buffer pool提高了读写的效率
如果Buffer pool写满了怎么办呢?
Buffer Pool的LRU
Buffer pool中维护了一个链表,以数据页作为链表的节点,采用LRU来实现热数据的保留
Change Buffer
change buffer 是 Buffer pool的一部分
如果要修改一个数据,但这个数据页不是唯一索引,不存在数据重复的情况,也就不需要从磁盘加载索引页判断数据是不是重复(唯一性检查)。这种情况下可以先把修改记录到change buffer中,从而提升更新语句的执行速度
最后把change buffer 记录到数据页的操作叫做 merge。
merge的发生有如下条件:
在访问这个数据页的时候
后台线程主动merge
数据库shutdown
redo log写满
如果数据库大部分索引都是非唯一索引,并且业务时写多读少的,不会在写数据后立刻读取,就可以使用change buffer,适当调大这个值
innodb_change_buffer_max_size
Adaptive Hash Index
Buffer pool中使用了LRU算法,我们通常自己实现LRU会使用HashMap加链表的结构,HashMap中保存链表的节点指针,以降低头插入法寻址的开销
Innodb中同样也是如此,使用一个自适应的hash索引,降低在内存中查找指定页的时间复杂度
脏页
修改数据的时候,先写入到了buff pool,而不是直接写到磁盘。
内存的数据页和磁盘数据不一致,那么内存中的数据就叫做脏页
脏页由后台线程每隔一段时间就一次性把多个修改写入到磁盘,这个动作也叫刷脏
Log Buffer
Redo log 的内存buffer
redo log并不直接写入磁盘,而是通过log Buff提高效率
磁盘结构
Redo Log
先看定义: Redo log 叫做重做日志,保存的是数据的改变
特性:
Redo log 的大小是固定的,前面的内容会被覆盖,一旦写满,就会触发buffer pool到磁盘的同步,以便腾出空间记录后面的修改
-
为什么要有重做日志呢?
因为脏页的刷新并不是实时的,假如内存中存在脏页,但是此时数据库宕机,内存中的数据丢失了,也就是有一部分的修改没有持久化到磁盘,这就产生了数据不一致的问题
为了避免这个问题,InnoDB在修改内存数据页时,先把所有对数据页的修改操作专门写入一个日志文件,然后再修改内存中的数据
如果有不一致的数据,数据库在启动的时候,会从这个日志文件进行恢复操作。
-
那为什么是写日志,而不是直接写到DBFile文件中去呢?写日志文件和写数据文件有什么区别?
之前介绍了磁盘的结构,盘片是不断转动的
写日志文件是磁头遇到任何可写的区域都可以进行写操作,这是顺序IO
写数据文件需要遵循B+树的结构,需要做寻址,这叫随机IO,刷盘是随机IO的
Undo Log
先看定义:Undo log叫做撤销日志,记录了事务发生之前的数据状态,分为insert undo log 和 update undo log
如果修改出现了异常,可以使用undo log来进行回滚
Bin Log
binlog以事件的形式记录了所有的DDL和DML语句,可以用来做主从复制和数据恢复,它记录的是操作而不是逻辑值,属于逻辑日志
它的文件内容是可以追加的,没有固定大小限制
在开启了binlog功能的情况下,我们可以把binlog导出成sql语句,把所有操作重放一边,来实现数据的恢复
Double Write
场景:
如果后台线程正在刷脏页到磁盘,刷到一半的过程中,mysql宕机,此时内存里面的页数被清除了,而磁盘里的页数据,只刷了一半
此时磁盘中的数据是损坏的,redo log也不能回放
所以mysql在刷数据到磁盘之前,要先把数据写到另外一个地方,也就是dublewritte Buffer,写完后,再开始写磁盘。dubblewrite可以理解为是一个备份,万一真的发生crash,就可以利用doubleWrite来修复磁盘里的数据