• 为了避免每次加载表记录是一条一条的加载,将多条表记录作为一个数据页进行加载
    • 感觉也是因为 时间局限性 和 空间局限性
      • 时间局限性: 有良好时间局限性的程序,被引用过的存储器位置可能会在接下来的时间中多次被引用
      • 空间局限性: 有良好空间局限性的程序,一个存储器被引用过一次,那么接下来的时间中临近的存储器将被引用

表空间

  • 加载数据页是从表空间中加载的
  • 逻辑上的表对应一个物理层面上的表空间
    • 磁盘上以 xxx.ibd 文件存储
  • 一个表空间有很多extent (表空间)

    • 一组 extent 有 256 个 extent
      • 一个 extent 有 64个数据页 (16k*64 = 1m)

        描述信息

  • 每个表空间第一组 extent 的第一个 extent 的前三个数据页是固定的

    • FSP_HDR: 存放表空间和这一组 extent 的一些属性
    • IBUF_BITMAP: 存放这一组数据页和所有 insert buffer 的一些信息
    • INODE: 存放一些特殊的信息
  • 每个表除了第一组 extent 外的每一组 extent第一个 extent 的前两个数据页也是存放特殊信息的
    • XDES: 存放该组 extent 的相关属性

数据页的组成

  1. 文件头: 38个字节
  2. 数据页头: 56个字节
  3. 最大记录、最小记录: 26个字节
  4. 多行数据行: 初始为空,不定长
  5. 空闲空间:不定长
  6. 数据页目录:不定长
  7. 文件尾部: 8字节

    缓存页和数据页的构造一样

  • 空的数据页如果被加载进缓存页时
    • 数据行为空,空闲空间最大
  • 当进行更新时,缓存页的数据行会增加
    • 直到空闲空间耗尽

单行的物理数据存储格式

数据页物理数据的行格式存储

  • 数据页的每一行数据,是根据 行格式 进行存储,比如 COMPACT 格式

    1. create table table_name (xxx) ROW_FORMAT=COMPACT;
    2. alter table table_name ROW_FORMAT=COMPACT;
  • 会变成类似下面的格式

    1. 变长字段的长度列表, null 值列表, 数据头,colmn01的值 [, column0x的值]

多行数据的字段会存放在一起

变长字段的长度列表

  • 引入变长字段的长度列表,里面存储的是变长字段的16进制长度

    • 字段正序存放,变长字段长度列表倒序存储
      1. [0x02, 0x03] abc ab
  • 读取的时候会从数据头拿到已知表的结构,然后根据表的结构进行读取,当读到变长字段时,再根据变长字段的长度列表知道接下来的变长字段有多长

null 值列表

  • 存储设置了可null字段的值,每一个可null字段由 bit 来存储
    • 要求 8bit 一个单位,不够前面补0
    • 当对应的字段为 null 时,对应的 bit 为 1
    • 同样也是倒序存储
  • 注意,变长字段为null时,对应 bit 为 1,并且变长字段的长度列表不会存储

数据头

  • 40bit
  1. 2 bit 预留位
  2. 1 bit delete_mask 标识该行数据是否被删除
  3. 1bit min_rec_mask 标记 B+树里每一层的非叶子节点里面的最小值
  4. 4bit n_owned 记录数
  5. 13bit heap_no 当前改行数据在记录堆里的位置
  6. 3bit record_type 该行数据的类型
    1. 0 普通、
    2. 1 B+树非叶子节点
    3. 2 最小值数据
    4. 3 最大值数据
  7. 16bit next_record 下一条数据的指针

    实际数据存储格式

  • 字符串会按照字符集编码将字符进行编码
  • 每行的实际数据前,有一些额外字段
    1. DB_ROW_ID 行的唯一标识,是数据库内部搞的标识,不是主键id
      1. 如果没有提供主键,会额外加一个 ROW_ID 作为主键
    2. DB_TRX_ID 事务id,标识是哪个事务更新
    3. DB_ROLL_PTR 回滚指针,用于事务回滚

行溢出

  • 当某一行数据太长,超过数据页的长度 (默认16kb),会在真实数据后补个 20字节 的指针,指向下一个数据页
    • 即同一行数据可以包含多个数据页

随机读写和顺序读写

  • 加载数据页/刷回数据页的时候,是通过随机读写的方式进行的

    1. // 类似这样
    2. dataFile.setStartPosition(102414515);
    3. dataFile.setEndPosition(102415454);
    4. dataFile.readOrWrite()
    • 指标
      • IOPS: 每秒随机读写量
      • 响应延迟: 随机读写后的耗时
  • 写 redo log 的时候,是顺序读写
    • 速度跟内存一样
    • 指标
      • 顺序读写吞吐量
      • 顺序读写响应延迟

流程

  • linux 存储系统

    1. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/367873/1583386943109-13d1a279-cd67-400d-abb7-aaf9a9ef4aac.png#align=left&display=inline&height=422&name=image.png&originHeight=422&originWidth=406&size=13890&status=done&style=none&width=406)
  • 应用发起一次读写请求时,会通过 vfs 会通过讲需要对哪个目录的哪个文件进行的磁盘 IO 操作 ,调用合适的文件系统来处理

  • 文件系统首先会从 Page Cache(一般 os cache 就是了) 来查找是否有对应的数据
  • 如果没有,将 IO 操作发给 通用 Block 层,该层会将 IO 操作转化为 Block IO 操作
  • Block IO 操作会发给 IO 调用层,该层默认用 CFQ 公平调度算法来进行 IO 操作的调度
    • 建议设置成 deadline 算法,避免能快速完成的 IO 操作饥饿等待
  • IO 调度完成之后,会发给 Block 设备驱动层,由它将 Block IO 操作发给真正的存储硬件
    • 存储硬件一般采用 RAID (磁盘冗余阵列) 进行部署,避免磁盘故障造成数据丢失
  • 操作完成后,响应结果原路返回到应用