Files
rikako-note/mysql/mysql文档/mysql_表.md

208 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 表
## 索引组织表
innodb存储引擎中表都是根据主键顺序组织存放的这种存储方式被称为索引组织表index organized table。在innodb存储引擎表中每张表都有主键primary key如果在创建表时没有显式指定主键那么innodb会按照如下方式创建主键
- 首先判断表中是否存在非空的唯一索引unique not null字段如果有则其为主键
- 如果不存在非空唯一索引那么innodb会自动创建一个6字节大小的指针作为主键
如果有多个非空唯一索引innodb存储引擎将会选择第一个定义的非空唯一索引作为主键。
## innodb逻辑存储结构
在innodb的存储逻辑结构中所有的数据都被逻辑存放在表空间table space中。表空间则由`段segementextentpage`组成。
组成如图所示:
<img src="https://pic2.zhimg.com/v2-7a3fe8eb03c68e1378f24847e464e139_1440w.jpg" data-caption="" data-size="normal" data-rawwidth="421" data-rawheight="275" data-original-token="v2-d7e41f080ece2820138fd0331f965a79" class="origin_image zh-lightbox-thumb" width="421" data-original="https://pic2.zhimg.com/v2-7a3fe8eb03c68e1378f24847e464e139_r.jpg">
### 表空间
表空间为innodb存储引擎逻辑结构的最高层所有数据都存放于表空间中。innodb存在一个默认的共享表空间`ibdata1`,在开启`innodb_file_per_table`参数后,每张表内的数据可以单独存放到一个表空间。
#### innodb_file_per_table
`innodb_file_per_table`参数启用会导致每张表的`数据、索引、插入缓冲bitmap页`存放到单独的文件中;但是其他数据,例如`回滚undo信息插入缓冲页系统事务信息double write buffer`等还是存放在默认的共享表空间中。
### 段segment
如上图所示表空间是由段segment所组成的常见的段分为`数据段,索引段,回滚段`等。
在innodb存储引擎中数据即索引索引即数据。`数据段即为B+树的叶子节点(Leaf node segment)`,`索引段即为B+树的非叶子节点(Non-leaf node segment)`
### 区Extent
区是由连续页组成的空间在任何情况下每个区的大小都为1MB。为了保证区中页的连续性innodb存储引擎会一次性从磁盘申请4~5个区。在默认情况下innodb存储引擎中页的大小为`16KB`,一个区中包含64个页。
在新建表时新建表的大小为96KB小于一个Extent的大小1MB因为每个段Segmenet开始时都会有至多32个页大小的碎片页等使用完这些页后才会申请64个连续页作为Extent。
都与一些小表或是undo这样的段可以在开始时申请较少的空间节省磁盘容量开销。
### 页Page
innodb存储引擎中页的大小默认为`16KB`,默认的页大小可以通过`innodb_page_size`参数进行修改。通过该参数可以将innodb的默认页大小设置为4K8K。
页是innodb磁盘管理的最小单位在innodb中常见的页有:
- 数据页B-tree node
- undo页undo log page
- 系统页system page
- 事务数据页transaction system page
- 插入缓冲位图页insert buffer bitmap
- 插入缓冲空闲列表页insert buffer free list
- 未压缩的二进制大对象页uncompressed blob page
- 压缩的二进制大对象页compressed blob page
### 行
innodb存储引擎是面向行的数据按行进行存放。每个页中至多可以存放`16KB/2 - 200`行的记录即7992行记录。
## innodb行记录格式
innodb存储引擎以行的形式进行存储可以通过`show table status like '{table_name}'`的语句来查询表的行格式,示例如下:
```sql
show table status like 'demo_t1'
```
| Name | Engine | Version | Row\_format | Rows | Avg\_row\_length | Data\_length | Max\_data\_length | Index\_length | Data\_free | Auto\_increment | Create\_time | Update\_time | Check\_time | Collation | Checksum | Create\_options | Comment |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| demo\_t1 | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 32768 | 0 | 1 | 2025-01-30 15:18:57 | null | null | utf8mb4\_0900\_ai\_ci | null | | |
上述示例中表的row_format为dynamic。
### Compact
在使用Compact行记录格式时一个页中存放数据越多其性能越高。
Compact格式下行记录的存储格式如下
<table>
<tr>
<td>变长字段长度列表</td>
<td>NULL标志位</td>
<td>记录头信息</td>
<td>列1数据</td>
<td>列2数据</td>
<td>......</td>
</tr>
</table>
#### 变长字段长度列表
Compact行记录格式其首部是一个`非Null变长字段长度`列表,其按照列的顺序逆序放置,变长列的长度为:
- 如果列的长度小于255字节用1字节表示
- 如果列的长度大于255字节用2个字节表示
变长字段的长度不能小于2字节因为varchar类型最长长度限制为65535。
#### NULL标志位
NULL标志位为bitmap代表每一列是否为空。如果行中存在n个字段可为空那么NULL标志位部分的长度为ceiling(n/8)。
#### 记录头信息
record header固定占用5字节其中record header的各bit含义如下所示
| 名称 | 大小(bit) | |
| :-: | :-: | :-: |
| () | 1 | 未知 |
| () | 1 | 未知 |
| deleted_flag | 1 | 该行是否已经被删除 |
| min_rec_flag | 1 | 该行是否为预订被定义的最小记录行 |
| n_owned | 4 | 该记录拥有的记录数 |
| heap_no | 13 | 索引堆中该条记录的排序记录 |
| record_type | 3 | 记录类型000代表普通001代表B+树节点指针, 010代表infimum011代表supremum1xx保留 |
| next_record | 16 | 页中下一条记录相对位置 |
除了上述3个部分之外其他部分就是各个列的实际值。
> 在Compact格式中NULL除了占有NULL标志位外不占用任何实际空间。
> 每行数据中,除了有用户自定义的列外,还存在两个隐藏列,即`事务id列`和`回滚指针列`长度分别为6字节和7字节。
>
> 如果innodb表没有自定义主键每行还会增加一个rowid列。
### 行溢出数据
innodb存储引擎可能将一条记录中某些数据存储在真正的存储数据页面之外。一般来说blob、lob这类大对象的存储位于数据页面之外。
当行数据的大小特别大导致一个页无法存放2条行数据时innodb会自动将占用空间大的`blob`字段或`varchar`字段值放到额外的uncompressed blob page中。
### dynamic
dynamic格式几乎和compact格式相同但是对于每个blob字段其存储只消耗20字节用于存储指针。
而对于Compact格式其会在blob格式中存储768字节的前缀字节。
### char存储结构
`char(n)`字段中n代表`字符长度`而非字节长度故而在不同字符集下char类型字段的内部存储可能不是定长的。
例如在utf8字符集下`ab``我们`两个字符串其字符数都是2个但是`ab`其占用2字节`我们`占用4字节即使同样是`char(2)`类型的字符串,其占用字节数量仍然有可能不同。
## innodb数据页结构
innodb中页是磁盘管理的最小结构页类型为B-tree Node的页存放的即是表中行的实际数据。
innodb数据页由如下7个部分组成
- File Header文件头
- Page Header页头
- Infimum和Supremum Records
- user records用户记录即行记录
- free space空闲空间
- page directory页目录
- file trailer文件结尾信息
file headerpage headerfile trailer的大小是固定的分别为38568字节这些空间用于标记页的一些信息例如checksum数据页所在B+树的层数等。
<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6d066e690d22484ebe33bbb4977c3cfb~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp" alt="image.png" loading="lazy" class="medium-zoom-image">
### File Header
File Header用于记录页的一些头信息由8个部分组成共占38字节
| 名称 | 大小 | 说明 |
| :-: | :-: | :-: |
| FIL_PAGE_SPACE_OR_CHKSUM | 4 | 代表页的checksum值 |
| FIL_PAGE_OFFSET | 4 | 表空间中页的偏移位置 |
| FIL_PAGE_PREV | 4 | 当前页的上一个页B+树决定叶子节点为双向列表 |
| FIL_PAGE_NEXT | 4 | 当前页的下一个页 |
| FIL_PAGE_LSN | 8 | 代表该页最后被修改的日志序列位置LSN |
| FIL_PAGE_TYPE | 2 | 存储引擎页类型 |
| FIL_PAGE_FILE_FLUSH_LSN | 8 | 该值仅在系统表空间的一个页中定义代表文件至少被更新到了该LSN值对于独立的表空间该值为0 |
| FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 代表页属于哪个表空间 |
### Infimum和Supremum record
在innodb存储引擎中每个数据页都有两行虚拟的行记录用于限定记录边界。Infimum是比该页中所有主键值都要小的值Supremum是比任何可能值都要大的值。`这两个值在页创建时被建立,并且在任何情况下都不会被删除。`
### user record 和 free space
user reocrd代表实际存储行记录中的内容free space则是代表空闲空间同样是链表数据结构在一条记录被删除后该空间会被加入到空闲列表中。
### page directory
page directory页目录中存放了记录的相对位置这些记录指针被称为目录槽directory slots`innodb中槽是一个稀疏目录一个槽中可能包含多个记录`记录Infimum的n_owned总为1记录Supremum的n_owned取值范围为`[1,8]`其他用户记录的n_owned为`[4,8]`。当记录被插入或删除时,需要对槽进行分裂或平衡的维护操作。
在slots中记录按照索引键值顺序存放可以通过二叉查询迅速找到记录的指针。
由于在innodb存储引擎中page directory是稀疏目录二叉查找结果只是一个粗略的结果innodb存储引擎必须通过record header中的next_record来继续查找相关记录。
#### B+树索引
<img src="//cloud.mo4tech.com/images/ldWYtlmLrJXYtJXZ0F2dtA3YmBnYmFTdzsWL2xGc05XYhR2MwIjZycTMyczN4gDOmBTY0Y2Y5QmYygjY1YTNk9CcjZGciZWM1NzatkWLuNWLz9Gdv02bj5yZtlWZ0lnYu4WaqVWdq1iNw9yL6MHc0RHa/cefbe79c0791cd0781f7c6aa3a923e21.image" data-cdn="">
如上图所示innodb中的数据是按照索引来进行组织的。但是通过B+树索引,其无法直接查询到指定的记录,其只能查询到记录所位于的页。
例如,在上图示例中,其在查询`ID=32`的时只能查询到该记录位于page 17中。
> 在B+树的叶子节点每个页的File Header中都存有指向前一个页和后一个页的指针`故而每个页之间是通过双向列表结构来维护的`;但是对于页中的记录,`记录与记录之间维维护的时单向列表的关系`。
>
> 对于Compact或Dynamic行格式的页其每条记录的record header中都包含一个next_record字段指向下一条记录。故而位于同一个页中的记录可以单向访问。
但是在一个页内查找某条记录时沿着单向链表进行查找其效率很低。故而page中的数据时机被分为了多个组被分为的组构成了一个subdirectory故而通过子目录能够缩小查询范围提高查询性能。
page directory是一个能够存储多个slots的部分每个slot中存储了group中最后一条记录的相对位置。假设slot中最大一条记录为`r`那么group中记录的条数被存储在`r`记录record header的`n_owned`字段中。
group中record的数量约束如下
- infimum group中records数量限制为`[1,8]`
- supremum group中records数量限制为`[1,8]`
- 其他group中records限制为`[4,8]`
page directory生成过程如下所示
- 最开始时页中只有infimum和supremum两条记录它们分别属于两个group。page directory中有两个slots指向这两条记录两个slot的n_owned都为1
- 后续当新的记录被插入页时系统会查找page directory中主键值大于待插入记录的第一个slot。slot对应最大记录的`n_owned`字段会增加1代表group中新插入了记录直到group中的记录数量到达8
- 当新纪录被插入到的group中记录数大于8时group中的记录被分为2个group一个group包含4条记录另一个group包含5条记录。该过程将会向page directory中新增一个新的slot
- 当记录被删除时slot最最大一条记录的`n_owned`将会减少1当n_owned字段小于4时会触发再平衡操作平衡后的page directory满足上述要求
page directory中的slots数量如page header中的`PAGE_N_DIR_SLOTS`所示。
一旦innodb中页包含page directory后其会通过二分查找快速的定位slot并且从group中最小记录开始通过`next_record`指针来遍历页中的记录列表。这样能够快速的定位记录位置。
### File Trailer
#### 完整性校验
在innodb中页的大小为16KB可能和磁盘的扇区大小不同。通常磁盘的扇区大小为512字节故而在写入一个页到磁盘中时需要分32个扇区进行写入。
在写入一个页时可能会发生宕机场景这时一个页只写入了一部分可能会发生脏写。此时可以通过double write buffer机制对脏写的页进行恢复。
在一个页被写入到磁盘中时首先会被写入的是File Header的`FIL_PAGE_SPACE_OR_CHKSUM`,该部分是page的checksum。
innodb设置了File Trailer部分来校验page是否被完全写入到磁盘中File Trailer中只包含一个`FIL_PAGE_END_LSN`部分占用8字节前4字节代表该页的checksum值后4字节和File Header中的`FIL_PAGE_LSN`相同,`代表最后一次修改该页关联的LSN位置`。将上述两个字段和File Header中的`FIL_PAGE_SPACE_OR_CHKSUM``FIL_PAGE_LSN`值进行比较,查看是否一致,从而保证页的完整性。
默认情况下在innodb每次从磁盘读取page时都会检查页面的完整性。