pull/1/head
595208882@qq.com 3 years ago
parent 32805ac981
commit 4604e050ba

@ -1493,70 +1493,49 @@ MySQL 5.7 版本以后,支持设置多个刷脏页线程,提高脏页处理
## 数据页 ## 数据页
数据页主要是用来存储表中记录的,它在磁盘中是用双向链表相连的,方便查找,能够非常快速得从一个数据页,定位到另一个数据页。 数据页主要是用来存储表中记录的,它在磁盘中是用双向链表相连的,方便查找,能够非常快速得从一个数据页,定位到另一个数据页。通常情况下,单个数据页默认的大小是`16kb`。当然也可以通过参数 `innodb_page_size` 来重新设置大小。不过,一般情况下,用它的默认值就够了。单个数据页包含内容如下:
- 写操作时,先将数据写到内存的某个批次中,然后再将该批次的数据一次性刷到磁盘上。如下图所示: ![InnoDB页结构示意图](images/Database/InnoDB页结构示意图.jpg)
![InnoDB-数据页-写操作](images/Database/InnoDB-数据页-写操作.jpg) ![InnoDB页各组成部分简单描述](images/Database/InnoDB页各组成部分简单描述.jpg)
- 读操作时,从磁盘上一次读一批数据,然后加载到内存当中,以后就在内存中操作。如下图所示: ### File Header(文件头)
![InnoDB-数据页-读操作](images/Database/InnoDB-数据页-读操作.jpg) **用于记录页Page的信息**,如页类型、上一页和下一页等,占固定的 `38byte`。重要字段结构如下:
磁盘中各数据页的整体结构如下图所示: - `FIL_PAGE_SPACE_OR_CHKSUM`**校验和**。为了快速比较、保证数据的完整性防止遭到破坏等
![InnoDB-数据页](images/Database/InnoDB-数据页.jpg) - `FIL_PAGE_OFFSET`**页号**。InnoDB通过页号来可以唯一定位一个页
通常情况下,单个数据页默认的大小是`16kb`。当然,我们也可以通过参数:`innodb_page_size`,来重新设置大小。不过,一般情况下,用它的默认值就够了。单个数据页包含内容如下: - `FIL_PAGE_TYPE`**页的类型**。InnoDB为了不同的目的而把页分为不同的类型
![InnoDB-单个数据页内容](images/Database/InnoDB-单个数据页内容.jpg) - `FIL_PAGE_PREV、FIL_PAGE_NEXT`:分别指向**上一页**和**下一页**
### 文件头部
通过前面介绍的行记录中`下一条记录的位置`和`页目录`innodb能非常快速的定位某一条记录。但有个前提条件就是用户记录必须在同一个数据页当中。
如果用户记录非常多,在第一个数据页找不到我们想要的数据,需要到另外一页找该怎么办呢?这时就需要使用`文件头部`了。它里面包含了多个信息但我只列出了其中4个最关键的信息 ### Page Header(页头)
- 页号 **存储页内的一些状态和汇总信息**,如本页有多少条记录等,占固定的 `56byte`。重要字段结构如下:
- 上一页页号
- 下一页页号
- 页类型
顾名思义innodb是通过页号、上一页页号和下一页页号来串联不同数据页的。如下图所示 - `PAGE_N_DIR_SLOTS`**页内槽的个数**,其占用`2byte`。新建空数据页初值为2分别指向Infimum最小记录、Supremum最大记录
![InnoDB-文件头部](images/Database/InnoDB-文件头部.jpg) - `PAGE_HEAP_TOP`**第一条记录地址**
不同的数据页之间通过上一页页号和下一页页号构成了双向链表。这样就能从前向后一页页查找所有的数据了。此外页类型也是一个非常重要的字段它包含了多种类型其中比较出名的有数据页、索引页目录项页、溢出页、undo日志页等。 - `PAGE_N_HEAP`**页内记录数**,含最大最小记录及标记删除的记录
### 页头部 ### Infimun+Supremum Records
比如一页数据到底保存了多条记录,或者页目录到底使用了多个槽等。这些信息是实时统计,还是事先统计好了,保存到某个地方?为了性能考虑,上面的这些统计数据,当然是先统计好,保存到一个地方。后面需要用到该数据时,再读取出来会更好。这个保存统计数据的地方,就是`页头部`。当然页头部不仅仅只保存:槽的数量、记录条数等信息。它还记录了: **为了快速找到最大或最小记录在保存用户记录时数据库会自动创建两条额外的记录最大记录保存到Supremum记录中最小记录保存在Infimum记录中**。如下图所示:
- 已删除记录所占的字节数
- 最后插入记录的位置
- 最大事务id
- 索引id
- 索引层级
### 最大和最小记录
在一个数据页当中,如果存在多条用户记录,它们是通过`下一条记录的位置`相连的。不过有个问题如果才能快速找到最大的记录和最小的记录呢这就需要在保存用户记录的同时也保存最大和最小记录了。最大记录保存到Supremum记录中。最小记录保存在Infimum记录中。
在保存用户记录时数据库会自动创建两条额外的记录Supremum 和 Infimum。它们之间的关系如下图所示
![InnoDB-最大和最小记录](images/Database/InnoDB-最大和最小记录.jpg) ![InnoDB-最大和最小记录](images/Database/InnoDB-最大和最小记录.jpg)
从图中可以看出用户数据是从最小记录开始,通过下一条记录的位置,从小到大,一步步查找,最后找到最大记录为止。
### 用户记录 ### User Records(用户记录)
对于新申请的数据页,用户记录是空的。当插入数据时,innodb会将一部分`空闲空间`分配给用户记录。用户记录是innodb的重中之重我们平时保存到数据库中的数据,就存储在它里面。其实在innodb支持的数据行格式有四种 **用来存储用户插入的数据记录**。对于新申请的数据页用户记录是空的。当插入数据时Innodb会将一部分`空闲空间`分配给用户记录。我们平时保存到数据库中的数据就存储在它里面。Innodb支持的数据行格式有四种
- compact行格式 - compact行格式
- redundant行格式 - redundant行格式
@ -1571,27 +1550,9 @@ MySQL 5.7 版本以后,支持设置多个刷脏页线程,提高脏页处理
一条用户记录主要包含三部分内容: 一条用户记录主要包含三部分内容:
- 记录额外信息它包含了变长字段、null值列表和记录头信息 - **记录额外信息**它包含了变长字段、null值列表和记录头信息
- 隐藏列它包含了行id、事务id和回滚点
- 真正的数据列:包含真正的用户数据,可以有很多列
#### 额外信息
额外信息并非真正的用户数据,它是为了辅助存数据用的。
- **变长字段列表**
有些数据如果直接存会有问题比如如果某个字段是varchar或text类型它的长度不固定可以根据存入数据的长度不同而随之变化。如果不在一个地方记录数据真正的长度innodb很可能不知道要分配多少空间。假如都按某个固定长度分配空间但实际数据又没占多少空间岂不是会浪费所以需要在变长字段中记录某个变长字段占用的字节数方便按需分配空间。
- **null值列表**
数据库中有些字段的值允许为null如果把每个字段的null值都保存到用户记录中显然有些浪费存储空间。有没有办法只简单的标记一下不存储实际的null值呢答案将为null的字段保存到null值列表。在列表中用二进制的值1表示该字段允许为null用0表示不允许为null。它只占用了1位就能表示某个字符是否为null确实可以节省很多存储空间。 **记录头信息**用于描述一些特殊的属性。它主要包含:
- **记录头信息**
记录头信息用于描述一些特殊的属性。它主要包含:
- deleted_flag即删除标记用于标记该记录是否被删除了 - deleted_flag即删除标记用于标记该记录是否被删除了
- min_rec_flag即最小目录标记它是非叶子节点中的最小目录标记 - min_rec_flag即最小目录标记它是非叶子节点中的最小目录标记
@ -1600,51 +1561,43 @@ MySQL 5.7 版本以后,支持设置多个刷脏页线程,提高脏页处理
- record_type即记录类型其中0表示普通记录1表示非叶子节点2表示Infrimum记录 3表示Supremum记录 - record_type即记录类型其中0表示普通记录1表示非叶子节点2表示Infrimum记录 3表示Supremum记录
- next_record即下一条记录的位置 - next_record即下一条记录的位置
- **隐藏列**它包含了行id(`db_row_id`)、事务id(`db_trx_id`)和回滚点(`db_roll_ptr`)
- **真正的数据列**:包含真正的用户数据,可以有很多列
#### 隐藏列
数据库在保存一条用户记录时,会自动创建一些隐藏列。如下图所示:
![InnoDB-隐藏列](images/Database/InnoDB-隐藏列.jpg)
目前innodb自动创建的隐藏列有三种
- db_row_id即行id它是一条记录的唯一标识。 ### Free Space(空闲空间)
- db_trx_id即事务id它是事务的唯一标识。
- db_roll_ptr即回滚点它用于事务回滚。
如果表中有主键则用主键做行id无需额外创建。如果表中没有主键假如有不为null的unique唯一键则用它做为行id同样无需额外创建。如果表中既没有主键又没有唯一键则数据库会自动创建行id。也就是说在innodb中隐藏列中`事务id`和`回滚点`是一定会被创建的但行id要根据实际情况决定 **为页面的剩余空间**。User Records部分从上往下使用剩余空间而Page Directory则从下往上使用剩余空间。
![FreeSpace剩余空间](images/Database/FreeSpace剩余空间.jpg)
#### 真正数据列
真正的数据列中存储了用户的真实数据,它可以包含很多列的数据。 ### Page Directory(页目录)
**为了在单页中能快速查找到对应的记录(最坏情况为全页扫描),把一页用户记录分为若干组,每一组的最大记录都保存到`页目录`,每一组的最大记录叫做`槽`,然后就能通过二分查找进行快速定位记录**。所下图所示:
![InnoDB-页目录](images/Database/InnoDB-页目录.jpg)
### 页目录
从上面可以看出,如果我们要查询某条记录的话,数据库会从最小记录开始,一条条查找所有记录。如果中途找到了,则直接返回该记录。如果一直找到最大记录,还没有找到想要的记录,则返回空。
但效率会不会有点低?这不是要对整页用户数据进行扫描吗?
这就需要使用`页目录`了。说白了,就是把一页用户记录分为若干组,每一组的最大记录都保存到一个地方,这个地方就是`页目录`。每一组的最大记录叫做`槽`。由此可见,页目录是有多个槽组成的。所下图所示: ### File Trailer(文件结尾信息)
![InnoDB-页目录](images/Database/InnoDB-页目录.jpg) 用于检验当前页的完整性,主要记录了页面的`校验和checksum`。具体地其占用 `8byte`,前`4byte`为校验和(checksum),后`4byte`为页面被最后修改时相应的日志序列位置(LSN)。
假设一页的数据分为4组这样在页目录中就对应了4个槽每个槽中都保存了该组数据的最大值。这样就能通过二分查找比较槽中的记录跟需要找到的记录的大小。如果用户需要查找的记录小于当前槽中的记录则向上查找上一个槽。如果用户需要查找的记录大于当前槽中的记录则向下查找下一个槽。如此一来就能通过二分查找快速的定位需要查找的记录了。
## 行格式
### 文件尾部 ### compact行格式
数据库的数据是以数据页为单位加载到内存中如果数据有更新的话需要刷新到磁盘上。但如果某一天比较倒霉程序在刷新到磁盘的过程中出现了异常比如进程被kill掉了或者服务器被重启了。这时候数据可能只刷新了一部分如何判断上次刷盘的数据是完整的呢这就需要用到`文件尾部`。它里面记录了页面的`校验和`。 ### redundant行格式
在数据刷新到磁盘之前,会先计算一个页面的校验和。后面如果数据有更新的话,会计算一个新值。文件头部中也会记录这个校验和,由于文件头部在前面,会先被刷新到磁盘上。 ### dynamic行格式
接下来,刷新用户记录到磁盘的时候,假设刷新了一部分,恰好程序出现异常了。这时,文件尾部的校验和,还是一个旧值。数据库会去校验,文件尾部的校验和,不等于文件头部的新值,说明该数据页的数据是不完整的。 ### compressed行格式
@ -1813,61 +1766,107 @@ InnoDB使用两种预读算法来提高I/O性能线性预读linear read-ah
## 磁盘结构 ## 磁盘结构
InnoDB磁盘主要包含TablespacesInnoDB Data DictionaryDoublewrite Buffer、Redo Log和Undo Logs。
### 表空间(Tablespaces) ### 表空间(Tablespaces)
#### The System Tablespace #### 系统表空间
**系统表空间(The System Tablespace)是`数据字典`、`双写缓冲区(Doublewrite Buffer)`、`Change buffer`和`Undo Logs`的储存区域**。该空间的数据文件通过参数`innodb_data_file_path`控制,默认值是`ibdata1:12M:autoextend`(文件名为ibdata1大小略大于12MB自动扩展)。8.0之后InnoDB将元数据存在该区域的数据字典中data dictionary
**The System Tablespace** 是Doublewrite Buffer和Change buffer的储存区域也有用户创建的表和索引数据。该空间的数据文件通过参数`innodb_data_file_path`控制,默认值是`ibdata1:12M:autoextend`(文件名为ibdata1大小略大于12MB自动扩展)。**8.0之后InnoDB将元数据以前的.frm文件存表结构存在该区域的数据字典中data dictionary**。
#### 独占表空间
**独占表空间(File-Per-Table Tablespaces)**默认开启,为每个表都独立建一个.ibd文件。 通过参数`innodb_file_per_tabl` 可以设置关闭这样的话所有表数据是都存在The System Tablespace的ibdata。
#### File-Per-Table Tablespaces
**File-Per-Table Tablespaces** 默认开启,为每个表都独立建一个.ibd文件。 通过参数`innodb_file_per_tabl` 可以设置关闭这样的话所有表数据是都存在The System Tablespace的ibdata。
#### 通用表空间
**通用表空间(General Tablespaces)**是通过`CREATE TABLESPACE`创建的共享表空间。通用表空间可以创建于MySQL数据目录外的其他表空间其可以容纳多张表且其支持所有的行格式。
#### General Tablespaces
**General Tablespaces** 是通过`CREATE TABLESPACE`创建的共享表空间。
#### 撤销表空间
**撤销表空间(Undo Log Tablespaces)**保存的是undo log 用于回滚事务。undo log撤销日志或回滚日志记录了事务发生之前的数据状态不包括select 。用来保证在必要时实现回滚如果另一个事务需要在一致性读操作中查看原始数据则从undo日志记录中检索未修改的数据也就是说MVCC机制也依赖于undo log来实现。在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,属于逻辑格式的日志。
#### Undo Tablespaces
**Undo Tablespaces**保存的是undo log ,用于回滚事务。
该表空间有rollback segments,**rollback segments**是用于存 **undo log segments**, 而**undo log segments**存的就是undo logs。MySQL启动的时候默认初始两个undo tablespace。因为sql执行前必须要有rollback segments。而两个undo tablespace才支持**automated truncation of undo**。
#### 临时表空间
**临时表空间(Temporary Tablespaces)**存储临时表的数据包括用户创建的临时表和磁盘的内部临时表。对应数据目录下的ibtmp1文件。当数据服务器正常关闭时该表空间被删除下次重新产生。
#### Temporary Tablespaces
InnoDB把 **Temporary Tablespaces**分为两种,**session temporary tablespaces** 和**global temporary tablespace**。
**session temporary tablespaces**存储的是用户创建的临时表和内部的临时表一个session最多有两个表空间用户临时表和内部临时表。**global temporary tablespace**储存用户临时表的回滚段rollback segments )。
### 数据字典(Data Dictionary)
**Data Dictironary(DD数据字典)是有关数据库对象的合集**,例如表、视图、索引等,可以看做是数据库的元信息。换句话说,数据字典存储了有关表结构的信息,每个表具有的列,表的索引等
### 数据字典(InnoDB Data Dictionary) InnoDB数据字典由内部系统表组成这些表包含用于**查找表、索引和表字段**等对象的元数据。元数据物理上位于InnoDB系统表空间中。由于历史原因数据字典元数据在一定程度上与InnoDB表元数据文件.frm文件中存储的信息重叠。
### 双写缓冲区(Doublewrite Buffer) ### 双写缓冲区(Doublewrite Buffer)
**Doublewrite Buffer机制极大的保障了Innodb引擎的数据安全性。尽管出现了宕机坏页的状况也能够从Doublewrite Buffer读取正常页来恢复**。Doublewrite缓冲区是一个存储区域InnoDB在将页面写入InnoDB数据文件中的适当位置以前会在其中写入从缓冲池中刷新的页面。若是在页面写入过程当中发生操做系统存储子系统或mysqld进程崩溃则InnoDB能够在崩溃恢复期间从Doublewrite缓冲区中找到该页面的良好副本。
在MySQL 8.0.20以前Doublewrite缓冲区存储区位于InnoDB系统表空间中。从MySQL 8.0.20开始Doublewrite缓冲区存储区位于Doublewrite文件中。从MySQL 8.0.20开始默认会建立2个Doublewrite Buffer文件。
**为何有了redo还要Doublewrite Buffer机制数据库双写的好处是什么**
Doublewrite Buffer机制主要是更大的保障了数据页的可靠性。**主要是解决部分写失效的问题。**好比16KB的页只写了前面4KB以后就发生宕机了这种状况被称为部分写失效。针对部分写失效的问题redo重作日志也不能解决这个问题。
### 重做日志(Redo Log) ### 重做日志(Redo Log)
redo log记录的DML操作的日志可以用来宕机后的数据前滚。在log buffer的redo log日志会在宕机中丢失 重作日志是基于磁盘的数据结构主要做用是在崩溃恢复期间用于纠正不完整事务写入的数据。在正常操做期间重作日志对更改表数据的请求进行编码记录这些请求是由SQL语句或低级API调用引发的。在初始化期间以及接受链接以前会自动重播未完成意外关闭以前未完成更新数据文件的修改。默认状况下redo log会自动生成2个文件`ib_logfile0`和`ib_logfile1` 。
**WAL机制**
WAL 的全称是 Write-Ahead Logging中文称**预写式日志**,是一种数据安全写入机制。就是**先写日志,而后在写入磁盘,这样保证数据的安全性**。Mysql中的Redo Log就是采用WAL机制。(这里的写日志因为是顺序写,因此不会成为性能瓶颈。)
**WAL做用**
Mysql中若是为了保证数据的持久性在每提交一个事务就将日志刷新到磁盘上这样效率就过低了严重影响性能因此就有了Write-Ahead 。
**Write-Ahead工作机制**
先在内存中提交事务,而后写日志(在InnoDB中就是Redo Log日志是为了防止宕机致使内存数据丢失),而后再后台任务中把内存中的数据异步刷到磁盘。
### 撤销日志(Undo Logs) ### 撤销日志(Undo Logs)
**undo log**记录数据更改前的快照感觉就是备份在数据需要回滚就可以根据undo log恢复。 回滚日志主要是为了支持事务回滚功能。默认会生成2个回滚日志保存在undo tablespaces默认状况下就在数据目录下`undo_001`和`undo_002`。
一个事务最多能够分配四个撤消日志,如下每种操做类型均可以分配一个:
- 对用户自定义表执行插入操做
- 对用户自定义表执行删除和更新操做
- 对用户自定义的临时表执行插入操做
- 对用户自定义的临时表执行删除和更新操做
## 存储索引
`InnoDB`支持`3`种常见索引:
- 哈希索引
- `B+ `树索引
- 全文索引
### 哈希索引
### B+树索引
### 全文索引
那些undo log 记录关于在global temporary tablespace 的用户临时表的回滚信息,不会在回滚中恢复。
@ -2471,16 +2470,12 @@ select * from t where k1=1 and k3=3;
`MySQL 5.7.7` 之前,默认的格式是 `STATEMENT` `MySQL 5.7.7` 之后,默认值是 `ROW`。日志格式通过 `binlog-format` 指定。 `MySQL 5.7.7` 之前,默认的格式是 `STATEMENT` `MySQL 5.7.7` 之后,默认值是 `ROW`。日志格式通过 `binlog-format` 指定。
- `STATMENT`:基于`SQL` 语句的复制( `statement-based replication, SBR` )每一条会修改数据的sql语句会记录到`binlog` 中 。 - `STATMENT`:基于`SQL`语句的复制( `statement-based replication, SBR` )每一条会修改数据的sql语句会记录到`binlog` 中
- - 优点:不需要记录每一行的变化,减少了 binlog 日志量,节约了 IO , 从而提高了性能; - - 优点:不需要记录每一行的变化,减少了 binlog 日志量,节约了 IO , 从而提高了性能;
- 缺点在某些情况下会导致主从数据不一致比如执行sysdate() 、 slepp() 等 。 - 缺点在某些情况下会导致主从数据不一致比如执行sysdate() 、 slepp() 等 。
- `ROW`:基于行的复制(`row-based replication, RBR` )不记录每条sql语句的上下文信息仅需记录哪条数据被修改了 。 - `ROW`:基于行的复制(`row-based replication, RBR` )不记录每条sql语句的上下文信息仅需记录哪条数据被修改了 。
- 优点不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题
- - 优点不会出现某些特定情况下的存储过程、或function、或trigger的调用和触发无法被正确复制的问题
- 缺点:会产生大量的日志,尤其是` alter table ` 的时候会让日志暴涨 - 缺点:会产生大量的日志,尤其是` alter table ` 的时候会让日志暴涨
- `MIXED`:基于`STATMENT` 和 `ROW` 两种模式的混合复制(`mixed-based replication, MBR` ),一般的复制使用`STATEMENT` 模式保存 `binlog` ,对于 `STATEMENT` 模式无法复制的操作使用 `ROW` 模式保存 `binlog` - `MIXED`:基于`STATMENT` 和 `ROW` 两种模式的混合复制(`mixed-based replication, MBR` ),一般的复制使用`STATEMENT` 模式保存 `binlog` ,对于 `STATEMENT` 模式无法复制的操作使用 `ROW` 模式保存 `binlog`

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Loading…
Cancel
Save