Mysql InnoDB 的内存逻辑架构


Posted in MySQL onMay 06, 2022

1 前言

我们都熟悉mysql数据库服务架构,也清楚 sql 的执行顺序,mysql的数据在磁盘和内存中的存储结构是采用B+树的数据结构,但是在InnoDB引擎中,数据在内存和磁盘中的展示形式以及怎么和mysql的服务架构建立联系,sql 查询和 InnoDB 引擎之前的联系,可能就不是不清楚了。

mysql 的逻辑架构图如下所示: 

Mysql InnoDB 的内存逻辑架构

2 InnoDB 存储引擎结构

InnoDB存储引擎的逻辑存储结构是什么呢,其实所有的数据都被逻辑地放在了一个空间中这个空间中的文件就是实际存在的物理文件,即表空间。默认情况下,一个数据库表占用一个表空间,表空间中存放该表对应的数据、索引、insert buffer bitmap undo信息、insert buffer 索引页、double write buffer 等是放在共享表空间中的。

# 默认一个数据库表单独占有一个表空间
show variables like '%innodb_file_per_table%'
innodb_file_per_table=ON
# 修改设置
SET GLOBAL innodb_file_per_table=OFF;

2.1 InnoDB表存储引擎文件

Mysql InnoDB 的内存逻辑架构

 每个表空间由 段 segment 区 extent 页 page 组成。页是数据存储数据的基本单位,默认大小为 16kb。 区是由连续页组成的空间,默认大小为 1MB。多个区构成表的段。 InnoDB 逻辑存储结构 

Mysql InnoDB 的内存逻辑架构

在我们执行sql时,不论是查询还是修改,myql 总会把数据从磁盘读取内内存中,而且在读取数据时,不会单独加在一条数据,而是直接加载数据所在的数据页到内存中,而读取的方式有两种,现行预读方式和随机预读方式,默认采用线性预读方式。

InnoDB 引擎架构 :

Mysql InnoDB 的内存逻辑架构

2.2 InnoDB 预读机制

线性预读和随机预读:

线性预读是以 extent 为单位,而随机预读是以 extent 中的page 为单位,线性预读着眼于将下一个extent 数据读取到 buffer pool 中,而随机预读是将当前extent中剩余的page读到 buffer pool 中。 如果一个extent 区中被顺序读取得page数量超过一定的数量( innodb_read_ahead_threshold),则直接加载 extent 中剩余的数据页。

2.3 InnoDB 特性

2.3.1 插入缓存

插入缓冲(Insert Buffer/Change Buffer)为了提升插入性能,insert buffer 是 insert buffer 的增强版,insert buffer 只对插入有效,而change buffer对 insert/update/delete 都有效。插入缓存只对非唯一索引和辅助索引有效,对每一次的插入不是写到索引页中,而是先判断插入的非聚集索引页是否在缓存中,如果在则直接插入,不存在则插入到 insert buffer 中,按照一定的频率进行合并操作,写回到磁盘。这样将多个插入操作合并进一个操作中,目的是为了减少随机IO带来的性能损耗。

2.3.2 二次写 (double write)

插入缓存给 InnoDB 存储引擎带来了性能上的提升,而 double write 则是保障 InnoDB 存储引擎操作数据页的可靠性。double write 分为两部分组成,一部分在内存中的 double write buffer, 大小为 2MB,另一部分是物理磁盘上共享表空间中连续的128个数据页,即2个区大小(同样是2MB)。在对缓冲池的脏页进行刷新时,并不是直接写磁盘,而是通过 memcpy 函数将脏页复制到内存中的 doublewrite buffer,之后通过doublewrite buffer 在分两次,每次1MB 顺序地写入共享表空间的物理磁盘上,然后马上调用 fsync 将数据同步至磁盘。由于doublewrite 是连续的空间,这样的顺序写IO开销不大。在doublewrite页写完后,再次离散写入各个表空间。如果操作系统在将数据页写入磁盘发生崩溃,那么在恢复的过程中,InnoDB 引擎会从共享表空间中的doublewrite找到该页的一个副本,将其复制到表空间文件,再应用重做日志。

Mysql InnoDB 的内存逻辑架构

2.3.3 自适应hash索引

hash是一种等值查询,InnoDB 存储引擎会监控对表上各个索引页的查询,如果观察到建立hash索引会带来速度提升,则建立相应的索引,因此称为自适应哈希索引(Adaptive Hash Index,AHI)。AHI是通过缓冲池中的B+树页构造而来,建立速度比较快,而且不需要对整张表建立哈希索引,只是建立热点页的索引。AHI默认是开启的状态。

2.3.4 异步IO

为了提高磁盘的操作性能, 当前的数据库系统一般采用异步IO(Asynchronous IO,AIO)的方式来处理磁盘操作,InnoDB 存储引擎也是如此,AIO的优势在于减少SQL查询需要的时间,另外也可以进行IO Merge 操作,就是将多个IO合并为1个IO,这样就可以提高IOPS的性能。

# 开启本地 AIO
show valiables like 'innodb_use_native_aio';

2.3.5 刷新邻接页

InnoDB 存储引擎提供了 Flush Neighbor Page(刷新邻接页)的特性,当刷新一个脏页时,InnoDB 存储引擎会检测该区内是否存在其它脏页,如果存在,则一并进行刷新,这样做得好处显而易见,可以将多个操作合并成一个操作,对于机械硬盘有着明显的优势,但对于固定硬盘,本事就有较高的IOPS,是否开启需要根据情况而定,

参数设置如下:

show varables like 'innodb_flush_neighbors'

3 sql 执行的逻辑

3.1 sql 执行

Mysql InnoDB 的内存逻辑架构

mysql写文件有2块缓存。一块是自己定义在内存的log buffer, 另一个是磁盘映射到内存的os cache。 mysql可以 调用 flush 主动将log buffer 刷新到磁盘内存映射,也可以调用 fsync 强制操作系同步磁盘映射文件到磁盘。默认情况下innodb_flush_log_at_trx_commit和sync_binlog 配置都为1。

不仅InnoDB引擎中有 buffer 的概念,这个是在用户空间中,而且在内核空间中也有 OS buffer的概念 

Mysql InnoDB 的内存逻辑架构

 还可以同时调用 flush + fsync, 将缓存直接落盘。
innodb_flush_log_at_trx_commit = 0就是每秒调用 flush + fsync ,定时器自己维护。
innodb_flush_log_at_trx_commit = 1就是实时调用 flush + fsync 没法批处理,性能很低。
innodb_flush_log_at_trx_commit = 2就是实时flush ,定时 fsync 交给OS维护定时器。
sync_binlog 配置
等于0:表示每次提交事务只write不fsync
等于1:表示每次提交事务都执行fsync
等于n:表示事务在write后,会累积N个事务后才fsync。

show variables like 'sync_binlog';
show variables like 'innodb_flush_log_at_trx_commit';
# 查看 mysql 正在执行的进程 
show processlist

InnoDB引擎BufferPool、LogBuffer、OS Buffer、Log files 之间的关系。 

Mysql InnoDB 的内存逻辑架构

mysql 在执行增删改sql时,InnoDB 引擎的执行步骤如下:

  • 1 执行器拿到需要执行的sql,需要根据更新条件从磁盘中加载需要修改的数据到内存中,也就是存放在 buffer pool 中。
  • 2 在修改对应的数据之前,需要将其数据进行备份,也就是将数据放进 undo log 中,方便在事务回滚时进行操作。
  • 3 直接在内存中按照sql语句修改对应的值。
  • 4 修改完后将按照修改后的数据写 redo log buffer。
  • 5 将 redo log 的内容进行写盘操作,这一步的操作参见 innodb_flush_at_trx_commit 的配置,一般是先写入系统的缓存中,然后由操作系统DMA异步操作写入系统文件中。 flush 操作只是把系统内存中的数据写入操作系统的缓冲中,数据读写一般是由内核线程完成的,这一步是数据从用户线程转变成内核线程进行操作,在读写文件时,在磁盘文件和内存之间会有多级缓存,用于提高数据交换效率,这里的 os cache 起到的就是这个作用。
  • 6 在写完redo log 后,然后进行 bin log 写入操作。
  • 7 和 redo log 的操作类似,也是先写入 os cache 再有操作系统刷到磁盘文件中。sync_log 的配置如图所示。一般情况下,数据库innodb_flush_at_trx_commit 和 sync_log 配置都为 1。
  • 8 在 redo log 和 bin log 写完后,就可以进行事务提交。在数据进行写盘操作时, InnoDB 采用两次写的方式进行写数据。

先写redo log 再写 bin log的原因: 由于mysql 是通过 bin log 进行复制传输的,如果先提交了 redo log,还没有写bin log时出现了宕机,mysql 实例恢复时根据 redo log进行恢复,就会造成 从库和主库之间的数据不一致。

二进制日志文件的记录格式为 statement、row 和 mixed,statement 模式就是直接执行sql,如果其中有函数操作(比如数据库时间设置为 now() )那就会造成数据不准确。row 模式就是同步所有行的数据,如果全表操作修改状态,那这种模式就不合适了,因此在数据同步时需要根据情况采用 mixed 的混合模式。

3.2 FreeList、LRU List 和 Flush List

Free List 空闲列表:

记录所有未被占用的数据页,按照顺序将加载到内存的数据放入buffer pool 中,并删除对应 Free List 中的节点

LRU List LRU 数据访问列表:

将冷热数据块连接起来,根据 LRU 算法进行维护。如果加载进内存的数据一次性放入列表头部,再不确定这批数据的热度情况下,会造成一部分数据的淘汰,mysql InnoDB 的做法是将数据放置在靠后的位置,如果数据在1s内被访问了,才能进入链表头部,即数据热区。

# 将新加载的数据放置在链表的位置 默认为 37 即5/8处,
show variables like 'innodb_old_blocks_pct';
# 冷区数据间隔多久访问才放入链表的热端,默认为1000ms
show variables like 'innodb_old_blocks_time';

Flush List 刷新脏页列表:

记录内存中修改的数据页,使用双向链表进行连接,在方便的时候做落盘操作。

Mysql InnoDB 的内存逻辑架构

InnoDB 中的 redo log 大小是固定的,在MYSQL数据库Innodb 引擎mvcc锁实现原理 中提到过 redo log 是保证事务持久性的,其文件个数也是可以根据需要进行配置,通过循环写文件的方式来实现的,当 write pos 追赶上 checkpoint 后,这个时候就不能再继续执行新的命令,需要把check point 往前推进,也就是把redo log 里的内容持久化,腾出空间继续写日志。

数据操作:

Mysql InnoDB 的内存逻辑架构

redo log buffer 循环写入 :

Mysql InnoDB 的内存逻辑架构

这里先写日志再写磁盘的关键点也是一个技术,Write-Ahead Logging(WAL技术)。

关于 redo log 的配置可以参见如下命令执行查看。

show variables like '%innodb_log%'
------- 执行结果 ------
innodb_log_buffer_size	16777216
innodb_log_checksums	ON
innodb_log_compressed_pages	ON
innodb_log_file_size	50331648
innodb_log_files_in_group	2
innodb_log_group_home_dir	./
innodb_log_write_ahead_size	8192
innodb_log_buffer_size 为内存中 redo log buffer 的大小,16777216/1024/1024=16MB
innodb_log_file_size 为每个redo log 的大小,50331648/1024/1024=48MB
innodb_log_files_in_group 为 redo log 文件组中文件的个数,默认为2个 
查看数据库表状态
show table status like 'my_table';

到此这篇关于Mysql InnoDB 的内存逻辑架构的文章就介绍到这了!


Tags in this post...

MySQL 相关文章推荐
Navicat for MySQL的使用教程详解
May 27 MySQL
MySQL系列之二 多实例配置
Jul 02 MySQL
为什么MySQL选择Repeatable Read作为默认隔离级别
Jul 26 MySQL
MySQL实例精讲单行函数以及字符数学日期流程控制
Oct 15 MySQL
mysql主从复制的实现步骤
Oct 24 MySQL
全面盘点MySQL中的那些重要日志文件
Nov 27 MySQL
MYSQL如何查看进程和kill进程
Mar 13 MySQL
一条 SQL 语句执行过程
Mar 17 MySQL
为什么MySQL不建议使用SELECT *
Apr 03 MySQL
Mysql中常用的join连接方式
May 11 MySQL
Mysql数据库group by原理详解
Jul 07 MySQL
MySQL分布式恢复进阶
Jul 23 MySQL
详细介绍MySQL中limit和offset的用法
May 06 #MySQL
MySQL数据库Innodb 引擎实现mvcc锁
May 06 #MySQL
讲解MySQL增删改操作
May 06 #MySQL
解决Mysql报错 Table 'mysql.user' doesn't exist
MYSQL常用函数介绍
May 05 #MySQL
MySQL 数据 data 基本操作
May 04 #MySQL
MySQL 字符集 character
May 04 #MySQL
You might like
两种php调用Java对象的方法
2006/10/09 PHP
php str_pad 函数使用详解
2009/01/13 PHP
PHP乱码问题,UTF-8乱码常见问题小结
2012/04/09 PHP
PHP实现图片的等比缩放和Logo水印功能示例
2017/05/04 PHP
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
JavaScript 继承机制的实现(待续)
2010/05/18 Javascript
js实现的点击数量加一可操作数据库
2014/05/09 Javascript
jquery实现勾选复选框触发事件给input赋值
2015/02/01 Javascript
jquery html5 视频播放控制代码
2016/11/06 Javascript
jQuery中的select操作详解
2016/11/29 Javascript
JavaScript实现图片拖曳效果
2017/09/08 Javascript
windows下更新npm和node的方法
2017/11/30 Javascript
OkHttp踩坑随笔为何 response.body().string() 只能调用一次
2018/01/08 Javascript
JS中用EL表达式获取上下文参数值的方法
2018/03/28 Javascript
Vue项目中跨域问题解决方案
2018/06/05 Javascript
Vue实现调节窗口大小时触发事件动态调节更新组件尺寸的方法
2018/09/15 Javascript
微信小程序之swiper滑动面板用法示例
2018/12/04 Javascript
深入剖析JavaScript instanceof 运算符
2019/06/14 Javascript
layer弹出层倒计时关闭的实现方法
2019/09/27 Javascript
基于Nuxt.js项目的服务端性能优化与错误检测(容错处理)
2019/10/23 Javascript
使用uni-app开发微信小程序的实现
2019/12/13 Javascript
nuxt.js写项目时增加错误提示页面操作
2020/11/05 Javascript
[05:17]DOTA2睡衣妹卖萌求签名 CJ第二天全明星影像
2013/07/28 DOTA
Python 私有函数的实例详解
2017/09/11 Python
python线程中同步锁详解
2018/04/27 Python
django+xadmin+djcelery实现后台管理定时任务
2018/08/14 Python
python得到windows自启动列表的方法
2018/10/14 Python
python:接口间数据传递与调用方法
2018/12/17 Python
Python基本类型的连接组合和互相转换方式(13种)
2019/12/16 Python
CSS3属性background-size使用指南
2014/12/09 HTML / CSS
html5画布旋转效果示例
2014/01/27 HTML / CSS
澳大利亚排名第一的露营和户外设备在线零售商:Outbax
2020/05/06 全球购物
公安交警中队队长个人对照检查材料思想汇报
2014/10/05 职场文书
2015年反洗钱工作总结
2015/04/25 职场文书
评奖评优个人先进事迹材料
2015/11/04 职场文书
2016年感恩教师节活动总结
2016/04/01 职场文书