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 相关文章推荐
MySQL创建索引需要了解的
Apr 08 MySQL
mysql如何能有效防止删库跑路
Oct 05 MySQL
Arthas排查Kubernetes中应用频繁挂掉重启异常
Feb 28 MySQL
MySQL去除密码登录告警的方法
Apr 20 MySQL
Mysql 数据库中的 redo log 和 binlog 写入策略
Apr 26 MySQL
sql查询语句之平均分、最高最低分及排序语句
May 30 MySQL
MySQL 语句执行顺序举例解析
Jun 05 MySQL
MySQL范围查询优化的场景实例详解
Jun 10 MySQL
delete in子查询不走索引问题分析
Jul 07 MySQL
一文解答什么是MySQL的回表
Aug 05 MySQL
数据设计之权限的实现
Aug 05 MySQL
MySQL存储过程及语法详解
Aug 05 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
Terran兵种介绍
2020/03/14 星际争霸
php中转义mysql语句的实现代码
2011/06/24 PHP
用PHP即时捕捉PHP中的错误并发送email通知的实现代码
2013/01/19 PHP
PHP的MVC模式实现原理分析(一相简单的MVC框架范例)
2014/04/29 PHP
php中ltrim()、rtrim()与trim()删除字符空格实例
2014/11/25 PHP
php实现上传图片保存到数据库的方法
2015/02/11 PHP
yii2 commands模式以及配置crontab定时任务的方法
2017/08/19 PHP
浅谈Javascript嵌套函数及闭包
2010/11/09 Javascript
jquery跟js初始化加载的多种方法及区别介绍
2014/04/02 Javascript
JavaScript中数组的22种方法必学(推荐)
2016/07/20 Javascript
老生常谈JavaScript中的this关键字
2016/10/01 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
2016/12/27 Javascript
ES6中的Promise代码详解
2017/10/09 Javascript
基于vue的换肤功能的示例代码
2017/10/10 Javascript
如何为vuex实现带参数的 getter和state.commit
2019/01/04 Javascript
详解Node.js异步处理的各种写法
2019/06/09 Javascript
js如何获取访问IP、地区、当前操作浏览器
2019/07/23 Javascript
vuex存储复杂参数(如对象数组等)刷新数据丢失的解决方法
2019/11/05 Javascript
JS实现随机抽取三人
2019/11/06 Javascript
React实现全选功能
2020/08/25 Javascript
Linux 发邮件磁盘空间监控(python)
2016/04/23 Python
Python图算法实例分析
2016/08/13 Python
Python从使用线程到使用async/await的深入讲解
2018/09/16 Python
pycharm 在windows上编辑代码用linux执行配置的方法
2018/10/27 Python
Python调用.NET库的方法步骤
2019/12/27 Python
Python实现从N个数中找到最大的K个数
2020/04/02 Python
UGG雪地靴德国官网:UGG德国
2016/11/19 全球购物
实体的生命周期
2013/08/31 面试题
体育教学随笔感言
2014/02/24 职场文书
四议两公开实施方案
2014/03/28 职场文书
安全生产月活动总结
2014/05/04 职场文书
产品生产计划书
2014/05/07 职场文书
党员评议表自我评价范文
2014/10/20 职场文书
2015年酒店客房部工作总结
2015/04/25 职场文书
JavaScript流程控制(分支)
2021/12/06 Javascript
MySQL生成千万测试数据以及遇到的问题
2022/08/05 MySQL