结论:
- 不是越多越好,不论是从空间、时间、优化器选择、维护成本来说都不是越多越好。
原因
文字详解:
时间上:
- 外部操作影响:
- 每当我们对数据做增、删、改操作得时候,索引需要跟着对应得修改,当索引数量很多时,对应的索引列都要改,增加耗时,降低性能。
- 内部变化影响:
- 当删除或增加数据的时候,索引列会采用分裂或合并的方式,来进行调整,索引列很多的时候,大量的合并和分裂行为,也会增加耗时,降低性能。
空间上:
-
空间占用大:
- 每创建一个索引,就需要新增一个B+树(B+树每个节点,默认占用16KB),建立的索引很多的时候,会占用很多磁盘空间,影响性能。
-
碎片化:
- 当进行了大量的增删操作时,索引可能会因为需要进行分裂或合并,从而产生碎片化,增加IO次数,降低性能
- 碎片化的索引还会导致更多的页缓存失效,进一步影响查询性能。(页缓存失效见扩展。)
优化器选择上:
-
选择过多,耗时增加:
- mysql在执行我们的SQL语句之前会使用优化器进行优化,优化器需要评估所有的索引,来选出最优选,如果索引过多,会增加选择的耗时
-
错误选择:
- 当索引过多的时候,优化器的选择会更加的复杂,从而可能导致优化器选择了错误的次优选,导致性能降低。
-
放弃选择:
-
在下述几种情况中,优化器会选择放弃使用索引,转而使用全表扫描
- 评估不完: 索引过多,优化器评估方案耗时太久
- 选择不出: 索引过多,优化器找不到一个明显优于其他索引的方案
- 覆盖不足: 索引很多,但都不符合 多列的查询条件,没有合适的联合索引。
- 选择性低: 当索引很多,但是索引的选择性很低,比如Boolean类型的列
- 表的数据量小: 当在小表中,优化器发现使用全表扫描的效果比使用索引还好
- 查询条件复杂: 在复杂查询,特别是多个子查询的时候,虽然后很多索引,但是使用索引的并不能显著提高性能,反而会增加表的复杂性
- 索引竞争: 在索引很多,且高并发的情况下,多个查询条件,可能会竞争同一个索引,导致锁争用或者IO瓶颈(详解见扩展)
-
维护成本上:
- 过多的索引,会增加数据库的维护成本,包括备份,恢复,迁移等。
扩展知识:
索引过多时,高并发场景下会出现锁争用和IO瓶颈,为什么?什么是锁争用?
-
锁争用概念: 并发场景下,多个线程争夺一个锁,称为锁争用
-
InnoD中锁争用的体现:
- InnoDB中对数据进行操作的时候,我们可以通过索引来快速定位到数据行。
- 因此索引是查找到数据的关键结构。
- 当我们在并发场景下,多个查询,并发访问同一个数据
- 就需要并发访问同一个索引
- 此时这种情况下,mysql就会对索引项加锁
- 而只有拿到这把锁的查询,才可以继续访问数据
- 所以这些并发查询就需要争抢这把锁,于是锁争用就出现了
-
InnoDB中锁争用的影响:
- 并发事务中,InnoDB通过行级锁来保证事务的隔离
- 当事务要对某一行数据进行改变的时候,会给当前行数据加上排他锁
- 防止同一时间,有其他事务,对同一数据进行修改
- 读取的时候,是共享锁
- 增删改的时候,因为索引是查询到数据的关键
- 所以也会对索引,添加排他锁
- 当有新的索引被删除或者添加时
- 索引页需要分裂或合并
- 此时事务会给分裂出的新的索引页
- 或要合并的相关的索引页
- 也加上锁
- 这就导致多个索引页同时被加锁
- 降低了性能
- 而索引越多,锁分裂和合并的也就越频繁,性能影响也就越大
IO瓶颈:
-
合并和分裂影响:
- 索引越多,修改数据时,需要合并和分裂的索引就越多
- 合并和分裂越多,需要的IO操作就越多
-
缓存失效影响:
- 当索引越多,修改数据时,就需要频繁的修改缓存中的索引页
- 也就是说缓存中的索引页会频繁的失效
- 失效就需要更换新的
- 更换新的就需要IO操作
-
碎片化影响:
- 当索引越多,修改数据的时候
- 合并和分裂就越多
- 产生碎片化的概率就越大
- 索引碎片化的越多
- 需要的IO操作就越多
- 性能就越差