有关Mysql底层存储结构前面已经写过一篇文章,当然这文章主要是基于听爪哇课程之后做的笔记,过了一段时间之后有重新看一遍,就用通俗的话说一下自己对这一块的理解。
一、概述
现在我们讨论的是Mysql的存储,通常我们是在cpu中处理数据,但是由于成本以及存储特性最终数据的保存又一定是要到磁盘的。CPU的运行速度与IO读取速度不匹配,为了解决这个问题就产生了诸多的解决方案或者说我们将它称之为“奇技淫巧”,让数据的存储于读取过程变得复杂了起来。然而这并不只是Mysql存在的问题,或许可以说他是所有计算机领域都存在的问题,诸多的解决方案以及编程思想也都为此而生。
在编程中缓存的存在就是为了解决各种场景下的不匹配问题,不管是redis还是通过基本的数据结构实现的缓存,都是如此,而在Mysql的这个场景中他一系列结构的设计与落实,也都是围绕缓存展开。试想,如果我们对每一条数据的操作都需要进行一次IO,那性能将会是指数级的下降。所以就有了一个基础的解决思路,可以将很多条数据积攒到一起,之后再进行一次对磁盘的IO操作。那么这些数据又应该如何组织起来进行管理——页,如果数据量非常大,仅仅通过一级的组织那肯定还是不能满足需求的就如同一个集团军如果然所有的班长都向军长做汇报,那军长管的过来吗?所以要有一层一层的组织——军、师、旅、团、营、连、排。而数据库上的索引一定程度上起着上述所例举的组织起的作用。通过索引以及页,就可以对数据进行简单的管理,那么在缓存与磁盘之间他们何时进行操作——被修改的数据何时刷新到磁盘、那些数据放到缓冲区、这些操作相关的都是依赖于链表——free、LRU链表等。电脑中对记录的操作就如同对人的管理!
二、底层的存储结构
Mysql的CPU与磁盘之间交互的基本单位是“页”,这个页中会有很多条的记录,一个页的大小通常为16KB。在这里将数据库表中的一条数据称之为一条记录,一个页中可以有很多条记录,除过存储这些记录,还有用来记录页的通用信息、页的专属信息、页中的最大最小记录、以及校验页是否完整的信息。
在底层中数据的存储是有顺序的、这个顺序通常按照主键的大小来进行。如果没有设计主键,那么会找是否有不为空且有唯一性的列,就会采用这个列来排序。如果还没有的话系统会采用一个隐藏列——row_id。最大最小的记录也会在进行数据索引的时候起作用。
在页与记录之间还有一个组的概念,我们可以理解为在连排与战士之间还有一个班长,这个班是一个最小的团体但是他并不是一个战术单位、例如我们可以将军队中的营理解为一个基本的战术单位,而班仅仅只是一个人员组织上的单位。是班长的同时他也是一个普通的战士,这个战士有一个头衔(n_owned)叫班长,班长会在他的头衔上记录一下当前他的这个班有多少个成员,而这个信息也只有做班长的才能够记录。而与n_owned这个信息类似的还有deleted_flag、heap_no(如同是在班中的排序)、record_type(兵种类型)、next_record等。而这些辅助信息是存在于每一个条记录中,这些辅助信息就像军队中一个士兵在军队体系中的基本信息。属于他个人自身的信息就是我们所要存储的表中直观可见的信息。
有了记录、堆组、页这些基础的数据结构之后,就可以实现对数据更快的一个管理,但是当明对更多数据的时候只管理到“营、班”级别肯定是不行的,所以还需要更高的级别,这时候我们就会将可以以页为基础单位组织成B树、B+树的结构。B树、B+树的结构可以快速的将数据的位置定位到页的层面。而页中的数据又有班组的概念,这些班组的信息登记在页的Page Dictroy中,可以使得页中的数据实现一个跳跃查找,从而快速确定页中的记录在哪里。
页的数据是放在缓冲池中,在缓冲池中每一个页有对应一个控制块,控制块记录页中的一些详细信息——是否有需要更新的元素等这些信息。页的信息是有不同的状态的,在Mysql中会通过维护一个Free链表来记录目前有哪些空闲的页可以使用;Flush链表记录那些页中的数据是需要刷新到磁盘;LRU链表中则记录这那些数据应该放在缓冲池中,而LRU链表中又有冷区域、热区域的概念!
试想在一个大的服务器上,他有很多的资源——cpu、内存(为缓冲池提供更大的空间),如果要是只提供一个“资源”源让所有竞争者来竞争,这就会出现高并发场景下的一些问题,在Mysq中处于对这一情况的考虑,在缓冲池与页之间又有了一个叫做chunk的结构概念。这里的实现类似于Java并发中的LongAdder!
三、结构之上的CURD
我们通常说的CURD,可以划分为两类一种是对数据更改了的一种是没有更改的——查询。这里不去区分数据库底层操作中的一些基本的概念。人就仅仅是通过简单地语言描述一下他们的一个操作逻辑——眼睛会欺骗你的!
当进行数据读取的时候并不是说我们查询那些数据,CPU就只读那些数据到磁盘,这里会存在一个预读的概念,就是说程序根据一定的机制将接下来可能会要的数据提前读取到缓冲池中。预备读的类型可以分为两类——线性预读、随机预读。这里说的线性、随机性,可以理解为判断是否预读的一个标准,而非是锁读取数据的一个标准。这里线性预读默认是关闭的。说到预读就应该补充一下结构中关于区的一个概念,区是介于表空间与页的一个“单位”。在上面我们说了LRU中存在冷区、热区,他们的存在主要是为了避免刷盘——大量的数据过来、将缓冲池中原本缓冲的数据都冲掉。同时规定热区中的数据都来源于冷区,通过对一个页中访问的时间是否都集中于一个规定的时间段内来决定是否要将这个页从冷区挪到热区。
就像SQL的语句中有group by以及一些聚合函数,与这些有关的运算应该也都是先将大面积的数据读取到缓冲区然后再进行计算返回吧!
对数据修改的一些操作,并没有直接的保存到磁盘,也是先保存到了缓冲区,在一定的条件或是机制下会将buffer中的数据读取到磁盘中。在这里面Flush链表起着重要的作用。不过这里我们应该想到这样的操作逻辑是不保险的,如果我们的数据已经写到了buffer中事务也显示成功并返回到业务代码。然而这时候系统发生了故障,那么这时候数据是不是就丢失了?那么我们应该采用什么样的措施呢?Mysql是结合redo、undo日志实现的,简单的来说就是事务提交的时候会保证redo这些日志持久化到磁盘,如果持久化成功了,事务就会结束并返回成功。就算这时候系统出现了异常,他也是可以根据这些日志将数据恢复过来的,如果要是连这些日志都没有持久化成功,那么事务就直接返回失败好了!这样就也不存在数据丢失!
哈哈哈是不是有点这个意思了: 计算机中没有什么是加一层解决不了的,如果解决不了那就再多加一层!