|
|
@ -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磁盘主要包含Tablespaces,InnoDB Data Dictionary,Doublewrite 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`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|