不用索引
数据量比较小时,由于数据较小,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果
用索引
数据量比较大的情况
怎么用索引
- 唯一值的字段,建立唯一索引(其实并不推荐在数据库级别做唯一性限制,可以将唯一性限制放在业务层去处理)
- 常用排序字段,建立索引,可以有效地避免排序操作
- 常作为查询条件的字段,建立索引,可以提高整个表的查询速度
- 选值类型较小的字段做索引。如果索引的值很长,那么查询的速度会受到影响。例如,对一个 CHAR(100) 类型的字段进行全文检索需要的时间肯定要比对 CHAR(10) 类型的字段需要的时间要多。
索引增减
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。在修改表的内容时,索引必须进行更新,有时还可能需要重构。因此,索引越多,更新表的时间就越长。
数据的使用方式被改变后,原有的一些索引可能不再需要。应该定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
一个索引很少利用或从不使用,那么会不必要地减缓表的修改速度。此外,MySQL 在生成一个执行计划时,要考虑各个索引,这也要花费时间。创建多余的索引给查询优化带来了更多的工作。索引太多,也可能会使 MySQL 选择不到所要使用的最佳索引。
唯一索引比普通索引快?
查询
对于普通索引来说,查找到满足条件的第一个记录后,需要查找下一个记录,直到碰到第一个不满足条件的记录。
对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索
由于少了一个步骤,所以感觉用唯一性索引会快一些。但是这个不同带来的性能差距微乎其微
为什么查询性能微乎其微?
InnoDB 的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。在 InnoDB 中,每个数据页的大小默认是 16KB。
因为引擎是按页读写的,所以说,当找到符合条件的记录的时候,它所在的数据页就都在内存里了。那么,对于普通索引来说,要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。
当然也会有特殊情况,就是符合条件的记录正好处于数据页的最后一个,那往下查找的操作就会拿下一个数据页放进内存,这个时候就会慢了,但是一个整型字段,一个数据页可以放进千的key,所以这个概率很低
更新
几个概念
buffer pool: Innodb维护了一个缓存区域叫做Buffer Pool,用来缓存数据和索引在内存中。Buffer Pool可以用来加速数据的读写,如果Buffer Pool越大,那么Mysql就越像一个内存数据库,所以了解Buffer Pool的配置可以提高Buffer Pool的性能
change buffer:的是 buffer pool 里的内存,因此不能无限增大。change buffer 的大小,可以通过参数 innodb_change_buffer_max_size 来动态设置。这个参数设置为 50 的时候,表示 change buffer 的大小最多只能占用 buffer pool 的 50%
merge:将 change buffer 中的操作应用到原数据页,得到最新结果的过程称为 merge。除了访问这个数据页会触发 merge 外,系统有后台线程会定期 merge。在数据库正常关闭(shutdown)的过程中,也会执行 merge 操作。
InnoDB更新数据的情况
- 数据页在内存中,直接更新
- 数据页不在内存中,InnoDB将更新操作缓存在change buffer中,在下次访问那条记录的时候,将那条记录所在的数据页放入内存中,然后执行change buffer中记录下来的操作。即,并没有立马更新,而是在后续查询这个数据页的时候再做的更新操作。
所以,普通索引会用到 change buffer。
具体流程
如果要在这张表中插入一个 id=5的新纪录,InnoDB 的处理流程是怎样的。
第一种情况:目标数据页在内存中
唯一索引:找到 4 和 6 之间的位置,判断到没有冲突,插入这个值,语句执行结束;
普通索引:找到 4 和 6 之间的位置,插入这个值,语句执行结束。
这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的 CPU 时间。
第二种情况是:目标数据页不在内存中
唯一索引:需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
普通索引:则是将更新记录在 change buffer,语句执行就结束了。
主要区别就是唯一索引需要把磁盘中的数据页放入内存。就是这步影响了性能。
将数据从磁盘读入内存涉及随机 IO 的访问,是数据库里面成本最高的操作之一。change buffer 因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。
应用场景
因为 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。
由此看来就是对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。
所以反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在 change buffer,但之后由于马上要访问这个数据页,会立即触发 merge 过程。这样随机访问 IO 的次数不会减少,反而增加了 change buffer 的维护代价。所以,对于这种业务模式来说,change buffer 反而起到了副作用。
总结
普通索引和唯一索引,在查询能力上基本是无差别的,而对于更新操作,由于change buffer的作用,所以还是尽量选择普通索引。
在实际使用中,普通索引和 change buffer 的配合使用,对于数据量大的表的更新优化还是很明显的。特别地,在使用机械硬盘时,change buffer 这个机制的收效是非常显著的。所以,当你有一个类似“历史数据”的库,并且出于成本考虑用的是机械硬盘时,那你应该特别关注这些表里的索引,尽量使用普通索引,然后把 change buffer 尽量开大,以确保这个“历史数据”表的数据写入速度。这时候,归档数据已经是确保没有唯一键冲突了。要提高归档效率,可以考虑把表里面的唯一索引改成普通索引。
唯一索引使用的问题,主要是纠结在“业务可能无法确保”的情况。
首先,业务正确性优先。如果业务不能保证数据的唯一性,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引,否则,唯一索引并不推荐。