在InnoDB存储引擎中,主键是唯一的标识符。应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚簇索引(Primary key)一般是顺序的,不需要磁盘随机读写。比如定义一下SQL表:
create Table t(
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a)
)
但是不可能每张表上只有一个聚簇索引,更多情况下,一张表上有多个聚簇索引,如下表所示。
create Table t(
a INT AUTO_INCREMENT,
b VARCHAR(30),
PRIMARY KEY(a),
KEY(b)
)
当插入一条数据时,主键a还是顺序存放的,但是对于非聚簇索引叶子节点b的插入其实是随机插入的。这时就需要离散的访问非聚集索引页,由于随机读取的存在而导致了插入性能的下降。当然这不是这个b字段上索引的错误,而是因为B+树的特性决定了非聚簇索引插入的离散性。 InnoDB存储引擎设计了Insert Buffer,对于非聚簇索引的插入或更新操作,不是每一次都插入到索引页中,而是先判断插入的非聚簇索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中。然后再以一定的频率和情况进行Insert Buffer和辅助索引叶子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),大大提高了对于非聚簇索引插入的性能。
然后Insert Buffer的使用需要同时满足以下两个条件:
如果说Insert Buffer带给InnoDB存储引擎的是性能上的提升,那么doublewrite (两次写)带给InnoDB存储引擎的是数据页的可靠性。当发生数据库宕机时,可能InnoDB存储引擎正在写人某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效( partial page write)。在InnoDB存储引擎未使用doublewrite技术前,曾经出现过因为部分写失效而导致数据丢失的情况。 有经验的DBA也许会想,如果发生写失效,可以通过重做日志进行恢复。这是一个办法。但是必须清楚地认识到,重做日志中记录的是对页的物理操作,如偏移量800,写aaaa’ 记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的。这就是说,在应用(apply) 重做日志前,用户需要一个页的副本,当写人失效发生时,先通过页的副本来还原该页,再进行重做,这就是doublewrite。在InnoDB存储引擎中doublewrite的体系架构如图下图所示。
通俗的说: MySQL默认页的大小时16K,而磁盘的最小单位页是4K,那么如果一个脏页需要写会磁盘,则需要写4个磁盘页,如果写了2个磁盘页后系统宕机,这种情况被称为部分写失效(partial page write)。 数据修改不丢失是由undo.log和磁盘上的数据页共同保证的,如果磁盘上的数据页发生了损坏,那数据修改就会发生丢失。 如果使用了double write机制,那么怎么保证数据不丢失呢? 上图中 page表示脏页(内存) double write buffer(内存) double write (磁盘) 数据文件(磁盘) 如果①时刻宕机,那么Page脏页在内存中会丢失,但是redo.log和磁盘上修改前的数据文件是完整的,则Page脏页可以通过以上恢复过来。 如果②时间宕机,即Page copy 到double buffer中宕机,则同上所示,在重新恢复呗。 如果③时刻宕机,则double buffer里的数据丢失,但是没关系,double buffer其实已经通过步骤②持久化了,所以可以doublewrite中的数据 recovery到 数据文件中。
再通俗的说: 要么保证redo.log和磁盘上修改前的数据页的安全性,要么保证脏页的安全性。如果脏页持久化到doublewrite中,那么我不用管磁盘上修改前的数据页是什么,我直接用脏页覆盖掉就好了。 通过redo.log恢复的前提是磁盘上修改前的数据页是没有问题的,如果出现部分写失效( partial page write),那么磁盘上的数据页就是错误的。
哈希是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),即一般仅需要一次查询就能定位数据。而B+树的查找次数,取决于B+树的高度,B+树的高度一般为3~4层,故需要3到4次的查询。 InnoDn存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI)。InnoDB存储引擎会自动根据访问的频率和模式来自动地位某些热点页建立哈希索引。
为了提高磁盘操作性能,当前的数据库系统都采用异步I0(AsynchronousI0, AIO)的方式来处理磁盘操作。InnoDB存储引擎亦是如此。
InnoDB存储引擎还提供了Flush Neighbor Page (刷新邻接页)的特性。其工作原理为:当刷新一个脏页时,InnoDB 存储引擎会检测该页所在区(extent) 的所有页,如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个I0写人操。作合并为一个I0操作,故该工作机制在传统机械磁盘下有着显著的优势。但是需要考虑到下面两个问题: 是不是可能将不怎么脏的页进行了写人,而该页之后又会很快变成脏页? 固态硬盘有着较高的IOPS,是否还需要这个特性? 为此,InnoDB 存储引擎从1.2.x版本开始提供了参数innodb_ fush_ neighbors, 用来控制是否启用该特性。对于传统机械硬盘建议启用该特性,而对于固态硬盘有着超高IOPS性能的磁盘,则建议将该参数设置为0,即关闭此特性。
参考书:《MySQL技术内幕:InnoDB存储引擎》 参考博客:https://blog.csdn.net/xx123698/article/details/107201808