同窗, 还记得上一回说的回龙观大叔面试的故事嘛? 通过上一回合的学习, 这位大叔终于找回了点自信, 此次又投了几家公司, 不过如今尚未公司去联系他.mysql
大叔的电脑桌放在阳台上, 这是个绝佳的位置, 天天清晨惺忪的阳光都能照进来, 但大叔可不想坐在那天天晚上数星星、白天晒太阳, 当小区里面的年轻人还在熟睡的时候, 大叔早已经坐在书桌前开始继续瞌 mysql 的知识了.面试
如下知识均为大叔周末聊天时的口述, 我这里整理下大概知识点.算法
大叔: 小兄弟, 大叔是面试的过来人, 这回合的知识很重要, 可要避免重走个人老路呀~sql
我把 Innodb 比喻成一本书, 那么基本存储单位就是页, 一个页的大小大约是16KB. 页的类型也不同, 在书里面有目录页、附录页、内容页, Innodb 根据不一样功能也有不少类型的页, 好比存放INODE信息的页、存放 undo 日志信息的页、存放数据索引的页等等.markdown
这是一行数据的底层存储结构, 看看我调的色都么清新~ 性能
下面大叔解释如下淡绿框字段的含义:学习
被删除的记录还在页中么? 他不会当即从页中真正的移除掉, 行记录中 delete_mask 就是标记已删除的记录, 全部被删除掉的记录都会组成一个所谓的垃圾链表,在这个链表中记录占用的空间称之为所谓的可重用空间,以后若是有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉spa
若是面试官要问你, 为何删除掉记录不真正移除掉?3d
你就答: 记录与记录之间是有关联关系的, 移除它们以后把其余的记录在磁盘上从新排列须要性能消耗.日志
min_rec_mask 用来标记 B+树 的每层非叶子节点中的最小记录
用来标记当前记录的页位置, 页中行数据排列顺序也是从小到大顺序排列的, 值得一提的是每一个页中自动加了两条虚拟记录, 一个记录的是当前页的主键最大记录, 一个记录的是最小记录. 也就是上一回合提到的每一个 page页 最少两条记录的缘由
当前行记录类型
类型值 | 含义 |
---|---|
0 | 普通记录(一般咱们插入的数据记录) |
1 | B+树非叶节点记录(索引数据) |
2 | 页最小记录(虚拟记录) |
3 | 页最大记录(虚拟记录) |
距离下一条记录的地址偏移量. 比方说第一条记录的 next_record 值为64,意味着从第一条记录的真实数据的地址处向后找64个字节即是下一条记录的真实数据, 若是 next_record 为0, 则表示没有下一条记录了, 这个对于咱们数据检索来讲是很是重要的
n_owned 表示该组内共有几条记录
我问大叔具体这个是啥意思啊, 大叔说他没搞明白, 若是面试官继续问就说不知道就能够了, 技术圈通常都已点到为止, 不用深究.
查找一条记录, 咱们须要遍历全部页嘛? 在数据量少的状况, 咱们能够根据页中存的最大最小记录查找, 可是数据量一多, 确定也是没法忍受的. 那 innodb 是怎么作的呢?
咱们仍是拿书举例子, 书也是一页一页的, 咱们怎么快速定位到某一章某一节的某一段内容呢? 聪明的小伙伴确定回答是: 目录。
没错, 咱们看看 mysql 是怎么实现页 ”页级别目录“ 的
(此图为回龙观大叔所盗《mysql是怎样运行的》, 与本文做者无关)
简单来讲, 就是一个 page 页中最大8条记录分组, 将每组最小最大的值偏移量记录到 slot 槽中, 这里的 slot 就至关于目录的一个做用, 下面是一个查数过程:
经过二分法肯定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录
经过记录的 next_record 属性遍历该槽所在的组中的各个记录
听回龙观大叔说这是让他那天面试最心碎的地方, 须要我额外注意, 你们提起精神来啊~
大叔狂磕第一回咱们就知道在一个数据页怎么定位数据了, 可是咱们都是假设基于主键进行的查找哦。那么若是不是主键呢?
这个就很悲剧了, 由于在数据页中并无对非主键列创建所谓的页目录,因此咱们没法经过二分法快速定位相应的槽。这种状况下只能从最小记录开始依次遍历单链表中的每条记录,而后对比每条记录是否是符合搜索条件(下面会讲到索引, 解决这个遍历查询慢问题)
一个数据页大概只有 16KB, 咱们数据通常不可能只有这些, 确定须要多个数据页存储, 那么多个页之间是怎么管理的呢?
页和页之间是双向链表链接 (此图为回龙观大叔所盗《mysql是怎样运行的》, 与本文做者无关)
若是没有索引的话, 默认是从页a开始查知道页b、页c挨个查找, 直到知足指定的条件为止.
当数据页数据变大时, 将会由新增页来存储新数据, 这个过程就叫 页分裂.
好比下图是在页10中插入记录主键4, 而已存在记录主键5, 当页10已满时, 新建立页28, 进行的数据调整过程.
(此图为回龙观大叔所盗《mysql是怎样运行的》, 与本文做者无关)]
你们忽略页号, 这个是没有实际做用的, 真正的先后关系是使用页之间双向链表维护的.
由上面的规则能够看出, 在对页中的记录进行增删改操做的过程当中, 下一个数据页中的主键值必须大于上一个页中主键值, 因此咱们通常设置主键都会设置自增, 这样是能够避免页满时数据进行交换调整.
页内查询咱们冗余 slot 空间来进行提升查询速度, 可是对于这么多页, 总不能一个页一个页的扫描吧. 固然不行! 那咱们再抽象一层.
(此图为回龙观大叔所盗《mysql是怎样运行的》, 与本文做者无关)
咱们看这样扫描页是否是就很快了, 咱们基于上面数据就能够很快定位到具体的页了.
key 就是咱们说的索引
(此图为回龙观大叔所盗《mysql是怎样运行的》, 与本文做者无关)
如上图所示, 同数据页同样, 咱们索引key也是也是页管理的, 还记得上面 record_type 类型么, record_type=1为 B+ 树非叶节点记录, 普通数据为 record_type=0
索引检索、slot检索都是经过二分查找算法实现的
若是数据量再大, 索引页也会变得更多, 那么咱们该怎么办呢? 再抽象一层!
可是这里树的高度并不能无限扩增哦, 由于每一层都表明了一次磁盘IO, 层级高磁盘IO次数变多将会致使查询变慢. B+树本质增大层的宽度来下降树的高度, 因此通常咱们树的高度也不会超过4层, 一个页节点存放1000条数据, 一层1000个节点, 四层则是1000X1000X1000X10000=100000000000!!!
如上图, Innodb 将全部数据存在叶子节点, 咱们一般称这种存储方式为聚簇索引.
我理解聚簇索引对于实现行锁是很是方便的, 主键查询条目比较少时,不用回行. 可是若是碰到不规则数据插入时,形成频繁的页分裂
MyISAM的索引方案也是树形结构,可是却将索引和数据分开存储的
当咱们基于二级索引查找数据时, 会给二级索引一样创建一个相似的 B+ 树, 不过叶子节点存储的是主键的id, 再基于主键查询叶子节点数据, 这种行为咱们称之为回表.
而咱们查询字段正好与索引彻底匹配, 不用再回表查询数据, 称为覆盖索引.
(此图为回龙观大叔所盗, 与本文做者无关)
页面和记录先按照联合索引前边的列排序,若是该列值相同,再按照联合索引后边的列排序. 这种记录模式决定了咱们直接使用后面的字段是没法用到索引的, 由于单独它自己就是无序的.
若是数据行数多和创建索引数据大, 索引是消耗存储空间的.
在增删改上都须要去修改各个B+树索引, 尤为对于无序的数据索引来讲, 修改顺序的后果是将会频繁的进行插入修改页、页分裂, 对于 DML 性能来讲是有损耗的.
只为用于搜索、排序或分组的列建立索引
一般咱们只对索引数据分布广创建索引, 对于性别类是不适合创建索引的, B+树内节点变成了二节点, 叶子节点数据汇集严重.
索引列的类型小, 对于大类型列占据更多的存储空间, 检索效果很差, 能够选定索引字符串值的前缀.
上一次见回龙观大叔, 距离如今过去才几天, 上次还抱怨找工做问他底层 mysql 知识, 此次感受很是乐于学习并向我分享这些底层基础知识了. 可是一个跟我父亲年龄相仿还天天在努力学习、找砖搬的大叔, 我不知道是应该替他担忧仍是高兴.