MySQL索引设计原则

102 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第31天,点击查看活动详情

刚设计完表,我咋知道未来会怎么查询表?表设计完后,先不急设计索引,确实你根本还不知道要怎么查表。

该正式业务开发了,根据需求Java业务代码写好,用MyBatis生成各种DAO和Mapper及SQL,开发阶段最后,功能都跑通了,就能开始考虑如何建立索引,因为现在你完全知道对每张表会怎么查询了。针对你的SQL语句里的where条件、order by条件以及group by条件去设计索引,即你的where条件里要根据哪些字段来筛选数据?order by要根据哪些字段来排序?group by要根据哪些字段来分组聚合?就能设计一或两三个联合索引,每一个联合索引都尽量去包含上你的where、order by、group by里的字段。

接着你就要审查是否每个where、order by、group by后面跟的字段顺序,都是某个联合索引的最左侧字段开始的部分字段?

比如你有个联合索引:INDEX(a,b,c)。发现有三个SQL,包含where a=? and b=?,order by a,b,group by a这些部分,此时where、order by、group by后续跟的字段都是联合索引的最左侧开始的部分字段,说明你的每个SQL语句都会用上索引。

综上,设计的索引最好让你的各where、order by和group by后面跟的字段都是联合索引的最左侧开始的部分字段,这样他们都能用上索引。

但还得考虑其它问题:

字段基数

有个字段,在10万行数据里有10万个值。结果这10万个值,要么是0,要么是1,则其基数就是2,因为字段值就两个选择,0和1。

对这种字段建立索引,还不如全表扫描,因为索引树仅含0、1,无法进行二分查找,建立索引没有意义。

所以建立索引,尽量使用那些基数较大的字段,即值较多,才能发挥出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 function(a) = xx

给你的索引里的字段a套个函数,就用不上索引了。所以尽量不要让你的查询语句里的字段搞什么函数或计算。

索引设计好后,系统跑起来,有数据插入也有查询,查询基本都能走索引一般问题不大,但插入肯定会更新索引树。插入数据肯定有主键吧,那有主键就得更新聚簇索引树,你插入一条数据肯定会包含索引里各字段的值吧,那你的联合索引的B+树是不是也要更新?随着你不停增删改,就会不停更新索引树。

所以因为你插入的数据值可能根本不按序,很可能导致索引树里的某个页就会自动分裂,页分裂过程很耗费时间,因此设计索引别太多,建议两三个联合索引就应该覆盖掉你这个表的全部查询了,否则索引太多必然导致增删改时性能很差,因为要更新多个索引树。

主键自增

别用UUID等,因为主键自增,至少你的聚簇索引不会频繁分裂, 主键值都有序,就会自然的新增一个页而已,但若UUID,会导致聚簇索引频繁页分裂。