mysql系列3---索引

103 阅读5分钟

什么是索引?有什么用处?

先简单的介绍下索引的概念,这么说吧,大家都知道一般mysql存储的时候都有主键id(即便你不设置主键,也会有隐藏的默认列),一般都是自增,那底层数据存储的时候,大约就是按照这个id从小到大存的,结合上一章的数据页的概念,我们可以简单的认为,数据是由很多页构成,每个页内的数据都是按主键顺序存的,页与页之间也是有双向链表链接的,下一个页的主键一定比上一个页大。有了这些前提,假设我要找id=xx的数据,那就直接通过二分法就能快速定位这个id所在的页,所在的槽,所在的行,就能读取这个数据了。其实这个就是索引的用途。(其他字段想加索引,无非是建立了一个以这个字段排序的数据页罢了,只不过只有这个字段和主键的值,没有其他信息)

当然实际上肯定比这个要复杂,索引页其实和数据页并没什么区别,也是按照上一章的结构来的,只不过user records存的不是你存的数据罢了。在你创建一个表的时候,这个时候会有一个数据页,当然里面是空的。当你insert的数据填充完这个页的时候(比如存了1-10,你想存入11),这个数据页就会页分裂(主要是得有个根目录),他会新创建一个数据页b,先把数据都复制给b,然后b会分裂b和c,可能b里面就是存了1-10的数据,c存的11,当然同时那个根目录的数据页也要改,改成啥呢?他会记录b的页号及最小值,那真实数据他就不存了,存些目录就行。比如b-1,c-11,这样假设你要查询id=11的值,他直接查根目录就知道数据在哪个页(比如你有100个页,没有目录比较,你得到每个页都去看看),根目录数据查询可以用二分法,因为也是从小到大排的。假设数据页实在太多,连目录都放不下了,那很简单放不下了就再开一个数据页n,把目录数据都给他,让他再开一个数据页m,跟刚才一样,根目录的数据继续改成n和m的最小值。这就变成3层了。咱们假设目录能放1000个数据,数据页能放100个真实数据,那10001000100=100000000 这就1亿数据了。这里只有最下层才有真实数据。这就是b+树的模型,叶子结点有数据。

b树和b+树区别在哪?

b树的叶子结点和非叶子结点都会存储数据,b+则不然,简单的来说,就是b树存储的那些根目录,目录,不止有目录还有完整数据,好处也有,假如有三层树,我们查询id=5的正好在根目录就有,那完全就不用往下再遍历了,直接return,1下搞定。如果是b+,起码也要继续往下走到最下面,3次io。当然既然有有点肯定优缺点,咱们之前计算的目录能存1000条,现在肯定只能存100条了那就变成100100100=1000000 一百万数据,想存1亿,可能要变成4层树了,而且假如你想排序,你可以试着画一下图,有的在1层,有的在3层,并不好排序,而如果大家都在一层,我的数据页之间就能有序,sql常见的排序之类的操作就很容易了。

什么是索引下推?

比如建立了一个联合索引abc,现在有个查询条件select * from xx where a=xx and c=xx,按照索引来看,a=xx的数据都是排好序的,所以a=xx的数据在索引这都是相邻的,可以命中索引,但是这些数据中,c不一定是有序的,理论上是需要查完a=xx的数据后回表,再判断c是否满足,但是联合索引已经有c的数据,完全可以当时就去判断一下,就能减少回表次数,这个优化就是索引下推(ICP)。那如果是where a<xx and b=xx 会走什么索引?首先a肯定能走范围索引,但是b=xx能帮我们减少扫描行数吗?不行。顶多是索引下推能帮我们判断下,但并不会减少扫描行数。如果是a<=xx and b=xx呢?比如a<=xx应该扫描出来10条数据,但其实后面3条数据假如正好就是a=xx的,这个时候读取第一条,发现符合a=xx and b =xx,读第二条发现a=xx但是b!=xx了,就不用继续往下扫描了,他减少了扫描行数。(说白了也就是符合那个a=xx的数据才会有可能较少一些扫描,其他该扫还是得扫)

什么是覆盖索引?

正常索引,放的只有索引列的值+一个主键值查完索引还得到聚簇索引去找完整数据,毕竟光看目录找不到答案。但是假如你要查的东西,目录就有,那就不用再回表了啊,比如你select 索引列,就不用回表,这个叫索引覆盖。

索引有几个级别?有什么区别

这个有点简单,const,ref,ref_or_null,range,index,const是常量级别,只有主键的等值查询或者唯一索引的非空查询才会有,如果是查is null的话,就是ref_or_null,普通二级索引的等值查询就是ref级别,范围索引是range,如果是没走索引,但是查询的字段正好在索引里(简单的来说只遍历目录就够了的那种),就是index。

索引合并是啥?查询sql只会走一个索引吗?

很明显不一定,但是条件也很苛刻。大概三种场景会合并,一种是select * from x where a=x and b=x 这种,a和b分别有自己的索引。而且a和b这种主键还是有序的,就可以取id集合交集。第二种就是or这种,这个很明显,select * from x where a=x or b=x 如果只能走一个索引,那肯定要全表扫描了。所以他会把a和b都去走下索引,然后把查出的id集合取并集。第三种是第二种的升级版,因为第二种前提是主键有序,也就是说select * from x where a>x or b<y 就不行了,为了优化,设计了一种先去查a>x的 把id排序下,再去查b<y 也把id排序,就行了。至于第一种为啥不能也去排序?反正设计者没设计。

多表关联执行过程

都知道连接分为内连接和外连接,区别就是内连接会把双方符合的数据才会展示,外连接则一定会把驱动表的数据都展示出来。比如select * from a left join b on xx=xx 假设找a为驱动,他会先找a的一条数据,再去通过关联id去找b 如果这个字段没加索引,那就相当于每一个a的数据都对应b的一次全表扫描。为了优化,也会申请一块buffer,把a的数据加载到内存,这样就能一批一批的比。至于where 和on的区别,无非就是where指的是无论是驱动表还是被驱动表,不符合条件的一律不展示,on则是针对被驱动表,不符合的填null。