当前位置: 首页 > 科技观察

分库分表实战:追本溯源——MySQL索引是如何形成的?

时间:2023-03-17 00:25:27 科技观察

问题分析:为什么查询慢?我们知道MySQL查询的整个过程,也知道整个过程的瓶颈在于磁盘IO,那么如何减少磁盘IO的次数呢?答案是索引。通过正确使用索引,我们可以有效地将磁盘IO次数降低到一个恒定的水平,这样查询速度就会变得非常快。接下来,让我们更深入地了解一下MySQL索引。为什么没有索引时查询会变慢?在磁盘中,MYSQL存储数据的基本单位是数据页,数据放在数据页中。每个数据页中有很多数据行,如下图所示:可以看到,在数据页的数据区中有很多数据行,这些数据行对应着数据页中的一行数据数据表,它们都是通过单向链表连接组合的。而多个数据页通过一个双向链表连接起来,如下图所示:一个数据页的默认大小是16KB,16KB的大小肯定是不可能装下整张表的数据的,所以里面的数据MYSQL的表,比如我们订单表中的订单数据,会通过这样一个双向链表的结构,放在多个数据页中。如果我们要查询一条数据,就得沿着双向链表一条一条地找。比如我们要查询主键为1的那条数据,可以从数据页1开始查询,首先把数据页1从磁盘加载到MYSQL内存中。如果发现在数据页1中没有找到我们要的数据,就要沿着双向链表查找。最坏的情况是我们顺着数据页1,数据页2,一直到最后,我们在最后一个数据页100找到我们想要的数据,但是在这之前,我们得把数据页1一直放到数据页100,loading这100个数据页通过磁盘IO进入内存相当于全表扫描。即使MYSQL有预读机制,也可能提前发生几次磁盘IO,提前加载一些数据页到内存中,但是这100个数据页也会造成至少几十次磁盘IO,而这个过程磁盘IO非常消耗性能。MYSQL的索引是怎么形成的?有没有什么办法可以让我们不用扫描整张表就可以最快的定位到数据页呢?这件事将由索引处理。沿着数据页的双向链表数据结构逐一查找,太费力了。我们可以为每个数据页创建一个目录。查询数据时,先到目录下看有没有你要的数据。这样是不是快了很多?首先,我们看一下数据页的内部结构:例如,我们以数据页1为例。数据页1中有很多数据行,数据行通过指针连接起来,以单向链表的形式组织起来,而且单向链表中的主键必须保证是有序的,不可能对无序数据建立索引。可以看出,数据行前面的0、2、3表示记录的类型,即数据行的类型,0表示普通类型,即表中的一行普通数据,2表示最小的记录,3表示最大的记录,因为我们都知道数据行对应的主键是有序的。这里为了展示索引方便,我们假设每个数据页有20条数据。我们建立索引后,如下图所示:可以看到,索引页会记录每个数据页中最小主键的值,即id。以及对应的数据页号,索引页起到我们刚才说的数据页目录的作用。索引页其实就是一个数据页,只不过是我们用来存放数据页的目录信息而已。您可以在索引页中看到记录类型。除了2和3之外,还有1。1表示目录的类型。因为它指向特定的数据页。而如果数据页很多,目录信息肯定放不下一个索引页。这时MYSQL会将索引页之外的目录信息放到一个新的索引页中,然后向上扩展一个索引页,如下图:可以看出数据页3和数据页的目录信息4放在索引页2,然后索引3作为扩展索引页,记录索引页1和索引页2中的最小主键值和索引页码,也就是记录在索引页上的信息索引页3相当于上一级索引的目录信息。如果索引页3的容量不够,此时索引页3以外的信息也会被放入一个新的同级索引页,然后向上扩展一层,如下图所示:可以看到,indexpage3的信息放不下之后,会放到indexpage4,然后向上展开一层index5。索引5存放索引3和索引4的目录信息,规则相同。在图中,我们可以看到索引页是一层层展开的,看起来像一棵树。这就是我们常说的B+索引树。图中索引的高度是3层,一般是可以的,存几千万级的数据。为什么索引查询可以变得更快?我们看刚才的图:有了索引之后,如果我们要查询主键为1的数据,可以从B+索引树的最顶层索引页开始查询。如图,我们可以先将索引页5加载到内存中,此时会发生一次磁盘IO,然后通过二分法,根据主键值1,快速到索引页5,并且每个目录项中的最小主键值进行比较,然后找到下一个索引页3,然后通过索引页3定位下一个索引页1。在这个B+索引树中,通过二分法比较最小主键值,最终在index页1中找到原来主键值为1的数据位于数据页1中,此时我们将数据页1有针对性的加载到内存中,然后可以在内存中找到主键为1的数据。这样我们可以发现,我们不需要像之前那样扫描整张表,将数据页一个一个地加载到内存中。相反,我们可以使用索引页通过高效的二分查找来快速定位数据。哪个数据页。并且在这个过程中我们也发现,即使是千万级的数据量,我们也可以只用个位数的磁盘IO就可以查询到数据,这也是为什么使用索引后查询效率明显提升的原因。所以,接下来sql优化的关键就是想办法让sql语句使用索引来查找数据,这样查询的效率会提高,但是有时候有很多因素会导致sql语句不能使用索引正常。归结到sql优化的一个关键点。结束语嗯,通过刚才的索引原理,我们知道数据量是千万级别,B+树基本就是三四层。如果索引正常使用,性能通常不会有问题,所以问题的原因基本可以确定是因为SQL没有使用索引,也就是索引失效。