InnoDB存储引擎的关键特性包括:
- 插入缓冲(Insert Buffer)
- 两次写(Double Write)
- 自适应哈希索引(Adaptive Hash Index)
- 异步IO(Async IO)
- 刷新邻接页(Flush Neighbor Page)
Insert Buffer
由于InnoDB存储引擎中,主键是行唯一的标识符,所以,通常行记录的插入顺序是按照主键的递增顺序进行插入的(聚集索引)。所以,对于这种情况下的插入操作,不需要随机读取其他页的记录,所以顺序是很快的。
注意并不是所有主键插入都是顺序的,需要满足递增(比如根据时间戳递增或者顺序递增)才可以,对于无序的UUID那么其插入也是随机的。
而对于非聚集索引,其插入顺序是按照主键的索引顺序插入的,这就导致了非聚集索引的插入不是顺序的,需要离散的访问其他页,从而导致插入性能的下降。InnoDB是采用Insert Buffer来解决非聚集索引的插入或者更新操作的。
Insert Buffer与数据页一样,也是物理页的一个组成部分。对于一个非聚集索引的插入或者更新操作,会先判断对应的非聚集索引页是否在缓冲池当中,如果在则直接插入,否则会先放入到一个Insert Buffer中,然后再以一定的频率做merge操作,这时候通常能将多个插入合并到一个操作中(因为在一个索引页上),这样就提高了非聚集索引插入的性能。
对于Insert Buffer需要同时满足下面两个条件:
- 索引是非聚集索引
- 索引不是唯一的
非唯一的原因是:数据库不会去查找索引页来判断插入记录的唯一性,否则可能会有离散的读取发生。
目前Insert Buffer存在几个问题:
- 发生宕机后的恢复时间问题。
- 写密集情况下,Insert Buffer占用内存问题。
在InnoDB从 1.x开始引入了Change Buffer,可以将其视为Insert Buffer的升级,从这个版本开始,存储引擎可以对于DML操作进行缓冲(Insert/Delete/Update),他们分别是Insert Buffer、Delete Buffer、Purge Buffer。
同Insert Buffer一致,Change Buffer适用的对象依然是非唯一的非聚集索引。
内部实现
Insert Buffer内部实现是B+树,存放在共享表空间中,默认也就是ibdata1中。因此,试图通过独立表空间ibd文件恢复表数据的时候,往往会导致CHECK TABLE失败,这是因为非聚集索引还在Insert Buffer中,也就是共享表空间里,所以通过ibd文件恢复后,还需要进行REPAIRE TABLE操作来重建非聚集索引。
B+树非叶节点存放的是search key: space+marker+offset。
其中space记录的待插入表的表空间id(InnoDB中每个表都有一个唯一id),offset表示页所在的偏移量。
所以,当一个非聚集索引需要插入到指定页(space,offset)的时候,如果页不在缓冲池里,那么需要根据上述规则构造一个search key,然后将这个search key插入到B+树(Insert Buffer)的叶节点中。
发生Merge的时机:
- 非聚集索引页被读取到缓冲中;
- Insert Buffer 追踪到非聚集索引页剩余空间不多时;
- Master Thread 以一定频率合并。
Double Write
Double Write 带个InnoDB存储引擎的是数据页的可靠性。考虑下面的情况,当数据页(16KB)回刷的时候,只回刷了4KB的数据,就发生了宕机,即发生了部分写失效(Partial Page Write)。
或许发生部分写失效的时候,可以通过重做日志回放。但是,如果磁盘上这个页本身已经发生损坏,对其进行重做是没有意义的。所以,应该有一个页的副本,当发生部分写入失效的时候,先通过副本页去还原原来的页,然后再进行重做,这就叫Double Write。
Double Write由两部分组成:
- 内存中的double write buffer(2MB)
- 磁盘上的共享表空间中连续的128个页(2个区,2MB)
对于缓冲池里的脏页回刷的时候,会将脏页先复制到内存中的double write buffer上,然后通过double write buffer再分两次,每次1MB顺序的写入共享表空间的物理磁盘上,然后才通过fsync函数同步磁盘,避免缓冲写带来的问题。由于这个过程是顺序的,所以开销并不是很大,在完成double write页的写入后,再将double write buffer中的页写入到各个表空间中,此时写入则是随机的。
通过这种方式,如果发生宕机后,可以在共享表空间上的doublewrite区域找到该页的一个副本,将其复制到表空间文件,然后再做重做日志。
Adaptive Hash Index(AHI)
InnoDB存储引擎会监控对表上各索引页的查询,如果观察到建立哈希索引可以带来速度提升,则建立AHI。AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。
简而言之,InnoDB存储引擎会自动根据访问的频率和模式来自动的为某些热点页建立哈希索引。
AHI有一个要求,即对这个页的连续访问模式必须是一样的,例如对于索引index(a,b),那么这个索引,可以有两个访问模式:
- where a=“xxx”
- where a=“xxx” and b=“xxx”
所以,如果是交替进行上述两种where查询,是不会针对该页建立AHI的。AHI的“热点”要求如下:
- 以该模式访问了100次
- 以该模式访问了N次,N=页中记录*1/16
Async IO
用户在发出一条索引扫描的查询后,如果这条SQL查询语句需要扫描多个索引页,在扫描完一个页再进行下一次的扫描是没有必要的,是可以同步完成的。
InnoDB 1.1.x开始,提供了内核级别的AIO支持,即Native AIO。
Flush Neighbor Page
当刷新一个脏页时,会检测该页所在区的所有页,如果存在其他脏页会一起通过AIO刷新(AIO可以将多个IO写入操作合并为一个IO操作),故该工作机制在传统机械磁盘下有着显著的优势(固态硬盘不需要)。