innodb 的引擎四大特性为:插入缓冲,二次写,自适应哈希和预读。
插入缓冲(Insert Buffer/Change Buffer):提升插入性能,change buffering 是 insert buffer 的加强,insert buffer 只针对 insert 有效,change buffering 对 insert、delete、update(delete+insert)、purge 都有效。
只对于非聚集索引(非唯一)的插入和更新有效,对于每一次的插入不是写到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在则直接插入;若不在,则先放到 Insert Buffer 中,再按照一定的频率进行合并操作,再写回 disk。这样通常能将多个插入合并到一个操作中,目的还是为了减少随机IO带来性能损耗。
使用插入缓冲的条件:
Change buffer 是作为 buffer pool 中的一部分存在。Innodb_change_buffering 参数缓存所对应的操作:(update 会被认为是 delete+insert)。
innodb_change_buffering,设置的值有:inserts、deletes、purges、changes(inserts和deletes)、all(默认)、none。
参数 | 描述 |
---|---|
all | 默认值,缓存 insert, delete, purges 操作 |
none | 不缓存 |
inserts | 缓存 insert 操作 |
deletes | 缓存 delete 操作 |
changes | 缓存 insert 和 delete 操作 |
purges | 缓存后台执行的物理删除操作 |
可以通过参数控制其使用的大小:innodb_change_buffer_max_size,默认是 25%,即缓冲池的 1/4。最大可设置为 50%。 当 MySQL 实例中有大量的修改操作时,要考虑增大 innodb_change_buffer_max_size。
上面提过在一定频率下进行合并,那所谓的频率是什么条件?
Doublewrite 缓存是位于系统表空间的存储区域,用来缓存 InnoDB 的数据页从 innodb buffer pool 中 flush 之后并写入到数据文件之前,所以当操作系统或者数据库进程在数据页写磁盘的过程中崩溃,Innodb 可以在 doublewrite 缓存中找到数据页的备份而用来执行 crash 恢复。数据页写入到 doublewrite 缓存的动作所需要的 IO 消耗要小于写入到数据文件的消耗,因为此写入操作会以一次大的连续块的方式写入。
在应用(apply)重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是 double write。doublewrite 组成:
内存中的 doublewrite buffer,大小 2M。
物理磁盘上共享表空间中连续的 128 个页,即 2 个区(extend),大小同样为 2M。
对缓冲池的脏页进行刷新时,不是直接写磁盘,而是会通过 memcpy() 函数将脏页先复制到内存中的 doublewrite buffer,之后通过 doublewrite 再分两次,每次 1M 顺序地写入共享表空间的物理磁盘上,在这个过程中,因为 doublewrite 页是连续的,因此这个过程是顺序写的,开销并不是很大。在完成 doublewrite 页的写入后,再将 doublewrite buffer 中的页写入各个 表空间文件中,此时的写入则是离散的。如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,innodb 可以从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。
Adaptive Hash index 属性使得 InnoDB 更像是内存数据库。该属性通过 innodb_adapitve_hash_index 开启,也可以通过 —skip-innodb_adaptive_hash_index
参数关闭。
Innodb 存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,建立哈希索引可以带来速度的提升。
经常访问的二级索引数据会自动被生成到 hash 索引里面去(最近连续被访问三次的数据),自适应哈希索引通过缓冲池的 B+ 树构造而来,因此建立的速度很快。
哈希(hash)是一种非常快的等值查找方法,在一般情况下这种查找的时间复杂度为 O(1),即一般仅需要一次查找就能定位数据。而 B+ 树的查找次数,取决于 B+ 树的高度,在生产环境中,B+ 树的高度一般 3-4 层,故需要 3-4 次的查询。
innodb 会监控对表上个索引页的查询。如果观察到建立哈希索引可以带来速度提升,则自动建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI)。AHI 有一个要求,就是对这个页的连续访问模式必须是一样的。例如对于(a,b)访问模式情况:
where a = xxx where a = xxx and b = xxx
特点:
- 无序,没有树高。
- 降低对二级索引树的频繁访问资源,索引树高 <=4,访问索引:访问树、根节点、叶子节点。
- 自适应。
缺陷:
- hash 自适应索引会占用innodb buffer pool;
- 自适应 hash 索引只适合搜索等值的查询,如
select * from table where index_col='xxx'
,而对于其他查找类型,如范围查找,是不能使用的;- 极端情况下,自适应 hash 索引才有比较大的意义,可以降低逻辑读。
预读(read ahead)
InnoDB 使用两种预读算法来提高 I/O 性能:线性预读(linear read-ahead)和随机预读(randomread-ahead)
为了区分这两种预读的方式,我们可以把线性预读放到以 extent 为单位,而随机预读放到以 extent 中的 page 为单位。线性预读着眼于将下一个 extent 提前读取到 buffer pool 中,而随机预读着眼于将当前 extent 中的剩余的 page 提前读取到 buffer pool 中。线性预读(linear read-ahead)
方式有一个很重要的变量控制是否将下一个 extent 预读到 buffer pool 中,通过使用配置参数 innodb_read_ahead_threshold,可以控制 Innodb 执行预读操作的时间。如果一个 extent 中的被顺序读取的 page 超过或者等于该参数变量时,Innodb 将会异步的将下一个 extent 读取到 buffer pool 中,innodb_read_ahead_threshold 可以设置为 0-64 的任何值,默认值为 56,值越高,访问模式检查越严格。
例如,如果将值设置为 48,则 InnoDB 只有在顺序访问当前 extent 中的 48 个 pages 时才触发线性预读请求,将下一个 extent 读到内存中。如果值为 8,InnoDB 触发异步预读,即使程序段中只有 8 页被顺序访问。你可以在 MySQL 配置文件中设置此参数的值,或者使用 SET GLOBAL 需要该 SUPER 权限的命令动态更改该参数。
在没有该变量之前,当访问到 extent 的最后一个 page 的时候,Innodb 会决定是否将下一个 extent 放入到 buffer pool 中。
随机预读(randomread-ahead)
随机预读方式则是表示当同一个 extent 中的一些 page 在 buffer pool 中发现时,Innodb 会将该 extent 中的剩余 page 一并读到 buffer pool 中,由于随机预读方式给 Innodb code 带来了一些不必要的复杂性,同时在性能也存在不稳定性,在 5.5 中已经将这种预读方式废弃。要启用此功能,请将配置变量设置 innodb_random_read_ahead 为 ON。