插入缓冲
Insert Buffer 可能是InnoDB存储引擎中最让人激动和兴奋的一个功能了。这个名字可能让人误以为插入缓冲是缓冲池中的一部分,其实不然,InnoDB缓冲池中又Insert Buffer信息固然没错,但是Insert Buffer和数据页一样,也是物理页的一个组成部分。
问题:通常在一张表中,不仅存在着聚集索引,也存在不唯一的辅助索引。在进行插入操作的时候,数据页的存放还是按照主键进行顺序存放的,但是对于非聚集索引(辅助索引),叶子节点的插入不再是顺序的了,这时就需要离散地访问非聚集索引页。这时,由于随机读取的存在而导致了插入操作性能的下降。
作用:为了解决这个问题,InnoDB开创性地提出了Insert Buffer的设计,对于非聚集索引的插入或者更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在,那就直接插入缓冲池中的数据;如果不在,则先放入到一个Insert Buffer对象中,好似一种骗术:数据库这个非聚集索引已经插入到叶子节点,而实际上并没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引叶子节点的merge操作,这时通常能将多个插入合并到一个操作中(因为是在同一个索引页中),这就大大地提高了对于非聚集索引插入操作的性能。
触发条件:InnoDB Buffer的使用需要同时满足以下两个条件:
- 索引是辅助索引
- 索引不是唯一的
当满足以上两个条件的时候,InnoDB存储引擎会使用Insert Buffer,提高插入操作的性能。辅助索引不能是唯一的,因为在插入缓冲的时候,数据库并不会去查找索引页来判断插入的记录的唯一性。如果查找的话又会出现离散读取的情况发生,从而导致Insert Buffer失去意义。
升级:InnoDB从1.0x版本开始引入了Change Buffer,可以将其视为Insert Buffer的升级。从这个版本开始,InnoDB存储引擎可以对DML操作–insert,delete,update操作都进行缓冲,分别是Insert Buffer,Delete Buffer,Purge Buffer。与之前一样,他们的使用对象都是非唯一的辅助索引。
内部实现:Insert Buffer内部实现的数据结构是一棵B+树,在现在的mysql版本,会有一棵全局的B+树,负责对所有表的辅助索引进行Insert Buffer。这棵B+树存放在共享表空间中,默认就是ibdata1中。因此,试图通过独立表空间ibd文件恢复表中的数据时,往往会导致CHECK TABLE失败。这是因为表的辅助索引中的数据还在Insert Buffer中,也就是共享表空间中,所以通过ibd文件进行恢复后,还需要REPAIR TABLE操作来重建表上的所有辅助索引。
当一个辅助索引要插入到页时,如果这个页在缓冲池中,会直接对缓冲池中的数据进行操作;如果这个页不在缓冲池中,那么InnoDB存储引擎首先根据上述规则构造一个search key,接下来查询Insert Buffer这棵B+树,然后将这条记录插入到Insert BufferB+树的叶子节点中。
Insert Buffer的合并:刚才在内部实现中有说道,当需要实现插入记录的索引页不在缓冲池中时,那么InnoDB存储引擎会将其插入到B+树中。那么Insert Buffer中的记录什么时候合并到真正的辅助索引中呢。Merge操作可能发生在以下几种情况:
- 辅助索引页被读取到缓冲池中时;
- Insert Buffer Bitmap页追踪到该辅助索引页已经无可用空间时;(Insert Buffer Bitmap用来追踪每个辅助索引页的可用空间,如果可用空间小于1/32,那么会强制进行合并操作,即强制读取辅助索引页)
- Master Thread;
两次写
问题:当数据库宕机时,可能InnoDB存储引擎正在写入到某个页到表中,而这个页只写了一部分,比如16kb,只写了前4kb,之后就发生了宕机,这种情况被称为部分写失效。并且在这种情况中,页本身发生了损坏的情况下,是无法通过重做日志来进行恢复的。因此需要一个页的副本,当写入发生失效时,先通过页的副本来还原页,再进行重做,即两次写;
两次写:对于上面这个图,解释如下:
我们看到的double分为两个部分,其中一个是内存中的,大小为2MB,另外一部分是物理磁盘的共享表空间中的,也就是ibdata文件中的连续的128个数据页,128*16K,也就是2MB,在对缓冲池的脏数据进行刷盘的时候,并不会直接写到磁盘中,而是先将数据复制到内存中的doublewrite的缓存中,之后通过缓存,再分两次,每次1MB的写入共享表空间的物理磁盘上。完事儿之后然后立马同步磁盘。这样,一份数据就在磁盘上有两个副本。
在上面的过程中,从数据页往内存中的doublewrite缓冲中写入数据是顺序的,相对来讲比较快,而从内存中的doublewrite落盘时候是离散的IO,相对来讲比较慢。如果在写入磁盘的时候出现了问题,innodb将会在共享表空间的doublewrite中找到该数据页的一个副本,将其复制到表空间文件,再应用重做日志,也就是redo log。
通过这种方式,可以在数据真实落盘之前防患于未然,一旦刷盘的动作失败,可以从共享表空间中找到副本进行恢复。我们都知道,ibdata文件一般比较大,如果你可以接收刷盘失败带来的损失,也可以使用skip_innodb_doublewrite来禁止使用doublewrite的功能,这个参数也可以改进一些性能问题。
异步IO
为了提高磁盘操作性能,当前的数据库系统都是采用异步IO(AIO)的方式来处理磁盘操作。AIO的优势在于不需要等待在一条IO请求完成之后才能发送下一条IO请求;
另一个优势是可以进行IO Merge操作,也就是将多个IO合并为一个IO,这样可以提高IOPS的性能。例如用户需要访问的页(space,page_no)为:(8,6)(8,7)(8,8),每个页的大小是16kb,那么同步IO需要3次操作,而AIO会判断到这三个页是连续的,因此AIO会发送一个IO请求,从(8,6)开始,读取48kb的数据。这样可以检查IO操作的次数,提高磁盘操作性能。。