普通索引和唯一索引的区别
查询
唯一索引
这里我们以下面的语句为例子进行介绍:这里的card_id就是唯一索引
select * from t_user where card_id = 142733
当InnoDB引擎查询到第一条符合条件的记录时就会返回,因为唯一索引保证了字段的唯一性;
普通索引
这里我们以下面的语句为例子进行介绍:这里的name就是普通索引,因为人名会有重复
select * from t_user where name = javalover
当InnoDB引擎查询到第一条符合条件的记录时,还会继续往后查询,直到不符合条件为止;
从表面上来看,好像普通索引的性能会低一些,因为它查的多啊
实际结果是:唯一索引和普通索引的查询性能,差别并不大;
因为mysql的数据操作并不是每查询一条,就去磁盘中取一条,而是按页去磁盘中取的;
按页取的数据会存在内存中(change buffer下面会介绍),供后续的查询和更新使用;
这样一来,从内存中读取连续的数据,对于服务器来说,就没啥压力了,多读几个数据差别也就没有多大了;
不过如果读取的数据刚好在数据页的末尾,那么性能可能会稍微低一点; 不过这个概率比较低,因为一个数据页的默认大小是16KB,大概可以存储上千个整型数值;
更新
如果要更新的数据页在内存中,那唯一索引和普通索引没啥区别,都是找到对应的索引然后直接更新;
如果要更新的数据页不在内存中,那更新过程两者会稍有不同
唯一索引
需要先把数据页从磁盘中读取出来,然后再去找到对应的索引进行更新;
普通索引
不会从磁盘中读取数据页,而是直接把更新操作保存在内存中(change buffer);
然后内存中的数据再在合适的时机,合并到数据库磁盘中;
这样来看的话,差距就很明显了,普通索引的性能会更好一点;
因为唯一索引的每次更新都要去磁盘中读取数据,这会涉及到磁盘的随机IO操作,而磁盘的随机IO操作,在数据库里面属于成本比较高的操作之一了。
change buffer
定义:change buffer就是一块内存区域,用来存储更新操作
这里我们需要注意一点,虽然change buffer听起来像是一个缓存区,应该只存在于内存中;但是实际上change buffer也是会持久化到磁盘中的;
上面我们提到了change buffer会在合适的时机,将它里面的数据合并(merge)到数据库磁盘中;
合并(merge)的过程是怎么样的呢?
合并就是把change buffer中的数据覆盖到数据库磁盘中旧的数据页,使其变成一个新的数据页的过程;
- 合并的第一步就是先把旧的数据页从数据库磁盘读到内存中,然后根据change buffer中的更新操作去更新旧的数据页;
- 此时会得到一个新的数据页,也叫做脏页,因为这个时候内存中的新数据页和数据库磁盘中的数据页是不一样的;
- 最后刷脏页到磁盘中,就是把新的数据页写入到磁盘中,这个刷脏页可能发生在系统内存不足时,也可能发生在系统空闲时等等(关于脏页的细节可以参考网上的资源)
有多种情况,比如:
- 当change buffer中的更新操作 对应的数据页有查询操作时,就会执行merge合并,因为不合并的话,从磁盘中读的数据页是旧的数据,而change buffer中存储的只是更新操作,无法直接读取;
- 系统后台定期merge合并;
- 数据库正常关闭时,也会执行merge合并;
change buffer的好处体现在哪里呢?
两个方面:
- 提高语句的执行效率:因为有了change buffer,就不用每次更新都去磁盘中读取数据页,然后更新,只需要将更新操作保存即可;
- 节省内存,提高内存的利用率:这个其实跟上面的原因是一样的,因为不用去磁盘中读取数据页,那自然就不用在内存中(buffer pool)存储那部分数据页,这样内存就可以省下来干别的事情;(不过change buffer也是用的buffer pool的内存,只是占的比数据页要少)
上面我们提到了合并操作的时机,第一个就是在语句查询时发生;
而合并操作是需要访问数据库的磁盘的,所以如果刚写的数据没多久就要查询,而且很频繁,那就不适合用change buffer了;
因为这个时候不仅磁盘的随机IO访问没有减少,还增加了change buffer的维护成本,得不偿失了;
所以change buffer的适用场合是写多读少的场合,比如日志系统等;
总结
唯一索引就是字段值唯一,不会重复,一般在业务无法保证数据的唯一性时,才考虑使用唯一索引;
普通索引的更新操作,会用到change buffer,此时系统会直接把更新操作保存到change buffer中,然后返回,表现出来的效果就是更新很快;然后change buffer再在下次查询时将数据merge到数据库磁盘,或者系统后台定期merge等等;