上一篇,我们学习了innodb文件系统的大的框架,知道了innodb文件系统是由一些log和每个表的ibd(16K的整数倍)等文件组成的。那么这些文件,里面是怎么样的呢?
表数据文件IBD
上一篇我们看到了,当你新建一个库时,首先文件系统上会多一个以库名命名的文件夹。里面有ibd、frm文件,每个表对应一个ibd文件。
那么当我们新建库时,innodb做了什么呢?会初始化一个名叫ibdata1的表空间文件,用来存储所有该库的表数据,以及一些系统表,列等系统信息。还会存储将来做事务时用来保证数据完整性的回滚段数据。
上一篇我们学过了,不同的表既可以共用一个ibd文件,也可以每个表自己一个ibd文件,默认是一个表一个。
但是需要注意的是,虽然是一个表一个ibd,但这个ibd里只存储了该表的B+树数据、索引、插入缓存等信息,其余的信息如列、属性等信息还是存储在默认的ibdata1里面的。
那么ibd里到底是什么数据呢?答案就是该表的所有索引数据。
相信很多人就要迷茫了,索引不就是相当于目录吗,那每行数据跑哪里去了。要是研究过B+ tree的人应该不会迷茫,没看过的可能还以为数据库是个TXT文件呢,一行数据占文本一行。其实不是的,索引里就包含了所有数据。
如果有迷茫的,还是先看这一篇,https://blog.csdn.net/tianyaleixiaowu/article/details/94552675 或者在网上找找B+ tree原理看看,后面也会讲。B+ tree的叶子节点,就会存放所有的数据。整个表,其实就是一棵B+ tree,一个ibd就是1-N个b+ tree。N等于你的索引数量。
当你新建一个表时,你会给表创建一个主键primary Key,然后这个key就带着整行数据占据着一块空间,作为B+ tree的一个叶子节点里元素,将来要找数据,就要靠这个主键了。你可以理解为一个key-value键值对,key就是主键,value就是整行数据。如果你根本就没创建主键(不推荐),那innodb也会给你分配一个RowId来作为将来找它的主键,虽然你看不到。
这棵拥有全量数据的b+ tree,就是将来提供数据的树了,一般来说,这棵树最大也就4层,3层就能存2千万数据了,4层就很多个亿了,将来通过主键查询时,通过2-4次IO就能找到数据行。这个索引树,我们给它起了个名字——聚簇索引。
是不是终于看到面试点了,谈谈聚簇索引和二级索引(非聚簇索引)。
二级索引就是你平时创建的那些索引了,可以建多个,建在一个列或者多个列上。这些索引也会构成B+ tree,和聚簇索引的区别就是它不需要存每行的详细数据,它的叶子节点只需要存primary key或(rowId)(当然还有主键索引所在磁盘的位置PageNo)。将来能让你通过这个索引找到数据行的ID就行了。要查数据时,就根据ID去聚簇索引那棵大树去查就是了,这就是回表。
最后,索引是方便查询的,索引列的数据不适合放大的,它占用的空间一多,那么B+ tree一层中能放的个数就越少。索引列一多,插入就越慢,如果没有索引,插入一行时只需要对主键进行排序即可。如果有很多列都有索引,那么插入时,就要做很多次排序。
数据文件格式
之前已经知道了,磁盘最小单位是512字节,操作系统是4KB,mysql里最小的是page(页面)有16K。现在也知道了ibd就是放索引树的,那总不能一个树就摊在一个txt文档里吧,所以必须还要有一种文件组织结构。所有的数据都放在page里,得用一种规则来把N个page连一起,让它们形成一些关联,才能将来好查询,要先找到page,再找到page内的数据。
文件格式包括段、簇、页面。
段
这是一个逻辑概念,并不是一个实际存在的文件。它是构成索引的基本元素,当你创建一个B+ tree索引时,会同时创建两个段。
他们是内节点段和叶子段,内节点段用来管理B+ tree里非叶子节点的数据,叶子段用来管理叶子节点的数据。叶子和非叶子应该知道是什么了,里面存的东西都是什么应该也清楚,如果还不知道,建议切蛋自尽。
内节点段负责管理那些非叶子节点的分裂啊、增长啊、删除啊,叶子端就负责行数据的相关动作。
簇
这玩意比段要低一级,段是个逻辑概念,段内部就是多个簇(Extent)组成的。一个簇是物理上连续分配的一段空间,(连续很重要)。每个段至少会有一个簇,在创建一个段时就会创建一个默认的簇,一个簇的大小默认是64个Page(页面),所以一个簇就是64*16K的硬盘空间。
当往段里写入数据后,就是往簇里写数据,簇可是硬盘空间。当一个簇已经放不下时,就会再来一个簇,等于又多了一块64*16K的连续硬盘空间。段可以无限大,注意,每个簇是一块连续的硬盘空间,但多个簇之间可不是连续的。
同样,两个段之间,在硬盘上也没有什么关系。
页面
每个簇里有64个页面,都会进行编号,页面就是最小的存储单元了。这个簇里的每个页面都是连续的一段空间,往里面写数据时,就会一个页面一个页面的写入,一个页面占满了,就去下一个页面。一个页面16K,放主键如int型能放好几千,放一行数据,譬如1K一行,能放十几行。这里就需要注意了,一行数据尽量不要过大,一旦跨page了,就会对性能产生影响。本来一个page就能查出来,结果每次要查2个page,那性能就丢了一倍。
其他
一个表,占用一个表空间,创建一个表空间时,至少有一个文件(0号文件),这个文件的第一个页面page,page_no=0,这个page中存储了这个表空间中,所有段、簇、页管理的入口。里面有如下信息:
FSP_SPACE_ID:表空间的唯一ID号
FSP_SIZE:当前表空间的总页面数
FSP_FREE:一个链表(存储了所有空闲簇(空闲的、新分配的),反正就是所有暂时没用的簇的指针,不用了就放这个链表,要用时,就从这里拿)
FSP_FREE_FRAG:上面那个是完全空闲的簇,这个里面是半满的,里面不是全空,是有部分页面被占用的
FSP_FULL_FRAG:所有被占满了的簇的链表头指针
等等,还有很多信息。目的当然就是将各个空间都管理起来,满的空的,等等各种指针,将来好做数据的增删改查。
下一篇,就进入到b+ tree内部,和每个page内部,去看看里面到底是什么。