MySQL 索引设计概要
三星索引
三星索引是对于一个查询语句可能的最好索引,如果一个查询语句的索引是三星索引,那么它只需要进行一次磁盘的随机读及一个窄索引片的顺序扫描就可以得到全部的结果集;因此其查询的响应时间比普通的索引会少几个数量级;根据书中对三星索引的定义,我们可以理解为主键索引对于 WHERE id = 1
就是一个特殊的三星索引,我们只需要对主键索引树进行一次索引访问并且顺序读取一条数据记录查询就结束了。
为了满足三星索引中的三颗星,我们分别需要做以下几件事情:
- 第一颗星需要取出所有等值谓词中的列,作为索引开头的最开始的列(任意顺序);
- 第二颗星需要将 ORDER BY 列加入索引中;
- 第三颗星需要将查询语句剩余的列全部加入到索引中;
三星索引的概念和星级的给定来源于 数据库索引设计与优化 书中第四章三星索引一节。
如果对于一个查询语句我们依照上述的三个条件进行设计,那么就可以得到该查询的三星索引,这三颗星中的最后一颗星往往都是最容易获得的,满足第三颗星的索引也就是上面提到的宽索引,能够避免大量的随机 IO,如果我们遵循这个顺序为一个 SQL 查询设计索引那么我们就可以得到一个完美的索引了;这三颗星的获得其实也没有表面上这么简单,每一颗星都有自己的意义:
- 第一颗星不只是将等值谓词的列加入索引,它的作用是减少索引片的大小以减少需要扫描的数据行;
- 第二颗星用于避免排序,减少磁盘 IO 和内存的使用;
- 第三颗星用于避免每一个索引对应的数据行都需要进行一次随机 IO 从聚集索引中读取剩余的数据;
在实际场景中,问题往往没有这么简单,我们虽然可以总能够通过宽索引避免大量的随机访问,但是在一些复杂的查询中我们无法同时获得第一颗星和第二颗星。
SELECT id, name, age FROM users
WHERE age BETWEEN 18 AND 21
AND city = "Beijing"
ORDER BY name;
在上述查询中,我们总可以通过增加索引中的列以获得第三颗星,但是如果我们想要获得第一颗星就需要最小化索引片的大小,这时索引的前缀必须为 (city, age),在这时再想获得第三颗星就不可能了,哪怕在 age 的后面添加索引列 name,也会因为 name 在范围索引列 age 后面必须进行一次排序操作,最终得到的索引就是 (city, age, name, id):
如果我们需要在内存中避免排序的话,就需要交换 age 和 name 的位置了,在这时就可以得到索引 (city, name, age, id),当一个 SQL 查询中同时拥有范围谓词和 ORDER BY 时,无论如何我们都是没有办法获得一个三星索引的,我们能够做的就是在这两者之间做出选择,是牺牲第一颗星还是第二颗星。
总而言之,在设计单表的索引时,首先把查询中所有的等值谓词全部取出以任意顺序放在索引最前面,在这时,如果索引中同时存在范围索引和 ORDER BY 就需要权衡利弊了,希望最小化扫描的索引片厚度时,应该将过滤因子最小的范围索引列加入索引,如果希望避免排序就选择 ORDER BY 中的全部列,在这之后就只需要将查询中剩余的全部列加入索引了,通过这种固定的方法和逻辑就可以最快地获得一个查询语句的二星或者三星索引了。
总结
在单表上对索引进行设计其实还是非常容易的,只需要遵循固定的套路就能设计出一个理想的三星索引,在这里强烈推荐 数据库索引设计与优化 这本书籍,其中包含了大量与索引设计与优化的相关内容;在之后的文章中读者也会分析介绍书中提供的几种估算方法,来帮助我们通过预估问题设计出更高效的索引。