相关系列链接
焱老师带你学习MYSQL系列 第六篇 (MYSQL是如何实现锁的)
焱老师带你学习MYSQL系列 第五篇 (MYSQL事务隔离级别是如何实现的)
焱老师带你学习MYSQL系列 第四篇 (MYSQL优化器详解)
焱老师带你学习MYSQL系列 第三篇 (MYSQL单表访问方法)
焱老师带你学习MYSQL系列 第二篇 (MYSQL 数据结构)
焱老师带你学习MYSQL系列 第一篇 (MYSQL 整体架构)
前记
有一天小明问焱老师 , 焱老师 为啥MYSQL是怎么存储那么多数据的呢 ? 是怎么存放的 ?
看着满脸疑惑的小明 焱老师陷入了沉思 !!!
1 . MYSQL 存储方式
MYSQL 存储方式是基于磁盘存储 , 区别于redis 基于内存存储 是可持久化 并且在应对一些极端情况能保证数据的完成性 .
2 . MYSQL 文件格式
上图上图 :
MYSQL 存储结构 从大到小 排列一次是 :
库 - >表空间(tablespace) - > 组 -> 段 (segment) -> 区 (extent) -> 页 (page) -> row (行)
2 . 0 库
当我们创建库时 , 会在数据目录下创建一个与数据库同名的子目录/文件夹 , 在该子目录下创建一个名为db.opt文件 . 包含了这个数据库的属性 , 比如字符集/比较规则等等 .
2 . 1 表结构
表结构的定义 . 每当我们建一个表时 , 都会在对应的目录下创建 一个 表名.frm文件来存放这个表结构的信息(二进制)
2 . 2 表空间
表空间按照属性不同分为两类 .
2 . 2 . 1 系统表空间
文件名称为ibdata1 (自适应扩大大小的)
2 . 2 . 2 独立表空间
每当MYSQL 创建一个表 , 就会在MYSQL 对应的data目录下 创建对应表的.ibd文件 . 举例子 比如表名叫table_a , 就会创建 table_a.ibd 文件 . 这个文件里面会存储所有的数据 以及这个表的索引 .
问 : 如何查看数据目录 ?
SHOW VARIABLES LIKE ‘datadir’
2 . 3 组
每 256个区为一组 每一组前三个页是固定的 页的类型分别为 FIL_PAGE_TYPE_FSP_HDR (表空间头部信息) , FIL_PAGE_IBUF_BITMAP (ChangeBuffer的一些属性) , FIL_PAGE_INODE (存储段的信息) 区 跟页的定义以及类型会在后面详细描述
2 . 4 段
段的作用 : 解决 聚簇索引 / 非聚簇索引 引起的大量随机I/O
段是一个虚化概念 . 并不是真实存在的 . 段可能是区 也可能是页 根据数据量来划分 .
针对范围查找 很多情况是找到根节点一直往后扫描 .
故我们把叶子节点 放在一个段内 , 非叶子节点放在另外一个段内 . 减少随机I/O
2 . 5 区
连续的64个页为一个区 , 区为1MB
区的分类 :
1 . 空闲区 (FREE)
2 . 有剩余空间页面的碎片区 (FREE_FRAG)
3 . 没有剩余空间页面的碎片区 (FULL_FRAG)
4 . 附属于某个段的区 (FSEG)
区与区之间是通过链表连接 .
(由于篇幅关系 焱老师 先不详细将区 的分类以及作用 , 区是如何管理以及对应区的作用 大家可以关注我 如果时间允许 我会在这个博文中详细讲解 区是如何管理以及对应的数据结构)
2 . 6 页
1 . 一页为 16KB
页的数据结构 :
无论什么页都是 FileHeader 开头 (38字节 确定当前页的属性)
每一个页都有一个页号 , 是FIL_PAGE_OFFEST , 可以通过该页号快速定位一个页
针对数据页可以组成链表 , 链表两个页面可以不连续 , 通过PERV跟NEXT组成双向链表
页的分类 :
1 . FIL_PAGE_TYPE_ALLOCATED - 最新分配 , 还未使用
2 . FIL_PAGE_UNDO_LOG - undo日志页
3 . FIL_PAGE_INODE - 存储段的信息
4 . FIL_PAGE_IBUF_FREE_LIST - ChangeBuffer空闲列表
5 . FIL_PAGE_IBUF_BITMAP - ChangeBuffer的一些属性
6 . FIL_PAGE_TYPE_SYS - 存储一些系统数据
7 . FIL_PAGE_TYPE_TRX_SYS - 事务系统数据
8 . FIL_PAGE_TYPE_FSP_HDR - 表空间头部信息
9 . FIL_PAGE_TYPE_XDES - 存储区的一些属性
10 . FIL_PAGE_TYPE_BLOB - 溢出页
11 . FIL_PAGE_INDEX - 索引页 , 也就是我们说的数据页
2 . 7 行
!!! 上面内容大家可以简单有个印象 就好 针对开发而言 掌握优先级不是很高 , 但行格式是很重要的 !!!
需要掌握
行格式数据结构 :
简单介绍 :
1 . 变长列表 (倒序排列)
是MYSQL针对每个可能为null的字段 设置一个变长字段字节数 (可能占一个字节 or 2个字节)
计算规则 :
W ; 值得是当前字符集一个字符最大的字节数 比如utf-8mb4 为 4字节
M : 当前可变字段能容纳的最大字符数 比如varchar(10) 为 10
L : 为实际字段内容存储的字节数
(当当前变长字段为null 时 , 不记录在这个列表里面)
当 : M * W <=255时 , 使用一个字节代表变长字节数
M * W>255时 :
当L <=127 时 , 使用一个字节 .
当L > 127时 , 使用两个字节 .
!!! 所以在我们设计字段时 应该尽量保证 不超过 255个字节 .
2 . NULL值列表 (倒序排列)
统计可以那些字段可以为null (not null字段类型不记录) 用2进制倒序排列 1 为null 0位非null.
3 . 记录头信息(5字节 40bit位) (固定大小)
预留字段1 - 1 bit
预留字段2 - 1 bit
deleted_flag - 1 bit (标记此记录有没有删除)
min_rec_flag - 1 bit (B+树的每层非叶子结点中最小的目录项记录会添加标记)
n_owned - 4 bit (一个页面会分组 , 该字段表示组内带头大哥 , 大哥会记录组内有多少小弟 , 小弟此值为0)
heap_no - 13 bit (当前记录在页中的相对位置)
record_type - 3 bit (0 - 普通记录
1 - 表示b+树飞叶子节点的目录项
2 - 表示Infimum记录
3 - Supremum记录 )
next_record - 16 bits (下一条记录的相对位置)
4 . 隐藏列
row_id (6字节 - 行id 唯一表示符)
trx_id (6字节 - 事务id )
roll_pointer (7字节 - 回滚指针 指向的是undo日志)