AnthonyZero's Bolg

MySQL笔记-change buffer

概述

若是每一次的更新操作(例如insert、update和delete操做)都要操作磁盘,IO成本实在过高。 InnoDB 的数据是按数据页为单位来读写的。当须要读一条记录的时候,并非将这个记录自己从磁盘读出来,而是以页为单位(16KB),将其总体读入内存

当须要更新一个记录,就是要更新一个数据页:

  • 若是数据页在内存中(buffer pool中时)就直接更新
  • 若是这个数据页尚未在内存中(没有在buffer pool中)。InooDB 会将这些更新操作缓存在 change buffer 中。在下次查询须要访问这个数据页时,将数据页(DB磁盘上)读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性 数据一致性

将 change buffer 中的操作应用到原数据页,获得最新结果的过程称为 merge,更新操作避免了大量的磁盘随机访问I/O。
merge之后这时候Page为脏页了,不用担心,InnoDB会在合适的时候flush进行数据落地

如果要在这张表中插入一个新记录(4,400)的话,InnoDB的处理流程是怎样的?
第一种情况是,这个记录要更新的目标页在内存中。这时,InnoDB的处理流程如下:

  • 对于唯一索引来说,找到3和5之间的位置,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,找到3和5之间的位置,插入这个值,语句执行结束。这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的CPU时间。

第二种情况是,这个记录要更新的目标页不在内存中。这时,InnoDB的处理流程如下:

  • 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,则是将更新记录在change buffer,语句执行就结束了。

将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。

对于唯一索引来说,所有的更新操作都要先判断这个操作是否违反唯一性约束。比如,在上面要插入(4,400)这个记录,就要先判断现在表中是否已经存在k=4的记录,而这必须要将数据页读入内存才能判断。如果都已经读入到内存了,那直接更新内存会更快,就没必要使用change buffer了

change buffer对更新过程的加速作用,也清楚了change buffer只限于用在普通索引的场景下,而不适用于唯一索引

因此,只有普通索引才能使用change buffer,若是业务上能保证不会数据重复,那么最好使用普通索引(能够使用change buffer,且两类索引查询能力没有区别)

普通索引和唯一索引应该怎么选择。其实,这两类索引在查询能力上是没差别的,主要考虑的是对更新性能的影响。 由于唯一索引用不上change buffer的优化机制,因此如果业务可以接受,从性能角度出发我建议你优先考虑非唯一索引。 如果所有的更新后面,都马上伴随着对这个记录的查询,那么你应该关闭change buffer。而在其他情况下,change buffer都能提升更新性能

注意:不是全部的场景用change buffer都能加速:

  1. 设想一个对于写多读少的业务来讲,change buffer 记录的变动越多越划算,可以利用change buffer加速,例如帐单类日志类事务
  2. 反过来,一个业务的更新模式是写入以后立刻会做查询,change buffer里的内容很少,因为立刻作查询要访问数据页而发生merge,这样的io次数不会减小

change buffer与redo log的联系

首先redolog有两种作用: 一种记录普通数据页的改动,一种记录changebuffer的改动

假设要在表t上执行这个插入语句,其中k是辅助索引,且为普通索引

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

假设当前 在k索引树上查找到插入位置后, k1 所在的插入位置的数据页page1在内存 (InnoDB bufferpool) 中, k2位置 所在的数据页不在内存中。它涉及了四个部分:内存、 redo log ( ib_log_fileX )、 数据表空间(t.ibd)、系统表空间( ibdata1)。
Alt text

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

  1. Page 1 在内存中(buffer pool中),直接更新内存,直接把数据插入到表t中的位置
  2. Page 2 没有在内存中,就在内存的 change buffer 区域,记录下 “ 我要往 Page 2 插入一行 ” 这个信息
  3. 将上述两个动作记入 redo log 中(图中 3 和 4 )(包含了数据的变动和 change buffer 的变动)

这样的一个事务,写了两处内存(直接在内存更新和在内存中的change buffer记录),而后写了一处磁盘(两次操做合在一块儿写了一次磁盘中redolog)

若是以后有一个读请求:

1
select * from t where k in (k1, k2)

Alt text
若是读语句发生在更新语句后不久,那么数据页还在内存中,那么此时读操做与系统表空间( ibdata1 )和 redo log ( ib_log_fileX )无关了,page1处于内存中,page2不存在于内存中

读 Page 1 的时候,直接从内存返回更新后的内容,要读 Page 2 的时候,须要把 Page 2 从磁盘读入内存中,而后应用 change buffer 里面的操作日志(add id2,k2 to page2).最后生成正确的版本并返回结果

可以看到,直到需要读Page 2的时候,这个数据页才会被读入内存
所以,redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。

change buffer使用redolog的意义

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

虽然咱们更新数据时只更新内存的change buffer,可是在事务提交的时候,咱们把 change buffer 的操作也记录到 redo log 里了,因此崩溃恢复的时候,changebuffer 也能找回来
首先redo log里记录了数据页的修改以及change buffer新写入的信息。针对未写完的,此部分操作,还未写入redo log,因此事务还未提交,所以没影响。 针对,已经写完成的,可以通过redo log来进行恢复。

疑惑1: 主键id也是索引,那我们的新增操作如何利用 change buffer呢?
答:主键索引 唯一索引都用不上,都是对于那些二级索引的才有效。一个insert语句要操作所有索引的嘛,收益在二级索引

疑惑2: 即使我们不主动创建主键 也会生成一个默认的row_id来当做主键, 意味着表一定是有一个主键, 即唯一索引. insert操作 一定会涉及主键索引的变动, 所以change buffer针对 insert 是完全没有用的吗
答:insert的时候,写主键是肯定不能用change buffer了,但是同时也会要写其它索引,而其它索引中的“非唯一索引”是可以用的这个change buffer机制的