这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
一、在磁盘中如何存放一行数据
mysql表中一行数据的结构由以下几个部分组成:
- 变长字段长度列表:如果要从磁盘读取第一行数据,首先要知道读取数据的长度,才能把数据在磁盘中给完整读取出来;这行数据有一个变长字段的类型为varchar(10),变长字段的长度是可能会变化的,导致一行数据的长度并不是固定的,mysql在设计每行数据结构时引入可变字段长度列表,对于每行数据,会把varchar类型字段的长度记录到变长字段长度列表中
变长字段长度列表的大小是用16进制形式存放的,如果有多个字段为varchar类型,就会按照,以16进制的格式逆序存放到变长字段长度列表中
读取数据时,扫描到第一个字段是变长字段类型varchar,就先到变长字段长度列表看下字段的长度,然后根据读取到的字段长度依次读取数据,然后接下来是char char都是定长的字段类型,直接分别读取一位,这样一条数据就顺序读取到了。
- null值列表:一行数据的字段不光会出现变长字段,也可能存在null值的情况,这些可以为null的字段它们的长度也不是固定的,只要字段长度不是固定的,就会造成预估要读取的数据长度不确定。
设计null值列表,用一个bit位来标记字段是否为null,1:表示字段为null值,0:表示字段不为null值,如一行数据为 null a a 对应的null值列表应该为[100],和变长字段长度列表的逆序排列特征⼀样,null值列表逆序存放就为[001],但是一般至少也是8个bit位即一个字节的大小,所以前面补全就是[00000001]\
引入null值列表之后,如果读取一行数据时,某个字段可以为null,就先从null值列表中获取该字段对应的bit位,bit位为1字段的值就为null,跳过不读取,bit位为0则字段的值不为null,此时该字段又为varchar字段,再从变长字段长度列表得到字段的长度,完整的读取数据,这样一行数据中不确定的两个因素:varchar字段、null值,就通过引入变长字段列表和null值列表解决了。
- 数据头:一行数据的数据头用来描述该行数据的,数据头由40个bit位组成。
引入数据头之后,一行数据在磁盘中的大概如下 :
- 实际数据:会先解析成字符编码的样式存放
实际数据中有几个隐藏字段分别如下:
-
DB_ROW_ID:表示行的唯一标识,一般情况都是主键,如果没主键会自动生成一个,目的是唯一标记该行数据
-
DB_TRX_ID:事务ID,记录最近一次被那个事务修改操作过,该标记在MVCC机制有非常大的作用
二、一行数据在数据页中的存储
数据页的数据存储模型:
当数据页中没有数据时,数据区几乎不占内存,随着一行行数据进入数据页,空闲区的内存被耗完,一个数据页的内存大小为16kb,如果一行数据中的一个字段的大小,它占的内存远远大于一个数据页的16kb大小时,此时一个数据页存放不下,会让多个数据页共同存放这行数据,原来的数据页先存放一部分数据,然后用20个字节的一个指针指向其他共同存放该字段数据的其他数据页,共同参与数据存放的数据页被称为溢出页。
数据页在表空间中的划分和存储
数据表只是一个逻辑上的一个概念,在实际的物理存储上,mysql innodb存储引擎的设计中,一张表则对应一个表空间,同时一个表空间就映射到磁盘中的一个表空间文件,表空间文件以.ibd为文件后缀。
而数据页存放在表空间中,但一个数据页才16kb大小,大量的数据页堆在表空间不好管理,引入数据区的概念:一个数据区中可以存放64个数据页即一个数据页大小为1MB大小。
256个数据区可以被划分一个数据区组,多个数据区组形成了表空间的整体数据结构;
经过以上对一行数据、一个数据页、表空间的分析,一个整体流程图如下:
\
redo log 和 undo log的数据存储模型
redo log引入redo log block来存放一条一条的redo log日志,每条redo log日志都是先存放在redo log block的body区中。
对于一个事务,一般都会有多条redo log日志信息,多条redo log 日志信息存放在512字节的redo log block中的body中,当body中的496字节的内存存满后就存放下一个redo log block,然后这些redo log block会先存放在内存中的redo log buffer缓存中,默认大小为16MB,可通过参数innodb_log_buffer_size设置,然后到一定的时机再刷到磁盘文件中,redo log日志文件对应磁盘mysql安装目录下的ib_logfile0和ib_logfile1两个文件,默认只有两个文件,每个文件大小上限都为48MB,写完一个就写下一个循环着,同一时刻最多只能存放96MB的redo log日志信息。
对于redo log刷盘时机,一般有以下四种情况:
- 如果一次性redo log buffer的内存超过一半,默认超过8MB:该情况下主要发生在高并发场景,大量的事务执行导致一下子redo log buffer内存过大,直接导致刷盘
- 提交事务的情况下会刷盘:对于事务的提交,只有依赖redo log刷盘持久化才能保障事务的可靠性
- 后台线程定时redo log buffer刷盘:类似buffer pool链表刷盘一样,定时会有后台线程将redo log block数据顺序写入磁盘中
- mysql停止服务时触发刷盘