【MySQL 读书笔记】普通索引和唯一索引应该怎么选择
通常我们在做这个选择的时候,考虑得最多的应该是如果我们需要让 Database MySQL 来帮助我们从数据库层面过滤掉对应字段的重复数据我们会选择唯一索引,如果没有前者的需求,一般都会使用普通索引。这篇文章将会站在性能的角度来分析一下两者的区别对性能的影响。

这里还是用一张之前分析索引用到的图。
查询过程
在我们查询的时候我们使用 select id from T where k=5。这个查询语句通过查询逐渐搜索到 B+Tree 的叶子节点,然后取到对应的数据页,然后在数据页内部找到对应记录。我记得没错的话数据页内部似乎是链表形式存储的。
对于普通索引来说,查找到对应满足的条件 500 之后,还需要找下一个记录,直到碰到第一个不满足 k=5 的条件记录。
对于唯一索引来说,查找到第一条满足条件的记录之后,就会立即停止继续检索。
这两者带来的性能差距是微乎其微的。
首先我们将数据页从磁盘里面读出来是读出整个16kb 的数据页,那么当我们在找到第一个满足条件的记录的时候其实绝大多数情况我们要读取的下一个记录也在内存中。即使不在,我们也只需要再读取一个数据页,并且我记得数据页之间是有指针直接可以取到下一个数据页位置的。这个优化进一步优化了读取连续数据页的性能,可以认为这样的操作成本很低。
更新过程
为了说明普通索引和唯一索引对更新语句性能的影响,我们先来普及一下 change buffer 这个概念。
当我们需要更新一个数据页的时候,如果数据也在内存中久直接更新,而如果这个数据页没有在内存中,在不影响数据一致性的情况下, InnoDB 会将这些更新操作缓存在 change buffer 中。这样就不需要立即取磁盘中读取这个数据页进行engine了。在下次需要访问这个数据页的时候,再将数据页读入内存,然后执行 change buffer 中相关数据页的操作。这种方式就能保证数据逻辑的正确性,并且节省随机读取 IO 消耗,而不是进行频繁随机读取。这里要特别注意,随机读写可能是数据库里面消耗最高的操作了。
需要说明的是,虽然名字叫作 change buffer 实际上它是可以持久化的数据。也就是说, change buffer 在内存中有拷贝,也会被写入到磁盘上。
将 change buffer 中的操作应用到原数据页,得到最新的结果的过程称为 merge。除了访问这个数据页会触发 merge 外系统有后台线程会定期 merge。在数据库正常关闭的过程中,也会触发 merge 操作。
数据读入内存是需要占用 buffer pool 的,如果我们需要更新的操作记录在 change buffer ,可以减少读磁盘,而且这种方式可以用避免短时间内占用内存,提高内存利用率。
那么哪些情况下可以使用 change buffer 呢?
对于唯一索引的情况所有的更新情况都要判断是否会违反唯一性约束,比如我们在插入一条记录的时候,我们需要先判断是否已经存在这条记录,只要有我们去扫表才能判断,我们就需要把对应的数据页读入内存,如果已经读入内存,如果已经读入内存就直接更新插入就行。没有必要去使用 change buffer ,反正都必须先读入内存。
因此,唯一索引的更新就不能使用 change buffer 这个东西。实际上就只有普通索引可以使用 change buffer.
change buffer 用的是 InnoDB buffer pool 里面的内存,change buffer 的大小可以通过参数 innodb_change_buffer_max_size 来动态设置。这个参数默认是 25。表示最多占用 buffer pool 百分之 25 应用于作 change buffer。
Change Buffer 的使用场景
通过上面的分析,你已经清楚了使用 change buffer 对更新过程的加速作用,也清楚了 change buffer 只限于用在普通索引的场景下,而不适用于唯一索引。那么,现在有一个问题就是:普通索引的所有场景,使用 change buffer 都可以起到加速作用吗?
因为 merge 的时候是真正进行数据更新的时刻,而 change buffer 的主要目的就是将记录的变更动作缓存下来,所以在一个数据页做 merge 之前,change buffer 记录的变更越多(也就是这个页面上要更新的次数越多),收益就越大。
因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好。这种业务模型常见的就是账单类、日志类的系统。
反过来,假设一个业务的更新模式是写入之后马上会做查询,那么即使满足了条件,将更新先记录在 change buffer,但之后由于马上要访问这个数据页,会立即触发 merge 过程。这样随机访问 IO 的次数不会减少,反而增加了 change buffer 的维护代价。所以,对于这种业务模式来说,change buffer 反而起到了副作用。
所以实际情况中,如果是并发量不高的业务,还是在底层对数据做保护不要犹豫,直接选用唯一索引是比较好的选择。如果是对更新性能要求极高的场景,可以考虑建普通索引,然后在代码里面对唯一性的情况进行保护。
change buffer 和 redo log
理解了 change buffer 的原理,你可能会联想到我在前面文章中和你介绍过的 redo log 和 WAL。
在前面文章的评论中,我发现有同学混淆了 redo log 和 change buffer。WAL 提升性能的核心机制,也的确是尽量减少随机读写,这两个概念确实容易混淆。所以,这里我把它们放到了同一个流程里来说明,便于你区分这两个概念。
现在,我们要在表上执行插入语句:
mysql> insert into t(id,k) values(id1,k1),(id2,k2);
这里,我们假设当前 k 索引树的状态,查找到位置后,k1 所在的数据页在内存 (InnoDB buffer pool) 中,k2 所在的数据页不在内存中。如图 2 所示是带 change buffer 的更新状态图。

这条插入操作做了如下操作:
1. page 1 在内存中,直接更新内存。
2. page 2 没有在内存中,就在内存的 change buffer 区域,记录下「我要往 page 2 插入行」这个信息。
3. 将这两个动作记入 redo log 中。
做完这些事情事务就可以完成了。成本是 更新两处内存,一个 直接更新内存 一个 更新 change buffer。然后一次写 redo log 写磁盘操作。
同时图中虚线部分不是后台操作,不影响更新时间。
前面我说了触发 merge 除了定时 merge 正常 shutdown MySQL 以外 如果立即查询对应页上的数据也会立即触发和 change buffer 的 merge。
如果读语句发生在更新语句后不久,内存中的数据都还在,那么此时的这两个读操作就与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关了。所以,我在图中就没画出这两部分。

从图中可以看到:
1. 读 Page 1 的时候,直接从内存返回。有几位同学在前面文章的评论中问到,WAL 之后如果读数据,是不是一定要读盘,是不是一定要从 redo log 里面把数据更新以后才可以返回?其实是不用的。你可以看一下图 3 的这个状态,虽然磁盘上还是之前的数据,但是这里直接从内存返回结果,结果是正确的。
2. 要读 Page 2 的时候,需要把 Page 2 从磁盘读入内存中,然后应用 change buffer 里面的操作日志,生成一个正确的版本并返回结果。
可以看到直到要读 page 2 的时候,这个数据页才会被读入内存。
所以,如果要简单对比这两个机制在提升更新性能上的收益的话, redo log 主要节省的是随机写的磁盘的 io 消耗。
由于唯一索引用不上 change buffer 的优化机制,因此如果业务可以接受。从性能的角度出发应该先考虑非唯一索引。但是我前面也说了,还是看业务来,如果对性能本就没有什么要求,并且代码质量才是头等大问题,那应该毫不犹豫的使用唯一索引。可以让你避免很多麻烦。
Reference:
本读书笔记皆来自发布在极客时间的 林晓斌(丁奇)的 MySQL 实战45讲:
极客时间版权所有: https://time.geekbang.org/ 版权所有:
https://time.geekbang.org/column/article/70848
【MySQL 读书笔记】普通索引和唯一索引应该怎么选择的更多相关文章
- MySQL 笔记整理(9) --普通索引和唯一索引,应该怎么选择?
笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> (本篇内图片均来自丁奇老师的讲解,如有侵权,请联系我删除) 9) --普通索引和唯一索引,应该怎么选择? 假如你在维护一个市民系统, ...
- MySQL 普通索引和唯一索引的区别
该文为< MySQL 实战 45 讲>的学习笔记,感谢查看,如有错误,欢迎指正 一.查询和更新上的区别 这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响.建议尽量选择普通索引 ...
- MySQL的几个概念:主键,外键,索引,唯一索引
概念: 主键(primary key) 能够唯一标识表中某一行的属性或属性组.一个表只能有一个主键,但可以有多个候选索引.主键常常与外键构成参照完整性约束,防止出现数据不一致.主键可以保证记录的唯一和 ...
- Mysql索引介绍及常见索引(主键索引、唯一索引、普通索引、全文索引、组合索引)的区别
Mysql索引概念:说说Mysql索引,看到一个很少比如:索引就好比一本书的目录,它会让你更快的找到内容,显然目录(索引)并不是越多越好,假如这本书1000页,有500也是目录,它当然效率低,目录是要 ...
- MySQL 普通索引、唯一索引和主索引
1.普通索引 普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度.因此,应该只为那些最经常出现在查询条件(WHEREcolumn=)或排序条件(ORDERBYcolumn ...
- mysql中怎样查看和删除唯一索引
mysql中怎样查看和删除唯一索引. 查看唯一索引: show index from mytable;//mytable 是表名 查询结果例如以下: 查询到唯一索引后,怎样删除唯一索引呢,使用例如以下 ...
- Mysql主键索引、唯一索引、普通索引、全文索引、组合索引的区别
原文:Mysql主键索引.唯一索引.普通索引.全文索引.组合索引的区别 Mysql索引概念: 说说Mysql索引,看到一个很少比如:索引就好比一本书的目录,它会让你更快的找到内容,显然目录(索引)并不 ...
- MYSQL学习笔记——sql语句优化之索引
上一篇博客讲了可以使用慢查询日志定位耗时sql,使用explain命令查看mysql的执行计划,以及使用profiling工具查看语句执行真正耗时的地方,当定位了耗时之后怎样优化呢?这篇博客会介绍my ...
- 如何选择普通索引和唯一索引《死磕MySQL系列 五》
系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强 ...
随机推荐
- 一致性Hash漫画图解
一年之前—— 未来两年内,系统预估的总订单数量可达一亿条左右. 按Mysql单表存储500万条记录来算,暂时不必分库,单库30个分表是比较合适的水平分表方案. 于是小灰设计了这样的分表逻辑: 订单表创 ...
- Linux用户和权限管理看了你就会用啦
前言 只有光头才能变强 回顾前面: 看完这篇Linux基本的操作就会了 没想到上一篇能在知乎获得千赞呀,Linux也快期末考试了,也有半个月没有写文章了.这篇主要将Linux下的用户和权限知识点再整理 ...
- windows环境设置mysql自动备份(测试成功)
00.背景介绍 最近做了个小程序,使用的是mysql数据库,涉及到将程序数据备份的事:虽然大部分数据库客户端工具都具有备份功能,但并不能做到定期自动备份:在Windows环境下,手工备份MySQL是很 ...
- python类属性用法总结
属性的定义:python中的属性其实是普通方法的衍生. 操作类属性有三种方法: 1.使用@property装饰器操作类属性. 2.使用类或实例直接操作类属性(例如:obj.name,obj.age=1 ...
- 程序猿想聊天 - 創問 4C 團隊教練心得(二)
在第二天裡,主要談的是關於 Courage (勇氣) . Co-Create (共創) 的部分因為我們不是真實的團隊,就沒有繼續下去了 一早開始先回顧了一下前一天的內容,接下來我們就開始玩小遊戲 這個 ...
- WPF Geometry 添加Path数据
当图片转svg,svg转Xaml后,根据数据加载显示图片 DrawingImage: <DrawingImage x:Key="Image.Search"> <D ...
- css3 动画 总结
原来的时候写过一个小程序,里面有一个播放背景音乐的按钮(也是一个圆形的图片),它是一直在旋转的,当我们点击这个按钮的可以暂停或者播放背景音乐.当初的这个动画,是同事自己写的,我看到的时候以为是他在上面 ...
- 微信小程序 选择微信自带的地址 用户授权选择了拒绝
// 选择微信自带地址 addAddr:function () { wx.chooseAddress({ success: function (res) { self.setData({ addrIn ...
- 《JavaScript高级程序设计》笔记:使用Canvas绘图(15)
基本用法 要使用<canvas>元素,必须先设置其width和height属性,指定可以绘图的区域大小.出现在开始和结束标签中的内容是后备信息,如果浏览器不支持<canvas> ...
- 第三次上机,ADO接口的使用
<html> <head> <title>Reg</title> </head> <body><center> &l ...