行格式(每行记录):
- 以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为 行格式 或者 记录格式。 InnoDB 存储引擎4种不同类型的 行格式 ,分别是 Compact 、 Redundant 、Dynamic 和 Compressed 行格式。
- 组成: 记录的额外信息 和 记录的真实数据.
- 记录的真实数据:为每条记录都添加
transaction_id
和roll_pointer
这两个列,但是 row_id 是可选的(没有自定义主机、Unique列时)- 记录的额外信息:
变长字段长度列表
、NULL值列表
、记录头信息
- 把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占 用的字节数按照列的顺序逆序存放。
- Compact 行格式把这些值为 NULL 的列统一管理起来,存储到 NULL 值列表中
- 记录头信息:
delete_mask
被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的 垃圾链表 ,在这个链表中的记录占用的空间称之为所谓的 可重用空间。next_record
从当前记录的真实数据到下一条记录的真实数据的地址偏移量,第一条记录的 next_record 值为 32 ,意味着从第一条记录的真实数据的地址处向后找 32 个字节便是下一条记录的真实数据。
数据页:
InnoDB
将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为16 KB
。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。- 结构:
数据页里每行记录:InnoDB始终会维护一条记录的
单链表
(next_record
),链表中的各个节点是按照主键值由小到大
的顺序连接起来的。
Page Directory 页目录:
- 所有正常的行记录会划分几个组,最后一条记录(也就是组内最大的那条记录)的头信息中的 n_owned 属性表示该记录拥有多少条记录,将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近 页 的尾部的地方。这个地方就是所谓的 Page Directory。页面目录中的这些地址偏移量被称为 槽。
- 主键查找记录:通过二分法确定该记录所在的槽,并找到该槽中主键值最小的那条记录,通过记录的 next_record 属性遍历该槽所在的组中的各个记录。
Page Header(页面头部):
- 数据页中存储的记录的状态信息,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等。
File Header(文件头部)
- 这个页的编号是多少,它的上一个页、下一个页等…
- 每个数据页的 File Header 部分都有上一个和下一个页的编号,所以所有的数据页会组成一个
双链表
File Trailer:页完整性检查。
总: 各个数据页可以组成一个 双向链表 ,而每个数据页中的记录会按照主键值从小到大的顺序组成一个 单向链表 ,每个数据页都会为存储在它里边儿的记录生成一个页目录 ,在
通过主键查找某条记录
的时候可以在页目录
中使用二分法快速定位到对应的槽
,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
索引背景:
- 主键索引查找数据:以在
页目录
中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录- 普通列查找数据:因为在数据页中并没有对非主键列建立所谓的 页目录 ,所以我们无法通过二分法快速定位相应的 槽,只能从最小记录开始遍历查询,一页接一页
目的
:为快速定位记录所在的数据页而建立一个别的目录
- 下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值。
- 在对页中的记录进行增删改操作的过程中,我们必须通过一些诸如记录
移动
的操作来始终保证这个状态(页分裂
)- 页目录(索引) -基础概念
- 页的用户记录中最小的主键值(key)
- 页号(page_no)
record_type
:0 :普通的用户记录、1 :目录项记录、2 :最小记录、3 :最大记录- 查找主键为20的记录时,1 5 12 209 二分法到 12,找到页9 单链表顺着找到20
- 当数据多起来后,页目录也多起来了,为快速定位 目录项记录,为目录项再 建一个页目录,形成层级
由↑ 此可见 由数据页 组成了一个类似 树的结构
B+树
- 实际用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为 叶子节点 或 叶节点 ,其余用来存放 目录项 的节点称为 非叶子节点 或者 内节点 ,其中 B+ 树最上边的那个节点也称为 根节点
聚簇索引:由 InnoDB 存储引擎自动创建
- 叶子节点存储了完整的用户记录(所有列)
页内的记录
是按照主键的大小顺序排成一个单向链表、页
也是根据页中用户记录的主键大小顺序排成一个双向链表、同一层次
中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表- 聚簇(索引即数据,数据即索引)
二级索引:普通列的索引创建。
- 以
该列值+主键
再建一个B+树,以该列的大小进行记录和页的排序,类似主键索引- 以该列值+主键 新建一个
根节点
- 只不过叶子节点不是完整的数据记录,需要用主键
回表
一次,查询完整的记录,如果索引覆盖则不需要了(索引下推
)
联合索引:以同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,组成一个新的B+树
- c2、c3 建立联合索引,目录项记录 都由 c2 、 c3 、 页号 这三个部分组成。
- 记录先按照 c2 列的值进行排序,如果记录的 c2 列相同,则按照 c3 列的值进行排序
- 叶子节点由 c2、c3、主键组成,查找完整记录再
回表
最左匹配
:idx_c2_c3_c4 建立联合索引,是先按c2值排序c2值相同再按c3排序以此类推,如果查询时不包含c2,则无法命中,无从找起啊。
- 如果我们想使用联合索引中尽可能多的列,搜索条件中的各个列必须是联合索引中从最左边连续的列
匹配列前缀(字符列索引):
- 字符排序时,先比较字符串的第一个字符,第一个字符小的那个字符串就比较小,两个字符串的第一个字符相同,那就再比较第二个字符,字符串的前n个字符,也就是前缀都是排好序的。
SELECT * FROM person_info WHERE name LIKE 'As%';
这样查询能更好匹配索引create index idx_name ON table_name (column_name(length));
只索引字符串值的前缀的策略,部分场景适用
索引代价:
- 空间上,每建立一个索引都要为它建立一棵 B+ 树,每一棵 B+ 树的每一个节点都是一个数据页,一个页默认会占用 16KB 的存储空间,一棵很大的 B+ 树由许多数据页组成。
- 性能消耗上, 索引列的值从小到大的顺序排序而组成了双向链表,每次对表中的数据进行增、删、改操作时,都需要去修改各个 B+ 树索引
索引使用:
- 多列排序时,order by c1,c2 此时有索引inx_c1_c2,会直接命中索引,直接回表取出该索引中不包含的列就好了
- 注意查询列包含c1,不能 desc、asc混用
- 要想使用索引进行排序操作,必须保证索引列是以
单独列
的形式出现,而不是修饰过的形式,类似ORDER BY UPPER(name)
- 分组查询时,分组顺序和B+ 树中的索引列的顺序是一致的
- 只为用于
搜索、排序或分组
的列创建索引- 为基数大(不一致的值)列创建索引
读 “从根儿上理解MySQL” 文章部分内容后,归纳记录一波。