InnoDB的四大特性

InnoDB存储引擎的三个关键特性:插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)。

insert/change buffer

什么是change buffer?

在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。

它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。

flush 链表的添加元素的条件

我们说 free 链表的添加条件是什么?

  1. 这个页已经从磁盘读入了Buffer Pool中。
  2. 当我们修改了此页数据,此缓存页变为了脏页,加入到flush链表中等待刷盘。

change buffer的作用

不清楚change buffer作用的朋友,做了下面的对比应该一清二楚了。

没有change buffer时,更新一条内存中不存在的页

那么假设我们现在读取的元素不在内存中,此时有人写了一个update语句更新数据页,InnoDB引擎的工作流程如下:

  1. 从磁盘加载数据页到缓冲池,一次磁盘随机读操作;
  2. 修改缓冲池中的页,一次内存操作;
  3. 写入redo log,一次磁盘顺序写操作;

没有命中缓冲池的时候,至少产生一次磁盘IO,对于写多读少的业务场景,是否还有优化的空间呢?

当出现change buffer时,更新一条内存中不存在的页(和flush链表的区别)

  1. 在写缓冲中记录这个操作,一次内存操作;
  2. 写入redo log,一次磁盘顺序写操作;

可以发现,这样change buffer的出现直接减少了一次磁盘IO。

读取数据是否会出现一致性问题?

当然不会,我们change buffer中,相当于以页为单位,存储了许多数据修改的逻辑。当change buffer没有刷到磁盘时,磁盘中的数据肯定是脏数据。那么读出来的数据肯定是不对的。

解决方案也很简单,就是先把脏数据读到内存中,再根据change buffer中对此数据页修改记录,还原出最新版本的数据页信息即可。(注意,这时候change buffer相关此页的数据就没了,同步到缓存中了。之后再修改此磁盘页的数据,就会进入flush链表中了)。是不是感觉融会贯通多了?

change buffer的刷盘时机

  1. 如上面描述的,当change buffer中有数据的时,发生读盘操作。会进行一次磁盘读取,再配合change buffer获取到最新数据。此时change buffer中的该页信息会刷掉;
  2. 有一个后台线程,会判断数据库空闲时刷盘;
  3. 数据库缓冲池不够用时;
  4. 数据库正常关闭时;
  5. redo log写满时;(redo log几乎不会写满,否则会造成MySQL吞吐量在一段时间内严重下降)

change buffer中存在数据时发生宕机怎么办?

每次change buffer中的数据会同步到redo log中,数据库异常崩溃,能够从redo log中恢复数据。

为什么change buffer是只针对于二级索引的优化呢?

我们来对比主键索引与二级索引进行一个新增操作的区别:

即将插入的记录所在目标页在内存中

  1. 对于唯一索引来说,找到3和5之间的位置,判断到没有冲突,插入这个值,语句执行结束;
  2. 对于普通索引来说,找到3和5之间的位置,插入这个值,语句执行结束。

这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的CPU时间。

但,这不是我们关注的重点。

即将插入的记录所在目标页不在内存中

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

将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。(我们可能积累了一个页中的很多数据,然后一起更新整个页,从而减少IO)。

在面试过程中,可以提起解决过这么一个问题。某天发现数据库的内存命中率从99%降低到了75%,整个系统处于阻塞状态,更新语句全部堵住。而探究其原因后,我发现这个业务有大量插入数据的操作,而他在前一天把其中的某个普通索引改成了唯一索引。

change buffer和redo的对比

这俩有啥可比性啊?一个缓存数据,一个日志文件,八竿子打不着啊!

然而,有了解过redo log的朋友,会知道redo log有一个特性和change buffer共有的一个特性是:尽量减少随机读写。那么围绕着这个角度我们来分析一下change buffer与redo log的区别。

redo log的顺序写

现在,我们要在表上执行这个插入语句:

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

这里,我们假设当前是以k为索引的二级B+树索引,查找到位置后,k1所在的数据页在内存(InnoDB buffer pool)中,k2所在的数据页不在内存中。如图所示是带change buffer的更新状态图。

image.png

分析这条更新语句,你会发现它涉及了四个部分:内存、redo log(ib_log_fileX)、 数据表空间(t.ibd)、系统表空间(ibdata1)。

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

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

做完上面这些,事务就可以完成了。所以,你会看到,执行这条更新语句的成本很低,就是写了两处内存,然后写了一处磁盘(两次操作合在一起写了一次磁盘),而且还是顺序写的。(注意:上述的三个步骤是一个事务,也就是必须redo log写完,事务才算搞定,这也印证了为啥redo log一定可以恢复change buffer中的数据)

change buffer减少随机读

我们现在要执行

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

这里,我画了这两个读请求的流程图。

如果读语句发生在更新语句后不久,内存中的数据都还在,那么此时的这两个读操作就与系统表空间(ibdata1)和 redo log(ib_log_fileX)无关了。所以,我在图中就没画出这两部分。

image

从图中可以看到:

  1. 读Page 1的时候,直接从内存返回。
  2. 要读Page 2的时候,需要把Page 2从磁盘读入内存中,然后应用change buffer里面的操作日志,生成一个正确的版本并返回结果。

可以看到,直到需要读Page 2的时候,这个数据页才会被读入内存。到真正写盘时是在数据库空闲或者不得已的时候才会进行。而当刷盘的时候,可能会有多条语句多次操作磁盘,此时将整个页整体刷入磁盘,就减少了许多次与磁盘之间的交互,从而达到减少磁盘IO的目的。

总结

所以,如果要简单地对比这两个机制在提升更新性能上的收益的话,redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。

double write buffer

以页为单位写盘是原子操作么?

我们知道,哪怕改一条数据,但是写盘依旧会把该条数据所在的磁盘页从内存中全部写入磁盘中。当然,加载也是以页为单位的。但是以页为单位的写数据是原子性的么?

答案当然不是。一个页那么多数据,必然不是原子性的。那么问题来了。万一一个磁盘页的数据更新了一半发生宕机了,这时候该怎么保证数据不丢失呢?

这时候大家又要抢答了。我知道有个redo log日志,InnoDB就是靠他保证数据不丢失的!对,但是redo log日志中记录的是对页的物理操作。而如果发生 partial page write(部分页写 入)问题时,此时重做日志(Redo Log)无能为力。 那么这种页写了一半的情况该如何解决呢?

doublewrite buffer解决页的部分写入

doublewrite buffer 是 InnoDB 在表空间上的 128 个页(2 个区,extend1 和 extend2),大小是 2MB。为了解决部分页写入问题。

当 MySQL 将脏数据 flush 到数据文件的时候, 先使用 memcopy 将脏数据复制到内存中的一个区域(也是 2M),之后通过这个内存区域再分 2 次,每次写入 1MB 到系统表空间,然后马上调用 fsync 函数,同步到独立表空间的磁盘上。在这个过程中是顺序写,开销并不大。

当第一次看到doublewrite buffer中的buffer时,一看到buffer就觉得是内存。但在这里,doublewrite buffer 实际上也是一个文件。 写系统表空间会导致系统有更多的 fsync 操作, 而硬盘的 fsync 性能因素会降低 MySQL 的整体性能。不过在存储上,doublewrite 是在一个连续的存储空间, 所以 硬盘在写数据的时候是顺序写,而不是随机写,这样性能影响不大,相比不双写, 降低了大概 5-10%左右。

因此,如果系统表中的doublewrite buffer写入失败,那么独立表中的实际磁盘数据更不可能写入成功了。因为这俩是有严格的先后顺序的。此时就需要从redo log中,整页的恢复数据。

如果doublewrite buffer写入成功,实际磁盘数据发生部分写入问题(数据库异常关闭的情况下启动),都会做数据库恢复(redo)操作,恢复 的过程中,数据库都会检查页面是不是合法(校验等等),如果发现一个页面校验结果不一致,则此时会用到双写这个功能。

自适应哈希索引(adaptive hash index)

我们知道InnoDB是不支持Hash索引的。最大的原因是因为这个数据结构不支持范围查询,在MySQL的使用环境来说,这个是非常不友好的。

普遍来讲,Hash索引确实不满足MySQL的底层索引要求。不过其接近O(1)的查询效率一直被InnoDB开发者们觊觎。因此,InnoDB的开发者们决定,将热点数据尽可能存储到hash索引中。

 Innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,建立哈希索引可以带来速度的提升。

经常访问的二级索引数据会自动被生成到hash索引里面去(最近连续被访问三次的数据),自适应哈希索引通过缓冲池的B+树构造而来,因此建立的速度很快。

预读

在从磁盘读取数据时,InnoDB会认为读取某个磁盘页数据时,InnoDB认为大概率还会访问次磁盘页附近的磁盘页,因此提前将这些访问磁盘页附近的磁盘页一起读入内存的机制。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大将黄猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值