Mysql基础(十四):索引

目录

写在前面

1、深入研究索引之前,先来看看磁盘数据页的存储结构

2、假设没有任何索引,数据库是如何根据查询语句搜索数据的?

3、不断在表中插入数据时,物理存储是如何进行页分裂的

4、基于主键的索引是如何设计的,以及如何根据主键索引查询?

5、索引的页存储物理结构,是如何用B+树来实现的?

 6、更新数据的时候,自动维护的聚簇索引到底是什么?

7、针对主键之外的字段建立的二级索引,又是如何运作的?

8、插入数据时到底是如何维护好不同索引的B+树的?

9、一个表里是不是索引搞的越多越好?那你就大错特错了!

10、深入理解联合索引查询原理以及全值匹配规则

11、几个最常见和最基本的索引使用规则

12、当我们在SQL里进行排序的时候,如何才能使用索引?

13、当我们在SQL里进行分组的时候,如何才能使用索引?

14、回表查询对性能的损害以及覆盖索引是什么?

15、设计索引的时候,我们一般要考虑哪些因素呢?(上)

16、设计索引的时候,我们一般要考虑哪些因素呢?(中)

17、设计索引的时候,我们一般要考虑哪些因素呢?(下)

18、案例实战:陌生人社交APP的MySQL索引设计实战(一)

19、案例实战:陌生人社交APP的MySQL索引设计实战(二)

20、案例实战:陌生人社交APP的MySQL索引设计实战(三)

21、案例实战:陌生人社交APP的MySQL索引设计实战(四)

写在前面

数据库的索引原理以及查询原理

1、深入研究索引之前,先来看看磁盘数据页的存储结构

   磁盘上的数据文件中的数据页的物理存储结构:

        数据库最终所有的数据(包括我们建的各种表以及表里的数据)都是要存放在磁盘上的文件里的,然后在文件里存放的物理格式就是数据页,那么大量的数据页在磁盘文件里是怎么存储的?

       大量的数据页是按顺序一页一页存放的,然后两两相邻的数据页之间会采用双向链表的格式互相引用

        其实一个数据页在磁盘文件里就是一段数据,可能是二进制或者别的特殊格式的数据,然后数据页里包含两个指针,一个指针指向自己上一个数据页的物理地址,一个指针指向自己下一个数据页的物理地址

DataPage: xx=xx, xx=xx, linked_list_pre_pointer=15367, linked_list_next_pointer=34126 ||
DataPage: xx=xx, xx=xx, linked_list_pre_pointer=23789, linked_list_next_pointer=46589 ||
DataPage: xx=xx, xx=xx, linked_list_pre_pointer=33198, linked_list_next_pointer=55681

        每个数据页在磁盘文件里都是连续的一段数据。

        每个数据页里,可以认为就是DataPage 打头一直到 || 符号的一段磁盘里的连续的数据,你可以认为每一个数据页就是磁盘文件里这么一段连续的东西。然后 每个数据页,都有一个指针指向自己上一个数据页在磁盘文件里的起始物理位置 ,比如linked_list_pre_pointer=15367,就是指向了上一个数据页在磁盘文件里的起始物理位置,那个 15367可以认为就是在磁盘文件里的position 或者 offset ,同理, 也有一个指针指向自己下一个数据页的物理位置

        然后一个数据页内部会存储一行一行的数据,也就是平时我们在一个表里插入的一行一行的数据就会存储在数据页里,然后数据页里的每一行数据都会按照主键大小进行排序存储,同时每一行数据都有指针指向下一行数据的位置,组成单向链表

2、假设没有任何索引,数据库是如何根据查询语句搜索数据的?

        数据页之间是组成双向链表的,然后数据页内部的数据行是组成单向链表的,而且数据行是根据主键从小到大排序的。 然后每个数据页里都会有一个页目录,里面根据数据行的主键存放了一个目录,同时数据行是被分散存储到不同的槽位里去的,所以实际上每个数据页的目录里,就是这个页里每个主键跟所在槽位的映射关系(因为一个槽位含多个数据行,个人理解是多个主键对应一个槽位即多个数据行)

假设数据库里那个表就没几条数据,那个表总共就一个数据页。

  • 根据主键查找一条数据,首先就会先到数据页的页目录里根据主键进行二分查找,然后通过二分查找在目录里迅速定位到主键对应的数据是在哪个槽位里,然后到那个槽位里去,遍历槽 位里每一行数据,就能快速找到那个主键对应的数据了。每个槽位里都有一组数据行,你就是在里面遍历查找就可以了。
  • 据非主键的其他字段查找数据,此时你是没办法使用主键的那种页目录来二分查找的,只能进入到数据页里,根据单向链表依次遍历查找数据了,这就性能很差了。

正常一个表里往往都是有大量数据的,可能有多达成百上千个数据页,这些数据页就存放在物理磁盘文件里。

  • 假设没有建立任何索引,那么无论是根据主键查询,还是根据其他字段来条件查询,直接从第一个数据页开始遍历所有数据页,从第一个数据页开始,你得先把第一个数据页从磁盘上读取到内存buffer pool的缓存页里来。然后你就在第一个数据页对应的缓存页里,按照上述办法查找
  • 根据主键查找的,你可以在数据页的页目录里二分查找。
  • 根据其他字段查找的,只能是根据数据页内部的单向链表来遍历查找。 假设第一个数据页没找到你要的那条数据呢? 只能根据数据页的双向链表去找下一个数据页,然后读取到 buffer pool 的缓存页里去,然后按 一样的方法在一个缓存页内部查找那条数据。 如果依然还是查找不到呢? 那只能根据双向链表继续加载下一个数据页到缓存页里来了,以此类推,循环往复。
全表扫描: 没有任何索引数据结构时,无论如何查找数据,就是根据双向链表依次把磁盘上的数据页加载到缓存页里去,然后在一个缓存页内部来查找那条数据 。最坏的情况下,你就得把所有数据页里的每条数据都得遍历一遍,才能找到你需要的那条数据,这就是全表扫描!

3、不断在表中插入数据时,物理存储是如何进行页分裂的

        在一个表里不停的插入数据的时候,会涉及到一个页分裂的过程,也就是说这个表里是如何出现一个又一个的数据页的。 正常情况下我们在一个表里插入一些数据后,他们都会进入到一个数据页里去,在数据页内部,他们会组成一个单向链表

        里面就是一行一行的数据,刚开始第一行是个起始行,他的行类型是2,就是最小的一行,然后他有一个指针指向了下一行数据,每一行数据都有自己每个字段的值,然后每一行通过一个指针不停的指向下一行数据,普通的数据行的类型都是0,最后一行是一个类型为3的,就是代表最大的 一行。

页分裂是什么意思呢?

你不停的在表里插入数据,那么刚开始是不是就是不停的在一个数据页插入数据?接着数据越来越多,越来越多,此时就要再搞一个数据页了

索引运作的一个核心基础就是要求你后一个数据页的主键值都大于前面一个数据页的主键值。
  • 如果你的主键是自增的,那还可以保证这一点,因为你新插入后一个数据页的主键值一定都大于前一个数据页的主键值。
  • 如果你的主键并不是自增长的,所以可能会出现你后一个数据页的主键值里,有的主键是小于前一个数据页的主键值的。
比如在第一个数据页里有一条数据的主键是 10 ,第二个数据页里居然有一条数据的主键值是 8,此时就会出现一个过程,叫做 页分裂 ,就是万一你的主键值都是你自己设置的,那么在增加一个新
的数据页的时候,实际上会 把前一个数据页里主键值较大的,挪动到新的数据页里来,然后把你新插入的主键值较小的数据挪动到上一个数据页里去,保证新数据页里的主键值一定都比上一个数据页里的主键值大

举例:

假设新数据页里,有两条数据的主键值明显是小于上一个数据页的主键值

        此时就会出现页分裂的行为,把新数据页里的两条数据挪动到上一个数据页,上一个数据页里挪两条数据到新数据页里去。 

        页分裂的核心目标就是保证下一个数据页里的主键值都比上一个数据页里的主键值要大。

4、基于主键的索引是如何设计的,以及如何根据主键索引查询?

        索引索引------》索引原理----〉查询原理-----》SQL调优

针对主键设计一个索引:针对主键的索引实际上就是主键目录,就是把每个数据页的页号,还有数据页里最小的主键值放在一起,组成一个索引的目录。

        查找过程:此时查找直接就可以到主键目录里去搜索,比如你要找id=3的数据,此时就会跟每个数据页的最小主键来比,首先id=3大于了数据页2里的最小主键值1,接着小于了数据页8里的最小主键值4。所以既然如此,你直接就可以定位到id=3的数据一定是在数据页2里的!

        假设你有很多的数据页,在主键目录里就会有很多的数据页和最小主键值,此时你完全可以根据 二分查找的方式来找你要找的id到底在哪个数据页里!所以这个效率是非常之高的,而类似上图的主键目录,就可以认为是主键索引

5、索引的页存储物理结构,是如何用B+树来实现的?

        正常表里的数据可能很多很多,比如有几百万,几千万,甚至单表几亿条数据都是有可能的,所以此时你可能有大量的数据页,然后你的主键目录里就要存储大量的数据页和最小主键 值,这怎么行呢?

        所以实际上是采取了一种把索引数据存储在数据页里的方式来做的。也就是说,你的表的实际数据是存放在数据页里的,表的索引也是存放在页里的,此时索引放在页里之后,就会有索引页,假设你有很多很多的数据页,那么此时你就可以有很多的索引页

        又会存在一个问题了,你现在有很多索引页,但是此时你需要知道,你应该到哪个索引页里去找你的主键数据,是索引页20?还是索引页28

        于是接下来我们又可以 把索引页多加一个层级出来,在更高的索引层级里,保存了每个索引页和索引页里的最小主键值。

        此时,假设我们要查找id=46的,直接先到最顶层的索引页35里去找,直接通过二分查找可以定位到下一步应该到索引页20里去找,接下来到索引页20里通过二分查找定位,也很快可以定位到数据应该在数据页8里,再进入数据页8里,就可以找到id=46的那行数据了。

        假如你最顶层的那个索引页里存放的下层索引页的页号也太多了,怎么办呢?

        此时可以再次分裂,再加一层索引页。

        有发现索引页不知不觉中组成了多个层级,搞的是不是有点像一棵树? 这就是一颗B+,属于数据结构里的一种树形数据结构,所以一直说MySQL的索引是用B+树来组成的

主键索引来举例:

        当你为一个表的主键建立起来索引之后,其实这个主键的索引就是一颗B+树,然后当你要根据主键来查数据的时候,直接就是从B+树的顶层开始二分查找,一层一层往下定位,最终一直定位到一个数据页里,在数据页内部的目录里二分查找,找到那条数据。 这就是索引最真实的物理存储结构,采用跟数据页一样的页结构来存储,一个索引就是很多页组成的一颗B+树

 6、更新数据的时候,自动维护的聚簇索引到底是什么?

        假设我们要搜索一个主键id对应的行,此时你就应该先去顶层的索引页88里去找,通过二分查找的方式,很容易就定位到你应该去下层哪个索引页里继续找

        定位到了下层的索引页35里去继续找,此时在索引页35里也有一些索引条目的,分别都是下层各个索引页(202859)和他们里面最小的主键值,此时在索引页35的索引条目里继续二分查找,很容易就定位到,应该再到下层的哪个索引页里去继续找。

        可能从索引页35接着就找到下层的索引页59里去了,此时索引页59里肯定也是有索引条目的,这里就存放了部分数据页页号(比如数据页2和数据页8)和每个数据页里最小的主键值。此时就在这里继续二分查找,就可以定位到应该到哪个数据页里去找

        比如进入了数据页2,里面就有一个页目录,都存放了各行数据的主键值和行的实际物理位置 此时在这里直接二分查找,就可以快速定位到你要搜索的主键值对应行的物理位置,然后直接在数据页2里找到那条数据即可了。

这就是基于索引数据结构去查找主键的一个过程,其实 最下层的索引页,都是会有指针引用数据页的,所以实际上索引页之间跟数据页之间是有指针连接起 来的。

        索引页自己内部,对于一个层级内的索引页,互相之间都是基于指针组成双向链表的。这个同一层级内的索引页组成双向链表,就跟数据页自己组成双向链表是一样的。

         假设你把索引页和数据页综合起来看,他们都是连接在一起的,看起来就如同一颗完整的大的B+树一样,从根索引页88开始,一直到所有的数据页,其实组成了一颗巨大的B+树。在这颗B+树里,最底层的一层就是数据页,数据页也就是B+树里的叶子节点了!

所以,如果 一颗大的B+树索引数据结构里,叶子节点就是数据页自己本身,那么此时我们就可以称这颗B+树索引 聚簇索引
        其实在InnoDB 存储引擎里,你在对数据增删改的时候,就是直接把你的数据页放在聚簇索引里的 ,数据就在聚簇索引里,聚簇索引就包含了数据 !比如你插入数据,那么就是在数据页里插入数据。如果你的数据页开始进行页分裂了,他此时会调整各个数据页内部的行数据,保证数据页内的主键值都是有顺序的,下一个数据页的所有主键值大于上一个数据页的所有主键值。同时在页分裂的时候,会维护你的上层索引数据结构,在上层索引页里维护你的索引条目,不同的数据页和最小主键值。
        然后如果你的数据页越来越多,一个索引页放不下了,此时就会再拉出新的索引页,同时再搞一个上层的索引页,上层索引页里存放的索引条目就是下层索引页页号和最下主键值。
按照这个顺序,以此类推,如果你的数据量越大,此时可能就会多出更多的索引页层级来。
         一般索引页里可以放很多索引条目,所以通常而言,即使你是亿级的大表,基本上大表里建的索引的层级也就三四层而已
         聚簇索引默认是按照主键来组织的 ,所以你在增删改数据的时候,一方面会更新数据页,一方面其实会给你自动维护B+ 树结构的聚簇索引,给新增和更新索引页,这个聚簇索引是默认就会给你建立的。

7、针对主键之外的字段建立的二级索引,又是如何运作的?

        聚簇索引:innodb存储引擎默认给我们创建的一套基于主键的索引结构,而且我们表里的数据就是直接放在聚簇索引里的,作为叶子节点的数据页。

         基于主键的数据搜索: 其实就是从聚簇索引的根节点开始进行二分查找,一路找到对应的数据页里,基于页目录就直接定位到主键对应的数据就可以了。
主键外的其他字段建立索引的原理
        假设你要是针对其他字段建立索引,比如name age之类的字段,比如你插入数据时
  • 一方面 会把完整数据插入到聚簇索引的叶子节点的数据页里去,同时维护好聚簇索引
  • 另一方面 会为你其他字段建立的索引,重新再建立一颗B+树
比如你基于 name 字段建立了一个索引,那么此时你插入数据的时候,就会重新搞一颗 B+ 树, B+ 树的叶子节点也是数据页,但是这个数据页里仅仅放主键字段和name 字段
        这是 独立于聚簇索引之外的另外一个索引B+树了 ,严格来说是 name字段的索引B+树,所
以在name字段的索引B+树里,叶子节点的数据页里仅仅放主键和name字段的 值,至于排序规则之类的,都是跟以前说的一样的。也就是说,name 字段的索引 B+ 树里,叶子节点的数据页中的 name 值都是按大小排序的,同时下一个数据页里的name 字段值都大于上一个数据页里的 name 字段值,这个整体的排序规则都跟聚簇索引按照主键的排序规则是一样的。
        name 字段的索引 B+ 树也会构建多层级的索引页,这个索引页里存放的就是下一层的页号和最
name 字段值,整体规则都是一样的,只不过存放的都是 name 字段的值,根据 name 字段值排序罢了。
         假设你要根据name字段来搜索数据 ,从 name 字段的索引 B+ 树里的根节点开始找,一层一层往下找,一直找到叶子节点的数据页里,定位到name 字段值对应的主键值。然后呢?此时针对 select * from table where name='xx' 这样的语句,你先根据 name 字段值在 name 字段的索引B+ 树里找,找到叶子节点也仅仅可以找到对应的主键值,而找不到这行数据完整的所有字段。
         还需要进行“回表” 回表就是说还需要根据主键值,再到聚簇索引里从根节点开始,一
路找到叶子节点的数据页,定位到主键对应的完整数据行,此时才能把select *要的全部字段值都拿出 来。
        因为我们根据name 字段的索引 B+ 树找到主键之后,还要根据主键去聚簇索引里找,所以一般把 name字段 这种普通字段的索引称之为二级索引,一级索引就是聚簇索引
        其实我们也可以 把多个字段联合起来,建立联合索引 ,比如name+age 此时联合索引的运行原理也是一样的,只不过是建立一颗独立的B+ 树,叶子节点的数据页里放了id+name+age,然后默认按照 name 排序, name 一样就按照 age 排序,不同数据页之间的 name+age 值的排序也如此。
        然后这个name+age 的联合索引的 B+ 树的索引页里,放的就是下层节点的页号和最小的 name+age 的值,以此类推,所以当你根据name+age 搜索的时候,就会走 name+age 联合索引的这颗 B+ 树了,搜索到主键,再根据主键到聚簇索引里去搜索。
         建立B+树,根据B+树一层一层二分查找罢了,然后不同的索引就是建立不同的B+树,然后你增删改的时候,一方面在数据页里更新数据,一方面就是维护你所有的索引。
         后续查询,你就要尽量根据索引来查询

8、插入数据时到底是如何维护好不同索引的B+树的?

  1.  开始建立一个表其实他就一个数据页,这个数据页就是属于聚簇索引的一部分,而且目前还是空的。此时你插入数据,就是直接在这个数据页里插入就可以了。
        2、然后 这个初始的数据页其实就是一个根页,每个数据页内部默认就有一个基于主键的页目录,所以 此时你根据主键来搜索都是 ok 没有问题的,直接在唯一 一个数据页里根据页目录找就行了。 然后你表里的数据越来越多了,此时你的数据页满了,那么就会搞一个新的数据页,然后把你根页面里 的数据都拷贝过去,同时再搞一个新的数据页,根据你的主键值的大小进行挪动,让两个新的数据页根 据主键值排序,第二个数据页的主键值都大于第一个数据页的主键值。

 3、此时根页就升级为索引页了,这个根页里放的是两个数据页的页号和他们里面最小的主键值,所以此时看起来如下图,根页就成为了索引页,引用了两个数据页。

4、 会不停的在表里灌入数据,然后数据页不停的页分裂,分裂出来越来越多的数据页。此时你的唯一 一个索引页,也就是根页里存放的数据页索引条目越来越多,连你的索引页都放不下了, 那你就让一个索引页分裂成两个索引页,然后根页继续往上走一个层级引用了两个索引页。

 

5、数据页越来越多,那么根页指向的索引页也会不停分裂,分裂出更多的索引页,当你下层的索引页数量太多的时候,会导致你的根页指向的索引页太多了,此时根页继续分裂成多个索引页,根页再次往上提上去去一个层级。

9、一个表里是不是索引搞的越多越好?那你就大错特错了!

使用索引------》单表查询语句的执行原理------》多表join 语句的执行原理------》 MySQL 执行计划-------》 SQL语句调优------》查询优化的调优案例-------》索引设计案例
         默认情况下MySQL给我们建立的聚簇索引都是基于主键的值来组织索引的,聚簇索引的叶子节点都是数据页,里面放的就是我们插入的一行一行的完整的数据了!
        在一个索引B+ 树中,他有一些特性,那就是 数据页和 索引页 里面的记录都是组成一个单向链表的,而且是按照数据大小有序排列的;然后 数据页和 索引页 互相之间都是组成双向链表的,而且也都是按照数据大小有序排列的,所以其实B+ 树索引是一个完全有序的数据结构,无论是页内还是页之间。
        正是因为这个有序的B+ 树索引结构,才能让我们查找数据的时候,直接从根节点开始按照数据值大小一层一层往下找,这个效率是非常高的。
索引好处是什么
直接根据某个字段的索引 B+ 树来查找数据,不需要全表搜索,性能提升是很高的。
索引坏处是什么
两个缺点,一个是空间上的,一个是时间上的。
  • 空间上而言:你要是给很多字段创建很多的索引,那你必须会有很多棵索引B+树,每一棵B+树都要占用很多的磁盘空间!所以你要是搞的索引太多了,是很耗费磁盘空间的。
  • 时间上:搞了很多索引,那么你在进行增删改查的时候,每次都需要维护各个索引的数据有序性, 因为每个索引B+树都要求页内是按照值大小排序的,页之间也是有序的,下一个页的所有值必须大于上一个页的所有值!
    • 所以你不停的增删改查,必然会导致各个数据页之间的值大小可能会没有顺序,比如下一个数据页里插入了一个比较小的值,居然比上一个数据页的值要小!此时就没办法了,只能进行数据页的挪动,维护页之间的顺序。
    • 或者是你不停的插入数据,各个索引的数据页就要不停的分裂,不停的增加新的索引页,这个过程都是耗费时间的。所以你要是一个表里搞的索引太多了,很可能就会导致你的增删改的速度就比较差了,也许查询速度确实是可以提高,但是增删改就会受到影响,因此通常来说,我们是不建议一个表里搞的索引太多的。

10、深入理解联合索引查询原理以及全值匹配规则

        一般都是设计联合索引,很少用单个字段做索引,原因是要尽可能的让索引数量少一些,避免磁盘占用太多,增删改性能太差。
举例
        
        咱们有一个表是存储学生成绩的,这个表当然有id 了,这个 id 是一个自增主键,默认就会基于他做一个聚簇索引,然后就是包含了学生班级、学生姓名、科目名称、成绩分数四个字段,平时查询,可能比较多的就是查找某个班的某个学生的某个科目的成绩。所以,我们可以针对学生班级、学生姓名和科目名称建立一个联合索引。
        下面有两个数据页,第一个数据页里有三条数据,每条数据都包含了联合索引的三个字段的值和主键值,数据页内部是按照顺序排序的。首先按照班级字段的值来排序,如果一样则按照学生姓名字段来排序,如果一样,则按照科目名称来排序,所以数据页内部都是按照三个字段的值来排序的,而且还组成了单向链表。然后数据页之间也是有顺序的,第二个数据页里的三个字段的值一定都大于上一个数据页里三个字段的值,比较方法也是按照班级名称、学生姓名、科目名称依次来比较的,数据页之间组成双向链表。
        索引页里就是两条数据,分别指向两个数据页,索引存放的是每个数据页里最小的那个数据的值,大家看到,索引页里指向两个数据页的索引项里都是存放了那个数据页里最小的值!
        索引页内部的数据页是组成单向链表有序的,如果你有多个索引页,那么索引页之间也是有序的,组成了双向链表。
            现在假设我们想要搜索:1班 + 张小强 + 数学的成绩,此时你可能会写一个类似下面的 SQL
句, select * from student_score where class_name='1 ' and student_name=' 张小强 ' and
subject_name=' 数学 '
        此时就涉及到了一个索引使用的规则,那就是你发起的 SQL语句里,where条件里的几个字段都是基于等值来查询,都是用的等于号 !而且where 条件里的几个字段的名称和顺序也跟你的联合索引一模一样!此时就是 等值匹配规则 ,上面的SQL 语句是百分百可以用联合索引来查询的。
        查询的过程首先到索引页里去找,索引页里有多个数据页的最小值记录,此时直接在索引页里基于二分查找法来找就可以了,先是根据班级名称来找1 班这个值对应的数据页,直接可以定
位到他所在的数据页。

        然后你就直接找到索引指向的那个数据页就可以了,在数据页内部本身也是一个单向链表,你也是直接就做二分查找就可以了,先按1班这个值来找,你会发现几条数据都是1班,此时就可以按照张小强这个姓名来二分查找,此时会发现多条数据都是张小强,接着就按照科目名称数学来二分查找。很快定位到下图中的一条数据,1班的张小强的数学科目,他对应的数据的id127

        然后就根据主键id=127 到聚簇索引里按照一样的思路,从索引根节点开始二分查找迅速定位下个层级的页,再不停的找,很快就可以找到id=127 的那条数据,然后从里面提取所有字段,包括分数,就可以了。
        上面整个过程就是联合索引的查找过程,以及全值匹配规则,假设你的SQL 语句的 where 条件里用的几个字段的名称和顺序,都跟你的索引里的字段一样,同时你还是用等号在做等值匹配,那么直接就会按照上述过程来找。
        对于联合索引而言,就是依次按照各个字段来进行二分查找,先定位到第一个字段对应的值在哪个页里,然后如果第一个字段有多条数据值都一样,就根据第二个字段来找,以此类推,一定可以定位到某条或者某几条数据!

11、几个最常见和最基本的索引使用规则

        当我们建立好一个联合索引后,SQL语句要怎么写才能让查询使用到建立好的索引呢?
  • 等值匹配规则:就是你where语句中的几个字段名称和联合索引的字段完全一样,而且都是基于等号的等值匹配,那百分百会用上我们的索引。
    • select * from student_score where class_name='1' and student_name='张小强' and
      subject_name=' 数学 '
  • 最左侧列匹配:假设我们联合索引是KEY(class_name, student_name, subject_name),那么不一定必须要在where语句里根据三个字段来查,其实只要根据最左侧的部分字段来查也可。
    • select * from student_score where class_name='' and student_name='',就查某个学生所有科目的成绩,这都是没有问题的。
    • select * from student_score where subject_name='',那就不行了,因为联合索引B+树里,是必须先按class_name查,再按student_name查,不能跳过前面两个字段,直接按最后一subject_name查的。
    • select * from student_score where class_name='' and subject_name='',那么只有class_name的值可以在索引里搜索,剩下的subject_name是没法在索引里找的。
  • 最左前缀匹配原则:即如果你要用like语法来查,比如select * from student_score where class_name like '1%',查找所有1打头的班级的分数,那么也是可以用到索引的。因为你的联合索引的B+树里,都是按照class_name排序的,所以你要是给出class_name的确定的最左前缀就是1,然后后面的给一个模糊匹配符号,那也是可以基于索引来查找的,这是没问题的。但是你如果写class_name like '%',在左侧用一个模糊匹配符,那他就没法用索引了,因为不知道你最左前缀是什么,怎么去索引里找啊?
  • 范围查找规则:我们可以用select * from student_score where class_name>'1' and class_name<'5'这样的语句来范围查找某几个班级的分数。 此时也会用到索引,因为我们的索引的最下层的数据页都是按顺序组成双向链表的,所以完全可以先找到'1'对应的数据页,再找到'5'对应的数据页,两个数据页中间的那些数据页,就全都是在你范围内的数据了!
    • select * from student_score where class_name>'1' and class_name<'5' and student_name>'',这里只有class_name是可以基于索引来找的,student_name的范围查询是没法用到索引的!
    • 这也是一条规则,就是你的where语句里如果有范围查询,那只有对联合索引里最左侧的列进行范围查询才能用到索引
  • 等值匹配+范围匹配的规则:如果你要是用select * from student_score where class_name='1' and student_name>'' and subject_name<'',那么此时你首先可以用class_name索引里精准定位到一波数据,接着这波数据里的student_name都是按照顺序排列的,所以student_name>''也会基于索引来查找,但是接下来的subject_name<''是不能用索引的。
所以综上所述,一般我们如果写 SQL 语句,都是用联合索引的 最左侧的多个字段来进行等值匹配+范围搜索 ,或者是基于 最左侧的部分字段来进行最左前缀模糊匹配 ,或者基于 最左侧字段来进行范围搜 索,这就要写符合规则的SQL 语句,才能用上我们建立好的联合索引!

12、当我们在SQL里进行排序的时候,如何才能使用索引?

        假设你有一个select * from table where xxx=xxx order by xxx  一个SQL 语句,似乎应该是基于 whe re语句通过索引快速筛选出来一波数据,接着放到内存或放在一个临时磁盘文件里(数据量很大时),然后通过排序算法按照某个字段走一个排序,最后把排序好的数据 返回。但是这么搞通常速度有点慢,尤其是万一你要排序的数据量比较大的话,还不能用内存来排序, 如果基于磁盘文件来排序,那在MySQL里有一个术语,叫做filesort ,这速度就比较慢了。
        
        通常而言,尽量是最好别这么搞,尤其是类似于select * from table order by xx1,xx2,xx3 limit 100这样的 SQL 语句,按照多个字段进行排序然后返回排名前 100 条数据,类似的语句常见于分页 SQL语句里,可能需要对表里的数据进行一定的排序,然后走一个 limit 拿出来指定部分的数据。 你要是纯粹把一坨 数据放到一个临时磁盘文件里,然后直接硬上各种排序算法在磁盘文件里搞一通排
序,接着按照你指定的要求走limit语句拿到指定分页的数据,这简直会让SQL 的速度慢到家了!
         通常而言,在这种情况下,假设我们 建立了一个INDEX(xx1,xx2,xx3)这样的一个联合索引,这时默认情况下在索引树里本身就是依次按照xx1,xx2,xx3三个字段的值去排序 的,那么此时你再运行 select * from table order by xx1,xx2,xx3 limit 100这样的 SQL 语句,你觉得还需要在什么临时磁盘文件里排序吗?显然是不用了啊!因为他要求也不过就是按照 xx1,xx2,xx3 三个字段来进行排序罢了,在联合索引的索引树里都排序好了,直接就按照索引树里的顺序,把xx1,xx2,xx3 三个字段按照从小到大的值获取前面100条就可以了。然后拿到100 条数据的主键再去聚簇索引里回表查询剩余所有的字段。
        所以说,在你的 SQL语句里,应该尽量最好是按照联合索引的字段顺序去进行order by排序,这样就可以直接利用联合索引树里的数据有序性,到索引树里直接按 照字段值的顺序去获取你需要的数据了。
        一些限定规则,因为联合索引里的字段值在索引树里都是从小到大依次排列的 ,所以 你在
order by里要不然就是每个字段后面什么都不加,直接就是order by xx1,xx2,xx3,要不然就都加DESC 降序排列,就是order by xx1 DESC,xx2 DESC,xx3 DESC。
  •  如果都是升序排列,直接就从索引树里最小的开始读取一定条数就可以了。
  •  如果都是降序排列,就是从索引树里最大的数据开始读取一定的条数就可以了。
  •  你不能order by语句里有的字段升序有的字段降序,那是不能用索引的。
  •  要是你order by语句里有的字段不在联合索引里,或者是你对order by语句里的字段用了复杂的函数,这些也不能使用索引去进行排序了。
        因为我们平时写一些管理系统最常见的分页语句的时候,往往就是select * from table order by xxx limit xxx,xx这样的写法,按照某个字段自动排序,同时提取每一页的数据,所以如果你可以在排序用上索引,那么可以说你的性能就会很高。

13、当我们在SQL里进行分组的时候,如何才能使用索引?

一个group by把数据分组接着用count sum之类的聚合函数做一个聚合统计。
        假设一个类似select count(*) from table group by xx SQL 语句,似乎看起来必须 把你所有的数据放到一个临时磁盘文件里还有加上部分内存,去搞一个分组,按照指定字段的值分成一组一组的,接着对每一组都执行一个聚合函数,这个性能也是极差 的,因为毕竟涉及大量的磁盘交互。
        因为 在索引树里默认都是按照指定的一些字段都排序好的,其实字段值相同的数据都是在一起的,假设要是走索引去执行分组后再聚合,那性能一定是比临时磁盘文件去执行 好多了。
        所以通常而言,对于group by 后的字段,最好也是按照联合索引里的最左侧的字段开始,按顺序排列开来,这样的话,其实就可以完美的运用上索引来直接提取一组一组的数据,然后针对每一组的数据执行聚合函数就可以了。
        SQL分组( SQL分组查询 - 郭东东郭 - 博客园
        group by和order by本质都是在group by和order by 之后的字段顺序和联合索引中的从最左侧开始的字段顺序一致,然后就可以充分利用索引树里已经完成排序的特性,快速的根据排序好的数据执行后续操作了。

总结

        平时设计表里的索引的时候,必须充分考虑到后续你的SQL语句要怎么写,大概会根据哪些字段来进行where语句里的筛选和过滤?大概会根据哪些字段来进行排序和分组?然后在考虑好之后,就可以为表设计两三个常用的索引,覆盖常见的where筛选、order by排序和group by分组的需求,保证常见的SQL语句都可以用上索引,这样你真正系统跑起来,起码是不会有太大的查询性能问题了。

        毕竟只要你所有的查询语句都可以利用索引来执行,那么速度和性能通常都不会太慢。
        对于更新语句而言,其实最核心的就是三大问题:
  • 一个是你索引别太多,索引太多了,更新的时候维护很多索引树肯定是不行的;
  • 一个是可能会涉及到一些锁等待和死锁的问题;
  • 一个就是可能会涉及到MySQL连接池、写redo log文件之类的问题。

14、回表查询对性能的损害以及覆盖索引是什么?

        不管是单列索引还是联合索引,其实一个索引就对应着一颗独立的索引B+树,索引B+树的节点仅仅包含了索引里的几个字段的值以及主键值

        即使我们根据索引树按照条件找到了需要的数据,那也仅仅是索引里的几个字段的值和主键值,对于sele ct *还需要很多其他的字段,那还得走一个回表操作,根据主键到主键的聚簇索引里去找,聚簇索引的叶子节点是数据页,找到数据页里才能把一行数据的所有字段值提 取出来。
        类似select * from table order by xx1,xx2,xx3 的语句,首先得从联合索引的索引树里按照顺序取出来所有数据,接着对每一条数据都走一个主键的聚簇索引的查找,其实性能也是不高的。
  • 有时MySQL的执行引擎甚至可能会认为,类似select * from table order by xx1,xx2,xx3的语句相当于是得把联合索引和聚簇索引,两个索引的所有数据都扫描一遍了,那还不如就不走联合索引了,直接全表扫描得了,这样还就扫描一个索引而已。
  •  select * from table order by xx1,xx2,xx3 limit 10这样的语句,那执行引擎就知道了,你先扫描联合索引的索引树拿到10条数据,接着对10条数据在聚簇索引里查找10次就可以了,那么就还是会走联合索引的。
覆盖索引: 覆盖索引不是一种索引,他就是一种基于索引查询的方式。就是针对类似select xx1,xx2,xx3 from table order by xx1,xx2,xx3这样的语句,这种情况下,你仅仅需要联合索引里的几个字段的值,那么其实就只要扫描联合索引的索引树 就可以了,不需要回表去聚簇索引里找其他字段了。
         需要的字段值直接在索引树里就能取出,不需回表到聚簇索引, 该查询方式叫覆盖索引。
        
        所以在写SQL语句的时候,一方面是你要注意一下也许你会用到联合索引,但是是否可能会导致大量的回表到聚簇索引,如果需要回表到聚簇索引的次数太多了,可能就直接给你做成全表扫描不走联合索引了; 一方面是尽可能还是在SQL里指定你仅仅需要的几个字段,不要搞一个 select * 把所有字段都拿出来,甚至最好是直接走覆盖索引的方式,不要去回表到聚簇索引。
        即使要回表到 聚簇索引,那也尽可能用limit、where之类的语句限定一下回表到聚簇索引的次
数,就从联合索引里筛选少数数据,然后再回表到聚簇索引里去,这样性能也会好一些。

15、设计索引的时候,我们一般要考虑哪些因素呢?(上)

        设计索引的时候,我们通常应该考虑哪些因素,给哪些字段建立索引,如何建立索引,建立好索引之后应该如何使用才是最合适的。

        现在以一个电商平台的商品系统、交易系统以及营销系统的表结构设计以及索引设计作为案例背景,在实际的系统设计中,应该如何设计表结构以及索引。这个案例将会包含商品表、商品详情表、订单表、物流表、退款表、购物车表、营销活动表,等多个表的设计,帮助大家在电商场景下去学习表结构的设计,以及针对具体的业务场景如何设计索引。
  • 首先,我们在针对业务需求建立好一张表的结构之后,就知道这个表有哪些字段,每个字段是什么类型的,会包含哪些数据。
  •  接着,设计好表结构之后就要设计表的索引,要考虑第一点就是未来我们对表进行查询的时候,大概会如何来进行查询?
  • 此时,我们完全可以在表结构设计完毕之后,先别急着设计索引,因为此时你根本不知道要怎么查询表。
  •  接着,我们就可以进入系统开发的环节,也就是说根据需求文档逐步逐步的把你的Java业务代码给写好。在写代码的过程中,现在一般我们都是用MyBatis作为数据持久层的框架的,你肯定会写很多的 MyBatisDAOMapper以及SQL吧?
  • 当系统差不多开发完毕了,功能都跑通了,此时你就可以来考虑如何建立索引了,因为你的系统里所有的MyBatisSQL语句都已经写完了,你完全知道对每一张表会发起些什么样的查询语句。
  • 第一个索引设计原则:针对你的SQL语句里的where条件、order by条件以及group by条件去设计索引 也就是说,你的where条件里要根据哪些字段来筛选数据?order by要根据哪些字段来排序?group by要根据哪些字段来分组聚合? 此时可以设计一个或者两三个联合索引,每一个联合索引都尽量去包含上你的where、order by、group by里的字段,接着你就要仔细审查每个SQL语句,是不是每个where、order by、group by后面跟的字段顺序,都是某个联合索引的最左侧字段开始的部分字段
    • 比如你有一个联合索引是INDEX(a,b,c),此时你一看发现有三个SQL,包含了where a=? and b=?order by a,bgroup by a这些部分,那么此时whereorder bygroup by后续跟的字段都是联合索引的最左侧开始的部分字段,这就可以了,说明你的每个SQL语句都会用上你的索引了。
第一条,是去保证你的每个 SQL 语句的 where、 order by和 group by 都可以用上索引。

16、设计索引的时候,我们一般要考虑哪些因素呢?(中)

        首先一个就是字段基数问题,举个例子,有一个字段他一共在10万行数据里有10万个值。这个10万值,要不然就是0要不然就是1,那么他的基数就是2,为什么?因为这个字段的值就俩选择,01。假设你要是针对上面说的这种字段建立索引的话,那就还不如全表扫描了,因为你的索引树里就仅仅包含01两种值,根本没法进行快速的二分查找,也根本就没有太大的意义了,所以这种时候,选用这种基数很低的字段放索引里意义就不大了。

  •   一般建索引,尽量使用那些基数比较大的字段,就是值比较多的字段,那么才能发挥出B+树快速二分查找的优势来。
  •  其次,尽量对那些字段的类型比较小的列来设计索引,比如说什么tinyint之类的,因为他的字 段类型比较小,说明这个字段自己本身的值占用磁盘空间小,此时你在搜索的时候性能也会比较好一点。 很多时候你就是要针对varchar(255)这种字段建立索引,哪怕多占用一些磁盘空间,那你也得去设计这样的索引,比较关键的其实还是尽量别把基数太低的字段包含在索引里,因为意义不是太大。
    • 如果真的有那种varchar(255)的字段,可能里面的值太大了,你觉得都放索引树里太占据磁盘空间了,此时你仔细考虑了一下,发现完全可以换一种策略,也就是仅仅针对这个varchar(255)字段的前20个字符建立索引,就是说,对这个字段里的每个值的前20个字符放在索引树里而已。
    • 此时你建立出来的索引其实类似于KEY my_index(name(20),age,course),就这样的一个形式,假设name是varchar(255)类型的,但是在索引树里你对name的值仅仅提取前20个字符而已
    • 此时你在where条件里搜索的时候,如果是根据name字段来搜索,那么此时就会先到索引树里根据name字段的前20个字符去搜索,定位到之后前20个字符的前缀匹配的部分数据之后,再回到聚簇索引提取出来完整的name字段值进行比对就可以了。
    • 但是假如你要是order by name,那么此时你的name因为在索引树里仅仅包含了前20个字符,所以这个排序是没法用上索引了!group by也是同理的。
索引字段的基数和前缀索引
  • 对于那种字段基数很低的列尽量别包含到索引里去,没多大用;
  • 对于那种比较长的字符串类型的列,可以设计前缀索引,仅仅包含部分字符到索引树里去,where查询还是可以用的 ,但是order bygroup by就用不上了。

17、设计索引的时候,我们一般要考虑哪些因素呢?(下)

        对于SQL:where function(a) = xx ,你给你的索引里的字段a 套了一个函数,此时是不能用上索引 。所以尽量不要让你的查询语句里的字段搞什么函数,或者是搞个计算。
      
         设计好索引,让你的查询语句都能用上索引,同时注意一下字段基数、前缀索引和索引列套函数的问题,尽量让你的查询都能用索引,别因为一些原因用不上索引了。
         数据插入数据时肯定会更新索引树。
  •  插入数据肯定有主键,那有主键就得更新聚簇索引树,你插入一条数据肯定会包含索引里各个字段的值,那你的联合索引的B+树也要更新。你不停的增删改数据,就会不停的更新你的索引树。
  • 所以因为你插入的数据值可能根本不是按照顺序来的,很可能会导致索引树里的某个页就会自动分裂。
    • 这个页分裂的过程就很耗费时间,因此一般让大家设计索引别太多,建议两三个联合索引就应该覆盖掉表的全部查询了
    • 否则索引太多必然导致你增删改数据的时候性能很差,因为要更新多个索引树。另外很关键一点,建议大家主键一定是自增的,别用UUID之类的,因为主键自增,那么起码你的聚簇索引不会频繁的分裂,主键值都是有序的,就会自然的新增一个页而已,但是如果你用的是UUID,那么也会导致聚簇索引频繁的页分裂。

18、案例实战:陌生人社交APPMySQL索引设计实战(一)

案例背景:        

        针对社交APP的用户信息表user_info进行筛选,该表会包含你的地区(你在哪个省份、哪个城市,这个很关键,否则不在一个城市,可能线上聊的好, 线下见面的机会都没有),性别,年龄,身高,体重,兴趣爱好,性格特点,还有照片,最近一次在线时间(否则半年都不上线APP了,你把他搜出来干什么呢?),另外如果支持交友过程中让其他人对他进行评价,那么可能还需要包含这个人的一个综合评分。

需求:

        除了select xx from user_info where xx=xx 有一系列的条件之外,APP肯定得支持分页展示,所以肯定还得跟上limit xx,xx的分页语句。 总得根据一定的规则对筛选出来的结果进行一个排序,把最符合你的条件和期望的用户排列在最上面。

最终SQL 语句可能类似: select xx from user_info where xx=xx order by xx limit xx,xx。
规则1: 在where条件里必须是使用联合索引里最左侧开始的连续多个字段进行筛选,order by排序时也必须用联合索引里的最左侧开始的多个连续字段进行排序
问题是,假设你的 SQL 需要按照年龄进行范围筛选,同时需要按照用户的评分进行排序,类似下面
SQL select xx from user_info where age between 20 and 25 order by score ,那就有问题了。
 
        假设就一个联合索引(age,score),age 在最左侧,那你的 where 是可以用上索引来筛选的,但排序是基于 score字段,那就不可以用索引了。
        假设你针对age和 score 分别设计了两个索引(age)(score),但是在你的 SQL 里假设基于age 索引进行了筛选,是没法利用另外一个 score 索引进行排序的。
记住1: where筛选和order by排序实际上大部分情况下是没法都用到索引的!

19、案例实战:陌生人社交APPMySQL索引设计实战(二)

问题1:

        在where和order by出现索引设计冲突,到底是针对where去设计索引,还是针对order by设计索引?到底是让where去用上索引,还是让order by用上索引?问题本质是要让where语句先基于联合索引去进行一个筛选,筛选出来一部分用户指定的数据,接着再把数据加载到内存或者是基于临时磁盘文件去进行指定条件的排序,最后用limit 语句拿到一页数据吗?还是说要让order by语句按照你的索引的顺序去找,找的过程中基于where里的条件筛选出来指定的数据,然后再根据limit语句拿出来一页数据?

        一般这种时候往往都是让where条件去使用索引来快速筛选出来一部分指定的数据,接着再进 行排序,最后针对排序后的数据拿出来一页数据。 因为基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,如果筛选出来的数据量不是太大的话,那么后续排序和分页的成本往往不会太大!

问题2:
        搜索潜在好友的时候,一般会用上哪些条件呢?到底要把哪些字段包含到索引里去?到底在联合索引里,字段的顺序要如何排列呢?

        其实,我们 首先应该在联合索引里包含省份、城市、性别,这三个字段 !因为这三个字段都是在搜索里几乎必定包含的三个字段,假设你要搜索潜在好友,那么必定是会搜索跟你同一个地方的,然后搜索某个性别的的其他用户,这几个条件在APP 里完全可以做成必选项,用户也几乎必定会指定。
问题3:

        之前说过基数太低的字段最好别放到索引里去,那省份、城市和性别都是基数非常小的几个字段,可选的值就那么几个,为什么要放到索引里去?

        假设因为省份、城市和性别几个字段的基数太小了,此时就不把他们几个包含到联合索引里去,那么你实际查询的时候都要基于这几个字段去搜索,此时你就只能把这几个字段放在where条件的最后,那么最后每次查询都必须要先用联合索引查询出来一部分数据,接着数据加载到内存里去,再根据where条件最后的省份、城市和性别几个字段进行过滤筛选。 所以与其如此,还不如就把省份、城市和性别三个字段,放在联合索引的最左侧,这样跟其他字段组合联合索引后,让大部分的查询都可以直接通过索引树就可以把where条件指定的数据筛选出来了。

         可以把基数较低但是频繁查询(几乎每次查询都会指定)的省份、城市和性别几个字段放到联合索引的最左侧去,此时就可以让每次查询时指定的省份、城市和性别,都直接从索引树里进行筛选。

20、案例实战:陌生人社交APPMySQL索引设计实战(三)

        目前的联合索引为province, city, sex

问题3:

        假设查询的时候,不指定性别,就指定了省份,城市和年龄,也就是说where province=xx and city=xx and age between xx and xx,那么此时怎么办呢?因为age不在索引里,所以就根本没法通过age去在索引里进行筛选了。

        如果把索引设计成(province, city, sex, age ),此时你的语句写成 where province=xx and city=xx and age>=xx and age<=xx,也是没法让 age 用上索引去筛选的,因为 city age 中间差了一个 sex ,所以此时就不符合最左侧连续多个字段的原则了。
        此时设计索引为( province, city, sex, age ),在搜索的时候就根据省份、城市和年龄来筛选,性别是不限的,此时就可以把where 语句写成:
where province=xx and city=xx and sex in  ('female', 'male') and age >=xx and age<=xx。
此时就 可以让整个 where 语句里的条件全部都在索引树里进行筛选和搜索了!
问题4:
        假设我们在查询语句里还有一些频繁使用的条件,通常都是兴趣爱好和性格特点,这个兴趣爱好和性格特点,往往都是有固定的一些枚举值的比如兴趣爱好可以有下述的值可选:运动、电影、旅游、烹饪,性格特点可能包含下面的值:温柔、霸气、御姐、体贴、善良,等等。那么针对这样的一些频繁使用的包含枚举值范围的一些字段,也完全可以加入到联合索引里去,可以设
计成 (province, city, sex, hobby, character, age)这样的一个 联合索引,此时假设出现了这样一个查询,按照省份、城市、性格和年龄进行搜索,此时SQL 怎么写?
        
         where province=xx and city=xx and sex in(xx, xx) and hobby in (xx, xx, xx, xx) and character=xx and age>=xx and age<=xx
        也就是说, 即使你不需要按性别和爱好进行筛选,但是在SQL里你可以对这两个字段用in语句,把他们所有的枚举值都放进去 。这样的话,就可以顺利的让province city character age 四个真正要筛选的字段用上索引,直接在索引里进行筛选都是没有问题的。
问题5:
        为什么age字段必须要放在联合索引的最后一个呢?
规则2: 假设where语句里有等值匹配,还有范围匹配,此时必须是先让联合索引最左侧开始的多个字段使用等值匹配,接着最后一个字段是范围匹配
  • 比如上面的语句where province=xx and city=xx and sex in(xx, xx) and hobby in (xx, xx, xx, xx) and character=xx and age>=xx and age<=xx,他们完全是按照联合索引最左侧开始的,province、city、sexhobbycharacter都是联合索引最左侧开始的多个字段,他们都是等值匹配,然后最后一个age字段使用的是范围匹配,这种就是可以完全用上索引的。
  • 假设你要是在联合索引里把age放在中间的位置,设计一个类似(province, city, sex, age, hobby,character)的联合索引,接着SQL写成where province=xx and city=xx and sex in(xx, xx) and age>=xx and age<=xx and hobby in (xx, xx, xx, xx) and character=xx的话,此时只有province, city, sex, age几个字段可以用上索引。
规则3: 在SQL里,一旦一个字段做范围查询用到了索引,那该字段接下来的条件都不能用索引了
 
         实际设计索引的时候,必须把经常用做范围查询的字段放在联合索引的最后一个,才能保证SQL里每个字段都能基于索引去查询

21、案例实战:陌生人社交APPMySQL索引设计实战(四)

 目前的联合索引为province, city, sex, hobby, character, age)

        关键是要让最频繁查询的一些条件都放到索引里去,然后在查询的时候如果有些字段是不使用的,可以用in (所有枚举值)的方式去写,这样可以让所有查询条件都用上你的索引,同时对范围查询的age字段必须放在最后一个,这样保证范围查询也能用上索引

问题6:

        登录过APP的用户,那么实际上可能你的用户表里有这么一个字段latest_login_time,你要是在where条件里加入这么一个latest_login_time <= 7天内语句,肯定这个是没法用上索引了。

  •  因为这里必然会用一些计算或者是函数,才能进行一些时间的比对。
  •  而且假设你的查询里还有age进行范围查询,并且范围查询时也就只有第一个范围查询是可以用上索引的,第一个范围查询之后的其他范围查询是用不上索引的
    • 也就是说,即使你索引设计成这样:(province, city, sex, hobby, character, age, latest_login_time),然后where语句写成这样:where xx xxx and age>=xx and age<=xxx and latest_login_time>=xx,虽然agelatest_login_time都在联合索引里,但是按照规则,只有age范围查询可以用到索引,latest_login_time始终是用不到索引的。
        所以此时有一个 技巧 ,你在设计表的时候,就必须考虑到这个问题,此时你完全可以设计一个字段为:does_login_in_latest_7_days,就是这个人是否在最近 7 天内登录过 APP 。假设在7 天内登录了这个 APP ,那么这个字段就是 1 ,否则超过 7 天没登录,这个字段就是 0 !这样就 把一个时间字段转换为了一个枚举值的字段 。 此时可以设计一个联合索引为: province, city, sex, hobby, character, does_login_in_latest_7_days, age ,然后搜索的时候,一定会在 where 条件里带上一个does_login_in_latest_7_days=1,最后再跟上 age 范围查询,这样就可以让你的 where 条件里的字段都用索引来筛选。
        实际上一般来说,假设你要是where 语句里通过上述联合索引就可以过滤掉大部分的数据,就保留小部分数据下来基于磁盘文件进行order by 语句的排序,最后基于 limit 进行分页,那么一般性能还是比较高的。
        万一你要是就仅仅使用联合索引里一些基数特别小的字段来筛选呢?比如就基于性别来筛选比如一下子筛选出所有的女性,可能有上百万用户数据,接着还要磁盘文件进行排序再分页?那这个性能可能就会极为的差劲了!
辅助索引:        
        所以针对上述问题,可以 针对那种基数很低的字段再加上排序字段单独额外设计一个辅助索引,专门用于解决where条件里都是基数低的字段,然后还要排序后分页的问题。
        比如 说就可以设计一个联合索引为:(sex, score )。此时万一你要是写出如下SQL
select xx from user_info where sex='female' order by score limit xx,xx
此时假设用之前设计的那个联合索引,那绝对是完蛋了,因为根本没法用索引。
        但是用我们设计的那个辅助的(sex, score )索引呢?
        此时因为where 条件里的字段是等值匹配,而且还是等于某个常量值,所以虽然 order by 后跟的 score 字段是(sex, score )索引里的第二个字段, order by 没有从索引最左侧字段开始排列,但是他也可以使用到索引来排序。
        因为具体到使用索引的层面,他会先对where 条件里的 sex='female' 在索引树里筛选到这部分数据,接着在sex='female' 的数据里,这些数据实际上都是排列在一起的,因为在索引里,会按照 sex score 两个字段去进行排序,所以sex='female' 的数据都是在一块儿的。
        然后找到这部分数据之后,接着就可以确定,这部分数据肯定是按照score 字段进行排序的,此时就可以按照score 字段值的顺序,去读取你的 limit 语句指定的数据分页出来就可以了。所以此时你这种针对sex 低基数的字段的筛选和基于评分排序的语句,整体运行的效率是非常高的,完
全可以基于辅助索引来实现。
        以此类推,完全可以通过对查询场景的分析,用 province, city, sex, hobby, character,
does_login_in_latest_7_days, age 这样的联合索引去抗下复杂的 where 条件筛选的查询,此时走索引筛选速度很快,筛选出的数据量较少,接着进行排序和limit 分页。
        同时针对一些低基数字段筛选 + 评分排序 的查询场景,可以设计类似( sex, score )的 辅助索引 来应对,让他快速定位到一大片低基数字段对应的数据,然后按照索引顺序去走limit 语句获取指定分页的数据,速度同样会很快。通过最近这个案例的分析,想必大家能够感悟到一些针对具体的查询场景来设计你的联合索引和辅助索引的技巧
核心重点:
尽量利用一两个复杂的多字段联合索引,抗下你80%以上的 查询,然后用一两个辅助索引抗下剩余20%的非典型查询,保证你99%以上的查询都能充分利用索引,就能保证你的查询速度和性能!
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值