这次来聊一下唯一索引和普通索引。这两者的区别就不赘述了,通俗来说,唯一索引不允许出现重复,在插入或者修改操作的时候都需要进行判断是否发生唯一性冲突,若有,则不能进入插入或者修改操作。

有一个数据库相关的面试题:

如果数据表中更新操作经常发生阻塞,操作命中率也低,可能是什么原因导致的?

这个问题的答案就是可能错误地使用了唯一索引。在不影响业务正确性的前提下,将唯一索引改成普通索引可以避免该问题。

那到底从何说起呢?

考虑这样一个表结构,还是一个居民表,有两个字段:身份证号姓名

一般来说身份证号太长,不太适合用来作为索引,因为长索引会造成维护成本过高。这里的正确做法应该是再加一个自增字段作为索引。但我们现在就是要把身份证号作为索引,但需要考虑一个问题:身份证号设置为唯一索引好?还是普通索引好?

设置为唯一索引好像不错,因为身份证号不允许重复,设置为唯一索引可以保证逻辑正确性。但是如果身份证号唯一性已经在后端就已经完成了保证呢?还有必要再在数据库这里再加一次审核吗?好像再加一层审核能更严谨。那要讨论这个问题,就要讨论一下唯一索引和普通索引的性能问题了。

  • 查询性能

讨论一下两种索引的查询性能区别。考虑这样一个查询语句

SELECT name FROM MyTable WHERE id=xxxxxx;

简单的一条根据身份证号查询名字的语句,看看普通索引会怎么执行:

普通索引在通过索引查找了对应身份证号之后,因为表中索引不是唯一的,所以还会继续向后扫描,直到不符合条件为止。

而在唯一索引上,找到了对应身份证号,因为唯一索引确信后续不会有相同的身份证号,所以会直接返回结果。

乍一看,普通索引多了一次向后扫描的操作,但是这个扫描操作也只是多扫描了一个数据,因为下一个身份证号肯定不相同,这是在业务和逻辑上就已经保证了的。一个数据的扫描对CPU来说简直可以忽略不记。所以在查询性能,两个索引的性能差距基本持平。

而重点则在更新和插入操作上

  • 更新和插入操作

对于唯一索引来说,更新操作和插入操作并不会马上更新到磁盘中,而是把这些操作记录在一个叫做"change buffer“的缓存区中,等到合适的时机,再统一对缓存区中的操作更新到磁盘中,那这个时机是什么时候呢?一般有两种:

1)接下来发生了对对应数据行的查询操作,此时数据库不得不从磁盘中把对应的数据页加载到内存中,那么change buffer缓存区就可以趁机把记录中该数据页对应的更新操作更新进去,然后再执行原来的查询操作,这样就能保证数据的正确性。也能使得多次磁盘IO操作减少为一次磁盘IO操作。

2)等待某些事件的发生,比如说连接关闭之前,防止缓存区记录丢失,也会更新到磁盘中。

这样有很多好处,避免了每次更新操作都要去进行一次磁盘IO。

然而change buffer只是普通索引独有的,唯一索引是不可以使用缓存区。为啥?就像之前说的,唯一索引的插入操作必须保证数据唯一性,如果该次操作会破坏唯一性,那么这次更新操作不能执行。所以唯一索引每次更新操作都不得不进行一次磁盘IO,对比原数据后才能判断该更新操作是否合法。

所以说唯一索引的每一次更新操作都对应一次磁盘IO,在数据库中,开销最大的操作也就是磁盘IO了。所以这也能够解释为什么使用唯一索引会使得数据表的更新操作经常阻塞。

当然了,将唯一索引换成普通索引的前提是不影响业务正确性。如果确实需要靠数据库这关来保证身份证号唯一,那使用唯一索引也是不可避免的。

  • 缓存区change buffer在任何时候都适用吗?

change buffer缓存区看起来很棒,但也不是所有场景都适合。

在一个写操作很多,而读操作很少的业务下,使用缓存区当然是最优选。缓存区的数据积累得越多(当然不能超过上限),说明带来的收益也越高,说明将多次IO操作变得更少。

但如果在一个场景下,数据被更新后很大可能会被查询,那么这个数据刚刚被写入缓存区,马上就要被查询,此时就要进行一次IO来保证查询的数据正确性。那频繁发生这样的事情,缓存区的收益就很小了,甚至维护缓存区还是一个很不划算的事情,所以这种情况下缓存区就不适合使用了。