当前位置: 首页 > 科技观察

不了解InsertBuffer的请举手

时间:2023-03-16 00:59:21 科技观察

BufferPool缓冲池回顾在说InnoDB激动人心的新特性InsertBuffer之前,我们有必要回顾一下BufferPool(缓冲池)的概念。前面提到,InnoDB存储引擎是基于磁盘存储的,以页为单位管理其中的记录。因此,它可以看作是一个基于磁盘的数据库系统(Disk-baseDatabase)。为了缓解CPU与磁盘速度之间的矛盾,基于磁盘的数据库系统通常采用缓冲池技术来提高数据库的整体性能。缓冲池其实就是一块内存区域,没什么特别的。对于数据库中页面的读取操作,首先从磁盘读取的页面存储在缓冲池中。这个过程也称为修复缓冲池中的页面。这样,下次读取同一个页面时,如果该页面在缓冲池中,则可以直接读取该页面,而无需去磁盘读取。对于数据库中页面的修改操作,首先修改缓冲池中的页面,然后以一定的频率刷新到磁盘中。简单的说,缓冲池就是利用内存的速度来弥补磁盘速度较慢对数据库性能的影响。当然,缓冲池毕竟不是无限的,不可能所有的数据都存储在缓冲池中。InnoDB使用一种称为Checkpoint的机制来确定哪些数据应该从缓冲池中删除(移动到磁盘)。我们在上一篇文章中也有说明,忘记的朋友可以看看上一篇文章。InsertBufferInsertBufferInsertBuffer这个名字可能会让朋友以为是BufferPool的一个组件。InsertBuffer其实是物理页面的一个组成部分,一棵B+树,页面存储在磁盘上,而BufferPool是一块内存区域。不过需要注意的是,BufferPool中会包含一些InsertBuffer的信息。我们来看一下InnoDB存储引擎的内存结构:可以看到InnoDBBufferPool包含的数据页类型有:索引页、数据页、undo页、InsertBuffer、自适应哈希索引、锁信息、数据词典信息等以问题为导向,对于InsertBuffer,我们需要明确两个问题:InsertBuffer能解决什么问题?什么情况下可以使用InsertBuffer?通常我们在建表的时候,都会给主键设置一个auto键。增长特性(AUTO_INCREMENT),即主键按升序插入。上一篇文章提到过,聚簇索引一般都是建立在主键上的,也就是说聚簇索引的插入一般是顺序的,不需要随机从磁盘读取。例如:CREATETABLEuser(idINT(11)AUTO_INCREMENT,usernameVARCHAR(30),PRIMARYKEY(id));id是一个自增主键,当我们插入一条新的记录时,不需要赋id或者NULL值,存储引擎会帮我们自动增长这个值。同时,页面中的行记录是按照主键id的值顺序存储的,所以当我们插入新的行记录时,一般来说,磁盘不需要随机读取另一个页面中的记录,所以速度非常快。当然,并不是所有的主键插入都是顺序的。在一些业务场景中,可能需要使用UUID作为主键。即使定义为自增长类型,如果每次插入都是UUID产生的指定值而不是NULL,那么它的插入显然是随机的。经过这样分析,好像我们的插入性能会更好,但是不可能一张数据库表只有一个聚簇索引,还有其他的辅助索引。事实上,辅助索引确实是插入性能的关键。例如,我们定义一个非聚集的非唯一索引用户名:CREATETABLEuser(idINT(11)AUTO_INCREMENT,usernameVARCHAR(30),PRIMARYKEY(id),key(username));插入的时候,数据页的存储确实是按照自增主键id顺序存储的,是正确的。然而,索引的本质是什么?它是B+树,是存储在磁盘上的物理文件。那么当我们构建辅助索引username的B+树时,非聚集索引的叶子节点的插入不再是顺序的,也就是说我们需要离散访问磁盘页。正是由于随机读的存在,导致了插入操作的性能下降。类似于“并非所有的主键插入都是顺序的”,在某些情况下,二级索引的插入也可能是顺序的,或者更顺序的。比如user表中有一个time字段,用来表示用户购买商品的时间。一般情况下,用户的购买时间是一个辅助指标,用于按时间条件查询。但是在插入的时候,是按照时间的增量来插入的,所以插入也是比较有顺序性的。到此为止,说了半天,好像还没看到InsertBuffer的影子?别担心,我们来了。InnoDB存储引擎开创了InsertBuffer的设计。对于辅助索引的插入或更新操作,并不是每次都直接插入到索引页(磁盘页)中,而是先判断插入的辅助索引页是否在BufferPool中:如果存在,则直接插入;如果没有,那么先放入一个InsertBuffer对象中,貌似是在欺骗数据库:告诉数据库辅助索引的叶子节点已经插入成功(在磁盘上),其实并不是,只是存储在内存InsertBuffer中。当然,这个叶子节点不可能一直保存在InsertBuffer中,对吧,这个辅助索引的B+树毕竟是要建立起来的。具体来说,InnoDB会按照一定的频率和情况对InsertBuffer和辅助索引页的子节点进行Merge(合并)操作。这时候相当于将多个叶子节点的插入操作合并为一个操作(因为在一个索引页中),这样就大大提高了辅助索引的插入性能。简单总结一下:InsertBuffer是一棵B+树。如果需要插入记录的辅助索引页不在BufferPool中,则需要先插入辅助索引记录。然后在适当的地方将这个B+树合并(Merge)到真正的二级索引中。举一个现实生活中的例子:我们去图书馆还书。对于图书管理员来说,他需要做的就是插入操作。管理员在1小时内接受了100本书。这时候他有2个方法把归还的书放回书架:每归还一本书,就把书送回书架,暂时不执行归位操作,再把这些书送回书架有空立刻上架上述方法1中,管理员需要进出图书馆100次,不断爬上爬下才能完成还书操作,既费力又低效。使用方法二,管理员只需要对要归还的图书进行分类,进出图书馆一次即可。对于同一位置的图书,无论数量多少,只需要爬一次楼梯,大大减轻了管理员的工作量。那么,在什么情况下可以使用InsertBuffer来提高插入操作的性能呢?该索引是辅助索引,索引不是唯一索引。为什么InsertBuffer不适合做唯一的辅助索引?一个很简单的嵌套娃娃问题(滑稽):如果辅助索引是唯一的,那么当要插入的对象存储在InsertBuffer中时,数据库需要在磁盘上搜索索引页来判断插入记录的唯一性.显然,如果进行查找,就会有离散读的发生,导致InsertBuffer失去意义。我们以图书馆管理为例:如果图书馆只允许放一本书,那么当我们还一本书到图书馆时,管理员必须爬到图书馆管理的指定位置来确认和判断这本书.这本书是独一无二的吗?这个过程相当于产生了一个IO操作。另外,InsertBuffer既有优点也有缺点。考虑一个极端的情况:如果数据库中有大量的insert操作,而且这些操作都涉及到非唯一非聚集索引,即使用InsertBuffer。如果此时数据库崩溃了,肯定有大量的InsertBuffer没有合并到实际的辅助索引中,此时的恢复可能需要很长时间。ChangeBufferInnoDB从1.0.x版本开始就引入了ChangeBuffer,现在有些博客也讲ChangeBuffer,容易让小白混淆,其实是InsertBuffer的升级版。从这个版本开始,InnoDB存储引擎可以缓冲DML操作——INSERT、DELETE和UPDATE。它们对应于:InsertBuffer、DeleteBuffer和Purgebuffer。和前面的InsertBuffer一样,ChangeBuffer应用于objects仍然是一个非唯一的二级索引。对记录的UPDATE操作可以分为两个过程:标记记录为已删除:对应于DeleteBuffer实际删除记录:对应于PurgeBuffer