前置信息
1、sql interface : 接收 sql 命令用于执行,并将结果返回给用户
2、sql parser : sql查询解析器,分析sql的合法性,并尝试分解sql
3、sql optimizer : sql查询优化器,对SQL命令按照标准流程进行优化分析,选择最优查询方式
4、storage engine : 存储引擎
(整理流程图)
一、 Buffer Pool
上图中 chunk 是 Buffer Pool 中的物理存储块数组,一个 Buffer Pool 有多个 chunk,每
个chunk的大小默认为128MB,最小为1MB,且这个值在Mysql 8.0中可以动态调整生效的。 由于每次调整Buffer Pool的大小需要重新向操作系统申请一块连续的内存空间,然后将旧的Buffer Pool中的内容复制到这一块新空间,这种操作是极其耗时的。所以,InnoDB不再一次性为某个Buffer Pool实例向操作系统申请一大片连续的内存空间,而是以一个chunk为单位向操作系统申请空间。这样需要做动态调整大小的时候,就不用整个 Buffer Pool 复制,而是针对chunk进行增加或者删除
1、 缓存数据、脏数据(由于更改数据会先更改缓存数据,再未更改磁盘数据的时候,
缓存数据和磁盘数据不一致,此时的缓存数据就是脏数据)
2、 buffer池大小可利用 innodb_buffer_pool_size 参数根据实际情况做调整
3、 数据页
- 如上图,磁盘中一个数据页大小为16K,Mysql会将查询到的数据所属数据页从磁盘文件中 加载到 Buffer Pool 中,并以数据页的格式缓存,也就是缓存页,大小也是16K
4、 缓存页
- 每个缓存页对应一个描述信息,包含缓存页的位置、编号等,大小占缓存页 5% 左右。
- 缓存页在数据库启动的时候就会按照配置从操作系统申请一块内存给 Buffer Pool 以16K大小 一块一块划分出来
5、 free链表 (读取数据到 Buffer Pool 中,能知道哪些缓存页时空的)
- 数据结构:free链表是双向链表,每一个节点是一个空的缓存页的描述数据块地址。该链表 有一个基础节点,该节点记录了链表中存储多少节点和链表的首位节点地址,大小在40字节,但是 该节点不属于 Buffer Pool(可以理解为独立存在)
- 初始化的时候,所有缓存页都是空的,所有描述数据块都会存到这个表中
6、 数据页缓存哈希表
- 为了快速知道某个数据页是否已经被缓存,这里利用一个哈希表,将 表空间号 和 数据页号 作为key,缓存页作为value。每次利用key查询,有则直接使用,没有则走一遍缓存流程。
7、 flush链表
- 前面提到脏数据(脏页),脏页会刷回磁盘。而需要刷回磁盘的脏页会存入到flush链表中
- 数据结构:双向链表,存储脏页的描述数据块,也有一个基础节点(和free链表差不多)
8、 LRU链表(在往 Buffer Pool 中塞数据的时候,自然也会有数据从中被淘汰)
- 数据结构:双向链表,存储所有被缓存的数据页对应的描述数据块,也有一个基础节点。每
次被访问加载的缓存页会放在链表头部
- 缓存命中率:假如100次请求,40次命中某个缓存页而不需要从磁盘中获取数据,那么该缓
存页的缓存命中率则是 40%。而选择被淘汰掉的应该是缓存命中率低的(换个说法就是不怎么被访
问的缓存页)。
- 预读机制:Mysql在读取磁盘空间数据的时候,会将数据块附近的数据页一起读取加载到缓
存中。这样将多次小的IO读取操作合并为一次大的IO读取操作,减少了IO次数,提高了性能。但是
也可能导致不必要的IO操作,比如说被读取的数据并不会被使用,那么预读操作就是无用的磁盘
IO,降低了IO效率,并且当 Buffer Pool 内存不够用时,还可能会淘汰掉有用的缓存。
预读机制触发条件:
-- innodb_read_ahead_threshold 参数,默认阈值为56,且是关闭状态。
打开后,顺序访问一个区域的数据页数量超过了这个阈值,就会触发
-- 如果 Buffer Pool 里缓存了一个区域的13个连续的数据页,且这些数据页经常被访问,也会触发
- 基于冷热数据分离思想设计的LRU链表
1、基本的LRU链表问题:
-- Mysql的预读机制可能会把一些用不上的数据也加载到LRU头部去了,淘汰掉一些
常用的缓存页
-- 全表查询将所有数据都从磁盘中加载到缓存中来占取缓存页,之前经常访问的缓存
页就会被排到尾部被淘汰掉
2、为了避免上述尴尬场景,Mysql的LRU分为冷热数据区,由参数innodb_old_blocks_pct
控制,默认是37,即冷数据区占总的37%,如下图
3、冷数据区的数据何时会到热数据区?
-- 由参数 innodb_old_blocks_time 决定,默认值为1000,即1000毫秒。即
在一个数据页加载到缓存页之后再隔一秒后再被访问,就会被挪动到热数据区头部去
4、Mysql对LRU的热数据区的优化
-- 关于热数据区的数据,会被经常访问,为了避免经常访问的数据频繁移动到链表
头部,造成不必要的消耗,热数据区前四分之一的数据即便被访问了,也不会被挪动,
只有后四分之三的数据被访问了,才会挪到热数据头部
5、LRU尾部数据何时被刷盘
-- Mysql有后台线程定时将 LRU 冷数据区尾部的部分数据刷入磁盘,同时对应的
缓存页会加入到 free 链表中,然后从flush表中清除
-- 后台线程也会有时机将flush链表中的数据刷入到磁盘中,并清除LRU中对应的
缓存页的数据,然后加入到free链表中 (LRU中的数据还可能被频繁修改)
-- 缓存页不够用了(虽然有后台线程,但不能保证它清理数据的速度比得上加载数据的速度),
此时free链表空了,flush链表有一堆的数据需要刷入磁盘,LRU链表中也有一堆数据。
这个时候再加载数据到缓存中,会从 LRU 冷数据区挑选最不经常使用的缓存页被淘汰掉
二、 Redo Log(重写日志,属于InnoDB引擎,偏物理)
1、redo log包括两部分:
- 日志缓冲(redo log buffer,易丢失)
服务器启动时会向操作系统申请redo log buffer的连续内存空间,
其为若干redo log block连续组成,每个block为512字节。innodb_log_buffer_size
可以设置buffer大小
通过 innodb_log_buffer_size 参数进行大小设置
- 重做日志文件(redo log file,持久)
1、由整体流程图可知,在修改Buffer Pool的数据后,会生成一条记录变更后的值的redo log,
写入到redo log buffer中
2、在事务提交的时候会将内容追加到磁盘的redo log file中
3、当事务commit时,会将redo log buffer中的内容以追加写的方式刷到磁盘的
redo log file中(顺序写)
4、之后便是将缓存数据刷新到磁盘中后,在redo log中打上commit的标记
2、InnoDB 引擎会在适当的时候(空闲),将redolog中记录的操作更新到表中数据对应的page页所在的物理磁盘上。更新redolog日志中的内容到真正的表数据对应的page页的刷盘操作通常比费时的,
属于随机IO,写入性能差。所以这里会先写日志,再写磁盘,这就是很多软件在提高写的性能的时候所使用的WAL(write ahead logging)预写日志机制
3、写入到 Redo Log Buffer 之后,此时提交事务,就会根据 innodb_flush_log_at_trx_commit 参数所配置的策略将 Redo Log Buffer 中的数据刷入到磁盘 (Redo Log)
innodb_flush_log_at_trx_commit:
(1)、为0,提交事务不会将Buffer数据刷入磁盘,此时数据可能丢失
此种情况由系统默认线程定时将数据刷入文件系统缓存,并调用fsync同步数据。
对于未提交的事务,写入文件系统缓存后并不会调用fsync
(2)、为1,提交事务就会将Buffer数据刷入到磁盘,事务成功后不会丢失数据
(3)、为2,提交事务之后先刷入到系统缓存中,之后再刷入磁盘,数据也可能丢失
三、Bin Log (归档日志,属于Mysql Server,偏逻辑)
Bin Log记录所有对数据库表结构变更和表数据修改的操作,主要用于数据恢复、主从复制。Bin Log日志包含三种格式:
- Statement-Based Replication (SBR、statement) 模式,记录的是SQL语句
优点:日志量较小。复现简单,对于大规模数据的变更,复制负载相对较轻,执行速度快
缺点:对于使用了函数或随机数的 SQL 语句,可能会出现不一致的情况,比如uuid()。
在高并发情况下,主从服务的执行计划可能不同会导致效率低下
- Row-Based Replication (RBR、row) 模式,记录的是数据行的变化情况
优点:确保数据完整性和一致性,避免了SBR中可能出现的复制一致性问题
缺点:日志量较大,占用空间较大、复制速度比较慢,增加主服务器的负担、增加网络传输负担
- Mixed-Format Replication (mixed) 模式,是 statement 模式和 row 模式的混合模式
优点:结合了SBR和RBR的优点,根据具体的情况自动选择使用哪种模式,尽量减少日志大小
的同时最大程度地做到主从复制一致性
缺点:配置和维护相对复杂
1、 在准备提交事务的时候,会将这一次更新的数据对应的 Bin Log 刷入到磁盘
1、事务执行时,Mysql会为其在内存中分配一块bin log cache,事务提交时会把bin log cache
写到bin log文件中,此时cache的内容会被清空。
2、一个事务的bin log不能被拆开提交,无论这个事务多大,也要确保一次性写入,这样才能保证原子性。
3、如果bin log cache空间满了,会将数据暂存入一个临时文件,并清空cache。如果过于庞大以至于
超过 max_binlog_cache_size 参数配置数值,会抛异常。在事务提交时,cache和临时文件都会被清空,
将数据写入到bin log file中
2、 刷盘策略对应参数:sync_binlog
sync_binlog:
(1)、为0,Mysql不做同步操作,数据先到系统缓存,再刷入磁盘,数据可能丢失
(2)、为1,每提交一次事务会执行一次fsync的磁盘同步指令将Binlog数据刷入磁盘
(3)、为n,每提交n次事务会执行一次fsync的磁盘同步指令将Binlog数据刷入磁盘
上述三种情况,1效率最高但最不安全,2最安全但效率最低,3处于中间
3、 写入 Bin Log 后,会将 Bin Log 日志文件名以及文件的修改位置写入到 Redo Log 中,并在 Redo Log 中写入 commit 表示事务完成。有 commit 标志可以保证 Bin Log 和 Redo Log 数据一致
4、 写入 commit 之后,会有后台IO线程随机将 Buffer Pool 中的数据刷入到磁盘,之后缓存和磁盘数据就一致了
四、 Undo Log
多个事务对缓存页的同一条数据进行查询更新时会涉及到 脏写、脏读、不可重复读、幻读 四个问题,常规解决如下:
1、read uncommitted 读未提交
排除脏写的问题
2、read committed 读并提交 (RC)
排除脏读和脏写的问题
3、repeatable read 可重复读 (RR)
排除脏读、脏写和不可重复读的问题
4、serializable 串行化
排除全部问题,也就说所有事务只能串行执行,不允许你并行执行,伴随的是性能极差
Mysql在默认RR隔离级别已经排除了幻读的问题
1、Undo Log版本链
数据库中每条数据有两个隐藏字段:trx_id、roll_pointer
trx_id :最近更新当前数据的事务的id
roll_pointer :指向最近更新数据之前的生成的undo log
2、ReadView 机制
每次执行一个事务,都会生成一个ReadView,其中比较关键的四个字段为:
min_trx_id:m_ids中最小的id
max_trx_id:mysql即将生成的事务id(也是生成时最大的id)
creator_trx_id:当前事务的id
3、Mysql RC 基于 ReadView 机制实现
RC级别下的事务,每次查询都会重新生成一个ReadView
4、Mysql RR 基于 ReadView 机制实现
RR级别下的事务,一旦生成一个ReadView就不会再变,直到当前事务提交