9 - 普通索引和唯一索引的选择

关键字

普通索引,唯一索引,change buffer,查询,更新

0.引子

在 MySQL 中的索引可以大致分为来两类:普通索引和唯一索引。

  • 普通索引:为索引项建立 B+ 树,加速数据查询的效率。
  • 唯一索引:在普通索引的基础上,对索引项添加了约束,即索引项目不可重复。如果在添加数据时,索引项重复,数据库会拒绝这个请求。

你可以看出,普通索引和唯一索引存在功能上的差距。除此之外,它们的性能还有很大的不同,今天,就从这两种索引的查询语句和更新语句的性能影响来帮助你决策如何选择索引类型。

1.查询过程

首先,回顾一下之前的内容,在 InnoDB 中,它的索引结构如下:


左边为主键索引,右边为用户添加的索引

假设,我们执行的查询语句是:

 select id from T where k=5

这个语句会从 B+树 的树根开始,按层搜索到叶子节点,最终到达数据页,然后通过二分查找定位记录。在最后的过程中,两种索引会出现差异:

  • 普通索引:找到第一个(5,500)记录后,依然会向后查找,直到找到不满足 k=5 的记录。
  • 唯一索引:由于唯一性的约束,在找到(5,500)之后,查询就结束了。

这两者的性能差距会有多少呢?

答案是:微乎其微,因为数据会将磁盘中的数据页(这涉及到操作系统的文件系统的处理方式,一般操作系统都是使用段页式存储文件,这其中的数据页,可能就是操作系统中的页的概念。)整块读入内存,在 InnoDB 中,每个数据页的大小默认是 16KB 。

也就是说,两种索引的查询方式的差异,也就是在内存中多读取一次数据的差异而已,这个操作成本对于 CPU 来说可以忽略不记。当然,如果你查询的数据恰好在页的末尾,可能会多进行一次磁盘操作,但是这种情况出现的概率非常小。

2.更新过程

要理解两种索引在更新中的差异,首先要理解一个概念:change buffer。

2.1change buffer

当我们对一个数据进行更新时,会出现两种情况:

  • 该数据的数据页已经在内存中,我们可以直接在内存中进行更新。
  • 该数据不在内存中,InnoDB 会将这个操作缓存在 change buffer 中。注意,此时数据库中的实际数据还没有发生改变,在之后系统会将 change buffer 中的数据存入到数据库中。

将 change buffer 的操作应用到原始数据页的操作,称为 merge。一般情况下,访问数据页会触发 merge 操作;同时,后台线程也会定期进行 merge,在数据关闭的过程中,也会进行 merge。

显然,使用 change buffer 可以减少磁盘操作,语句的执行速度也会得到提升。

2.2两种索引和 change buffer

唯一索引
对于唯一索引来说,它无法使用 change buffer ,因为唯一索引会在数据插入时检查该数据是否违反了唯一性约束,这就要求它必须从磁盘中将数据读出,判断数据是否重复。

既然唯一索引的插入必须读取磁盘,change buffer 的缓存作用也就没有了,反而徒增了 操纵 change buffer 的消耗。

普通索引
对普通索引来说,如果要更新的内存页不在内存中,只需要将更新记录放入 change buffer,语句执行就结束了,这大大提升了性能。

所以,对于普通索引来说,可以使用 change buffer 提升性能,但是 change buffer 也有自己的适用场景。

3.change buffer 的使用场景

  • merge 是真正数据更新的时刻,而 change buffer 的主要目的是将变更记录缓存下来。所以,在 merge之前,change buffer 内的记录越多,收益就越大。
  • 因此,对于写多读少的业务来说,页面写完之后马上访问的概率较小,此时使用 change buffer 的效果也最好。常见的业务模型有账单类、日志类系统。
  • 反过来,如果在变更之后立即会对数据进行访问,change buffer 不仅没有提升性能,反而起到了副作用。
  • 对于上面的情况,你应该主动关闭 change buffer:change buffer 使用了 buffer pool 中的内存,可以通过 innodb_change_buffer_max_size 设置它在 buffer pool 中的使用占比。
  • 实际中,普通索引 + change buffer 对更新量大的数据表的优化还是很明显的。

4.change buffer 和 redo log

其实在学习日志系统的时候,我一直认为 redo log 的功能是提供数据缓存,而实际上实现这个功能的是 change buffer 。 那么,这两个容易混淆的概念该怎么区分呢?

假设我们要在一个表上执行下面的插入语句:

mysql> insert into t(id,k) values(id1,k1),(id2,k2);

在这里,假设 k1 的数据页在内存(InnoDB buffer pool)中,k2 的数据页不再内存中,下图就是带 change buffer 的更新状态:
9-带changebuffer的更新过程.png

对于这个过程的描述,我将专栏作者的描述放在下面:

这条更新语句做了如下的操作(按照图中的数字顺序):

  1. Page 1 在内存中,直接更新内存;
  2. Page 2 没有在内存中,就在内存的 change buffer 区域,记录下“我要往 Page 2 插入一行”这个信息
  3. 将上述两个动作记入 redo log 中(图中 3 和 4)。

做完上面这些,事务就可以完成了。所以,你会看到,执行这条更新语句的成本很低,就是写了两处内存,然后写了一处磁盘(两次操作合在一起写了一次磁盘),而且还是顺序写的。

如果我们要执行 select * from t where k in (k1, k2) ,假设内存中的数据依然还在,那么它的执行如下:

9-带change buffer的读过程.png

描述如下:

  1. 读 Page 1 的时候,直接从内存返回。有几位同学在前面文章的评论中问到,WAL 之后如果读数据,是不是一定要读盘,是不是一定要从 redo log 里面把数据更新以后才可以返回?其实是不用的。你可以看一下图 3 的这个状态,虽然磁盘上还是之前的数据,但是这里直接从内存返回结果,结果是正确的。
  2. 要读 Page 2 的时候,需要把 Page 2 从磁盘读入内存中,然后应用 change buffer 里面的操作日志,生成一个正确的版本并返回结果。

可以看到,直到需要读 Page 2 的时候,这个数据页才会被读入内存。

综合我在上面两个加粗的地方,可以看出 redo log 和 change buffer 的区别:redo log 将更新记录缓存下来,并在事务执行完成之后(一般建议这么做,你可以通过innodb_flush_log_at_trx_commit = 1 设置每个事务执行后就将redo log 写入磁盘。)一次性顺序将日志写入磁盘。change buffer 节省从磁盘中读取数据的 IO 消耗。

总结

  • 唯一索引和普通索引在数据搜索上的很小。
  • change buffer 可以缓存数据变更操作。
  • 普通索引 + change buffer 可以大大提升频繁数据写入的情况。
  • redo log 可以缓存日志,并节省写磁盘的 IO 操作。
  • 如果业务能够保证数据不重复,建议使用普通索引。

课后思考

通过图9-带changebuffer的更新过程.png 你可以看到,change buffer 一开始是写内存的,那么如果这个时候机器掉电重启,会不会导致 change buffer 丢失呢?change buffer 丢失可不是小事儿,再从磁盘读入数据可就没有了 merge 过程,就等于是数据丢失了。会不会出现这种情况呢?

答案会在下一篇文章公布。

上期问题

在下面两种情况下,会出现上一节的问题:


9-课后问答答案1.png

9-课后问答答案2.png

以上就是本节内容,希望对你有所帮助。

注:本文章的主要内容来自我对极客时间app的《MySQL实战45讲》专栏的总结,我使用了大量的原文、代码和截图,如果想要了解具体内容,可以前往极客时间

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351