回顾
上节我们了解Master Thread的主要工作,它包括:
- 刷新redo日志到磁盘中.
- 合并插入缓冲.
- 回收undo日志(新版本中被Purge Thread分担).
- 刷新脏页。(新版本中被Page Cleaner Thread分担).
那么这个插入缓冲到底是什么东西?
面试中常有提及的自适应哈希又是怎么一回事?
我们这节来探索这些InnoDB中的关键特性-插入缓冲(Insert Buffer).
InnoDB的关键特性
- 插入缓冲(Insert Buffer).
- 双写(Double Write).
- 自适应哈希索引(Adaptive Hash Index).
- 异步IO(Async IO).
- 刷新邻接页(Flush Neighbor Page).
插入缓冲 Insert Buffer
首先,不要被插入缓冲这个名字所欺骗,它其实更多的是物理页的组成部分。
这个特性在往数据库插入数据时得到很好的体现,对Primary Key(通常我们称为主键)的插入时,如果该列有AUTO_INCREMENT的属性,在不声明主键列的值情况下,那么MySQL会帮你自动按照自增长的形式进行主键顺序递增,这种速度非常迅速。
Warning: 对UUID这种主键策略往往不能生效,因为UUID是随机的32位数值。是不遵循顺序增长这种规则的。
但是,对于非聚集索引,我们则需要引入插入缓冲来解决离散访问非聚集索引页导致性能低下的问题。这里举一个简单的例子:
id | name | age |
---|---|---|
1 | 张三 | 20 |
2 | 李华 | 53 |
3 | 刘备 | 12 |
这里我们假设id是主键,age作为二级索引,那么作为主键的id肯定是自增长的,但是age则不是按顺序排列的。如果此时插入数据,要同时更新二级索引,而更新二级索引这个操作是无法保证顺序插入的。
所以,这里我们能得到结论,插入缓冲是用来解决插入数据时对二级索引的离散插入而产生的IO问题。
这里有个博客举了一个非常生动的例子,我认为很好的诠释了离散插入的概念。有兴趣的朋友可以阅读一下。
innodb insert buffer 插入缓冲区的理解
InnoDB设计了Insert Buffer,对于非聚簇的非唯一索引的插入或者更新操作,不是每一次都同步到索引页中,而是先判断插入的非聚簇索引页是否在缓冲池中,如果在,则直接插入;如果不在,则先放入到Insert Buffer对象中,然后等待线程池在适当的时机进行Merge操作,如此一来,多次插入产生的非聚簇的非唯一索引则一次性合并到了一次Merge操作中了,大大提高了插入性能。但是凡事都有利弊,如果Insert Buffer中存在大量的未合并索引,那么如果数据库发生故障,这个恢复时间会比较漫长。
那么为什么这个非聚簇索引不能是唯一索引呢?这是因为在使用Insert Buffer的时候,数据库不会去查找索引页来判断插入的记录的唯一性。如果先进行读操作再进行写操作,那么Buffer的意义就不复存在了。
下面放一张MySQL的官方文档图加深理解:
可以通过命令 SHOW ENGINE INNODB STATUS
来查看插入缓冲的信息,下面对Ibuf中各参数进行一个解释:
- seg size : 当前Insert Buffer的大小,需要乘与16KB进行计算.
- free list len : 空闲列表的长度.
- size : 已经合并j记录页的数量.
- inserts : 插入的记录数.
- merged recs : 合并的插入记录数量.
- merges : 合并的次数,也就是实际读取页的次数
- merges : merges recs : 如果这个数为1/3,插入缓冲将对于非聚集的非唯一索引页的离散IO逻辑请求约降低了2/3
Change Buffer
InnoDB从1.0.x版本开始引入Change Buffer,可以对DML操作-INSERT、DELETE、UPDATE都进行缓冲。
与Insert Buffer一样,Change Buffer是对非聚集的非唯一索引进行缓冲的。
Change Buffer可以细分为:Insert Buffer、Delete Buffer、Purge Buffer(缓冲在后台发生的物理删除操作)
一次UPDATE操作的过程
- 将旧记录标记为已删除.
- 真正将旧记录删除
- 插入新的记录
过程: 在Delete Buffer中将此记录标记为删除,然后真正删除后,在Purge Buffer中记录真正的删除标识,然后进行插入,是否需要Insert Buffer取决于索引的类型与是否能命中当前缓冲池中索引页。可见,要缓冲更新操作至少需要Insert Buffer、Delete Buffer。(An update operation is a combination of an insert and a delete)
innodb_change_buffering
InnoDB提供的Change Buffer配置参数。你可以通过这个参数配置你的MySQL缓冲行为。默认为all.
- all
默认值:缓冲inserts, delete-marking operations, and purges
- none
不需要任何缓冲
- inserts
缓冲Insert行为
- deletes
缓冲 delete-marking operations.
- changes
缓冲 inserts and delete-marking operations.
- purges
缓冲在物理表中真正执行的删除操作:purges
innodb_change_buffer_max_size
此参数用于控制Change Buffer最大使用内存的数量.默认值为25,它表示Change Buffer最多占用缓冲池的25%,最多可以设置到50%.
你可以用SHOW ENGINE INNODB STATUS
来查看当前Change Buffer的行为
- merge operations
显示Change Buffer中每个操作的次数,insert表示Insert Buffer;delte mark 表示Delete Buffer;delete 表示Purge Buffer.
- discarded operations
当Change Buffer准备发生merge时,表已经被删除。此时可以将这部分合并抛弃。
InnoDB实现Insert Buffer的原理
Insert Buffer是一颗B+树。它是全局唯一的(4.1版本后),负责对所有的表的辅助索引进行Insert Buffer。它存在共享表空间中,默认在ibdata1中。因此,试图通过独立表空间ibd文件恢复表中数据时,往往会导致CHECK TABLE失败。这是因为表的辅助索引中的数据可能还在Insert Buffer中,也就是共享表空间中,所以通过ibd文件进行恢复后,还需要进行REPAIR TABLE操作来重建表上所有的辅助索引。
非叶子节点结构
非叶子节点存放的是search key(键值),9个字节
space
待插入记录所在表的表空间id,4个字节。(在InnoDB中,每个表都有属于自己的唯一space id,通过space id可以查询得知是哪张表)marker
用来兼容老版本的Insert Buffer,1个字节offset
页所在的偏移量,4个字节
插入缓冲的细节
当一个非聚簇的非唯一索引要插入到页(space,offset)时,如果这个页不在缓冲池中,那么InnoDB会构造一个search key,然后查询Insert Buffer这棵B+树,然后将这条缓冲记录插入到Insert Buffer中。
Insert Buffer的叶子节点
space、marker、offset的规则都跟search key一样,占用9个字节。接下来我们来介绍一下metadata.
-
metadata
占用4个字节。它存储的内容如下
名称 | 字 节 |
---|---|
IBUF_REC_OFFSET_COUNT | 2 |
IBUF_REC_OFFSET_TYPE | 1 |
IBUF_REC_OFFSET_FLAGS | 1 |
IBUF_REC_OFFSET_COUNT:
整数,用来排序每个记录进入Insert Buffer的顺序,通过这个顺序回放(replay)得到记录的正确值。
剩下的则是需要合并的索引记录了。由此可见,叶子节点有13个额外的字节+需要合并的索引记录组成。
Insert Buffer Bitmap
启用Insert Buffer后,原本要插入到非聚簇索引页(space,page_no)的数据可能被插入到Insert Buffer B+树中了,那么为了保证每次Merge Insert Buffer页必须成功,还需要有一个特殊的页来标记每个非聚簇索引页(space,page_no)的可用空间,这个页的类型为Insert Buffer Bitmap.
每个Insert Buffer Bitmap页用来追踪16384个非聚簇索引页,也就是256个区。关于非聚簇索引页的信息如下:
名称 | 大小(bit) | 说明 |
---|---|---|
IBUF_BITMAP_FREE | 2 | 表示该辅助索引页中的可用空间数量: 0: 表示无可用剩余空间 1: 表示剩余空间大于1/32页(512字节) 2: 表示剩余空间大于1/16页 3: 表示剩余空间大于1/8页 |
IBUF_BITMAP_BUFFERED | 1 | 1表示该非聚簇索引页有记录被缓存在Insert Buffer B+树中 |
IBUF_BITMAP_IBUF | 1 | 1表示该页为Insert Buffer B+树的索引页 |
合并插入缓存的时机
- 当非聚簇索引页被读取到缓冲池中时
插入缓冲中的数据还没有持久化到非聚簇索引中,但是此时的查询请求需要用到这部分信息,此时先检查Insert Buffer Bitmap,如果发现IBUF_BITMAP_BUFFERED为1,则执行插入缓冲合并,保持索引页数据的一致性。
- Insert Buffer Bitmap页追踪到该非聚簇索引页无可用空间时
如果当前索引页的可用空间小于1/32页,强制执行插入缓冲合并,把非聚簇索引页读到缓冲,然后合并后插入。
- Master Thread
Master Thread每10秒或者每秒进行的操作。
Insert Buffer采用随机选择页的方式,让插入缓冲更具备公平性。值得注意的是,如果合并插入缓冲时发现该该表已经被删除,那么丢弃这次合并的操作。