二、InnoDB存储引擎

InnoDB是事务安全的MySQL存储引擎,通常是OLTP应用中核心表的首选存储引擎。同时,也正是因为InnoDB的存在,才使MySQL数据库变得更有魅力。
从MySQL 5.5版本开始是默认的表存储引擎(之前的版本InnoDB存储引擎仅在Windows下为默认的存储引擎)。该存储引擎是第一个完整支持ACID事务的MySQL存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效地利用以及使用内存和CPU。
InnoDB存储引擎同MySQL数据库一样,在GNU GPL 2下发行。目前应用最广泛的存储引擎之一,几乎所有大型互联网公司都有使用。

2.1 InnoDB体系架构

InnoDB的简单体系架构图:




mysql 内核 调度 参数_缓冲池


InnoDB存储引擎有多个内存块,可看成一个大的内存池,负责如下工作:
□ 维护所有进程/线程需要访问的多个内部数据结构。
□ 缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存。
□ 重做日志(redo log)缓冲。
……
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态。

2.1.1 后台线程

InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。1.Master线程
Master线程是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收等。后面会详细介绍。http://2.IO线程
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要是负责这些IO请求的回调(call back)处理。
主要有4个IO线程:write、read、insert buffer和log IO线程。
命令show engine innodb status可以查看IO线程的详细情况,其中0号是insert buffer IO线程,1号是log线程,其他分别是读写线程。3.Purge线程
事务被提交后,其所使用的undolog可能不再需要,因此需要Purge线程来回收已经使用并分配的undo页。在1.1版本之前,purge操作Master线程中完成,从1.1版本开始,purge操作可以独立到单独的线程中进行。启用独立Purge线程的配置:


[mysqld]


命令SHOW VARIABLES LIKE 'innodb_purge_threads'可以查看purge线程的数量。

4.Page Cleaner线程

Page Cleaner线程从1.2.x版本开始引入。其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。而其目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

2.1.2 内存

1.缓冲池
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。由于CPU速度与磁盘速度之间的鸿沟,使用缓冲池技术来提高数据库的整体性能。
对于页的读取操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为Checkpoint的机制刷新回磁盘。同样,这也是为了提高数据库的整体性能。
缓冲池的大小直接影响着数据库的整体性能。为了让数据库使用更多的内存,强烈建议采用64位的操作系统。
命令SHOW VARIABLES LIKE 'innodb_buffer_pool_size'查看缓冲池大小,同样通过innodb_buffer_pool_size来设置大小。
缓冲池中缓存的数据页类型如下图:


mysql 内核 调度 参数_存储引擎_02


从1.0.x版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。可以通过参数innodb_buffer_pool_instances来进行配置,该值默认为1。

2.LRU列表、Free列表和Flush列表

数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理的。缓冲池中页的大小默认为16KB。
InnoDB对传统的LRU算法做了一些优化,在LRU列表中引入了midpoint位置。新读取到的页,并不直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置。midpoint之后的列表称为old列表,之前的列表称为new列表,new列表中的页都可以理解是热点数据。默认的midpoint位置是37%(约3/8)。
这样优化的理由是,像索引或数据的扫描这类操作,需要访问表中的许多页,甚至是全部的页,而这些页通常并不是热点数据。如果这些页放入LRU的首部,很有可能把真正的活跃页给替换掉,从而影响缓冲池的效率。
InnoDB还引入了另一个参数(innodb_old_blocks_time),用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。
当数据库刚启动时,LRU列表是空的,这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时,称此时发生的操作为page made young,而因为innodb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。
缓冲池的命中率(Buffer poolhit rate)需要重点关注,通常该值不应该小于95%,若小于95%这种情况,需要观察是否是由于全表扫描引起的LRU列表被污染的问题。
InnoDB从1.0.x版本开始支持压缩页的功能,即将原本16KB的页压缩为1KB、2KB、4KB和8KB。对于非16KB的页,是通过unzip_LRU列表进行管理的,而且对不同压缩页大小的页分别进行管理,通过伙伴算法进行内存的分配。
在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。脏页同时存在于LRU列表和Flush列表。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。
LRU/unzip_LRU列表、Free列表、Flush列表的使用情况和运行状态,都可以通过命令SHOW ENGINE INNODB STATUS查看。3.重做日志缓冲
InnoDB首先将重做日志信息先放入到这个缓冲区,然后按一定频率刷新到重做日志文件。因为每秒会将重做日志缓冲刷新到日志文件,因此重做日志缓冲不需要设置很大。该值可由配置参数innodb_log_buffer_size控制,默认为8MB。
重做日志缓冲刷新到重做日志文件的三种情况:
□ Master线程定时(每秒)刷新;
□ 事务提交时;
□ 重做日志缓冲区剩余空间小于1/2时。4.额外的内存池
在InnoDB中,对内存的管理是通过内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffer control block),这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请了很大的InnoDB缓冲池时,也应考虑相应地增加这个值。

2.2 Checkpoint技术

如果每次页有发生变化,都刷新到磁盘,那么这个开销会非常大,数据库的性能也变得非常差。但如果在发生宕机时,缓冲区还有修改的页没有刷新到磁盘,那么就会丢数据。为了避免数据丢失,数据库普遍都采用了Write Ahead Log策略,即当事务提交时,先写重做日志,再修改页。当因宕机而导致数据丢失时,可通过重做日志来完成数据的恢复。这也是事务ACID中D(Durability持久性)的要求。
因为缓冲池大小有限,且内存成本大,不可能存下数据库所有数据。而重做日志理论上可以存下所有数据,但恢复数据的时间会相当长。由此引入了Checkpoint技术来解决如下问题:
□ 缩短数据库的恢复时间。因为Checkpoint之前的页都已经刷新回磁盘。故数据库只需对Checkpoint后的重做日志进行恢复。
□ 缓冲池不够用时,将脏页刷新到磁盘。LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页刷回磁盘。
□ 重做日志不可用时,刷新脏页。若此时重做日志还需要使用,那么必须强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。
InnoDB通过LSN(Log Sequence Number)标记版本。LSN是8字节的数字,其单位是字节。每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN。
Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘。InnoDB有两种Checkpoint:
□ Sharp Checkpoint,发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1。
□ Fuzzy Checkpoint,在数据库运行时,只刷新一部分脏页到磁盘。如果数据库运行时使用Sharp Checkpoint会影响数据库的可用性。
如下几种情况的Fuzzy Checkpoint:
□ Master Thread Checkpoint,差不多以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘,这个过程是异步的。
□ FLUSH_LRU_LIST Checkpoint,InnoDB需要保证LRU列表中有约100个空闲页可供使用,否则会将LRU列表尾端的页移除。如果这些页中有脏页,那么需要进行Checkpoint。
□ Async/Sync Flush Checkpoint,是指重做日志文件不可用的情况(这部分较为复杂,下面单独介绍)。
□ Dirty Page too much Checkpoint,脏页的数量太多,会强制进行Checkpoint,也是为了保证缓冲池中有足够可用的页。可由参数innodb_max_dirty_pages_pct控制。Async/Sync Flush Checkpoint
重做日志文件不可用时,需要强制将一些页刷新回磁盘。此时脏页是从脏页列表中选取的。下面详细说明如何选取脏页。
重做日志的LSN记为redo_lsn,已经刷新回磁盘最新页的LSN记为checkpoint_lsn,则可定义:


checkpoint_age = redo_lsn - checkpoint_lsn
# 再定义以下的变量:
async_water_mark = 75% * total_redo_log_file_size
sync_water_mark = 90% * total_redo_log_file_size


若重做日志文件的大小为1GB,并且定义了两个重做日志文件,则重做日志文件的总大小为2GB。则:
□ 当checkpoint_age < async_water_mark时,不需要刷新回磁盘;
□当async_water_mark < checkpoint_age < sync_water_mark时触发AsyncFlush,刷新足够的脏页回磁盘,使满足checkpoint_age < async_water_mark;
□ checkpoint_age > sync_water_mark情况很少发生,除非设置的重做日志文件太小,并且在进行类似LOAD DATA的BULK INSERT操作。此时触发Sync Flush操作,刷新足够的脏页回磁盘,使满足checkpoint_age<async_water_mark。
1.2.x版本之前,Async Flush Checkpoint会阻塞发现问题的用户查询线程,而Sync Flush Checkpoint会阻塞所有的用户查询线程。从InnoDB 1.2.x版本开始,这些刷新操作放到了Page Cleaner线程中,不会阻塞用户查询线程。

2.3 Master线程的工作方式

InnoDB存储引擎的主要工作都是在Master线程中完成的。

2.3.1 版本1.0.x之前的Master线程

Master线程具有最高的线程优先级别。其内部由多个循环(loop)组成:

  • 主循环(loop)
  • 后台循环(backgroup loop)
  • 刷新循环(flush loop)
  • 暂停循环(suspend loop)

Master线程会根据数据库运行的状态在这几个loop之间进行切换。Loop被称为主循环,因为大多数的操作是在这个循环中,其中有两大部分的操作:每秒的操作和每10秒的操作。这里的每秒或每10称不精确,负载大时会有延迟的情况。
主循环每秒的操作包括:
□ 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)。这就是为什么再大的事务提交(commit)的时间也是很短的。
□ 合并插入缓冲(可能)。一秒内发生的IO次数小于5次,可以执行合并插入缓冲的操作。
□ 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)。就是Sync Flush Checkpoint操作,以及相对应的触发条件下执行。
□ 如果当前没有用户活动,则切换到background loop(可能)。
主循环每10秒的操作包括:
□ 刷新100个脏页到磁盘(可能),过去10秒之内磁盘的IO操作小于200次时执行;
□ 合并至多5个插入缓冲(总是);
□ 将日志缓冲刷新到磁盘(总是);
□ 删除无用的undo页(总是),即执行full purge操作,每次最多尝试回收20个undo页;
□ 刷新100个或者10个脏页到磁盘(总是)。如果脏页超过70%,则刷新100个脏页到磁盘,如果脏页小于70%,则只刷新10%的脏页到磁盘。
如果当前没有用户活动(数据库空闲时)或者数据库关闭(shutdown),就会切换到background循环,执行以下操作:
□ 删除无用的Undo页(总是);
□ 合并20个插入缓冲(总是);
□ 跳回到主循环(总是);
□ 不断刷新100个页直到符合条件(可能,跳转到flush循环中完成)。
若flush循环也没什么事情可做,会切换到suspend循环,将Master线程挂起,等待事件的发生。

2.3.2 版本1.2.x之前的Master

版本1.0.x之前的Master线程实现,对IO有限制,在缓冲池向磁盘刷新时都做了一定的硬编码(hard coding)。当固态磁盘(SSD)出现时,这很大程度上限制了InnoDB对磁盘IO的性能,尤其是写入性能。
无论什么情况,InnoDB最大只会刷新100个脏页到磁盘,合并20个插入缓冲。对于写密集的应用,很有可能超过这些值,Master线程可能一直处于忙碌状态。即使处理得过来,当发生宕机需要恢复时,由于很多数据没有刷新回磁盘,恢复时间也可能比较长,尤其是insert buffer。
因此从1.0.x版本开始提供了参数innodb_io_capacity,用来表示磁盘IO的吞吐量,默认值为200。对于刷新到磁盘页的数量,会按照innodb_io_capacity的百分比来进行控制。规则如下:
□ 在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity值的5%;
□ 在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity。
当使用SSD或RAID等拥有更高IO速度时,可以根据磁盘IO的吞吐量来调高innodb_io_capacity的值。
另外,参数innodb_max_dirty_pages_pct默认值的问题,从默认值为90改成了75。这个值如果太大,刷新脏页速度太慢,太小会增加系统负担。默认75既可以加快刷新脏页的频率,又能保证了磁盘IO的负载。
这个版本还引入了其他参数:

  • innodb_adaptive_flushing(自适应地刷新),该值影响每秒刷新脏页的数量。
  • innodb_purge_batch_size,控制每次full purge回收的undo页的数量,默认值为20。

很多测试都显示,1.0.x版本在性能方面有了极大的提高,其实和前面提到的改动密不可分。

2.3.3 版本1.2.x的Master线程

版本1.2.x中再次对Master线程进行了优化。其中srv_master_do_idle_tasks()就是之前版本中每10秒的操作,srv_master_do_active_tasks()就是之前每秒中的操作。同时对于刷新脏页的操作,从Master线程分离到一个单独的Page Cleaner线程,从而减轻了Master线程的工作,同时进一步提高了系统的并发性。

2.4 InnoDB关键特性

InnoDB的关键特性包括:
□ 插入缓冲(Insert Buffer)
□ 双写(Double Write)
□ 自适应哈希索引(Adaptive Hash Index)
□ 异步IO(Async IO)
□ 刷新邻接页(Flush Neighbor Page)
这些特性为InnoDB存储引擎带来更好的性能以及更高的可靠性。

2.4.1 插入缓冲

1.Insert Buffer
Insert Buffer可能是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能。但这个名字可能会让人认为是缓冲池中的组成部分。其实不是,Insert Buffer和数据页一样,也是物理页的一个组成部分。
在InnoDB中,主键是行唯一标识。通常行记录的插入顺序就是主键递增的顺序。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取,插入速度非常快。
像主键类似UUID之类的,以及非聚集索引叶子节点的插入不再是顺序,此时插入性能会下降。这是由B+树的特性决定的,非聚集索引插入本身就是离散的。如下面表中的b:
CREATE TABLE t( a INT AUTO_INCREMENT, b VARCHAR(30), PRIMARY KEY(a), KEY(b) );
在某些情况下,辅助索引(secondary index)的插入依然是顺序的,或者说比较顺序,如时间字段。
InnoDB存储引擎开创性地设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。
Insert Buffer的使用需要同时满足以下两个条件:
□ 索引是辅助索引(secondary index);
□ 索引不是唯一(unique)的。
需要考虑一种情况,大量的插入操作都涉及了不唯一的非聚集索引,也就是使用了InsertBuffer。若此时发生了宕机,势必有大量的InsertBuffer并没有合并到实际的非聚集索引中去。这时恢复可能需要很长的时间,在极端情况下甚至需要几个小时。
辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致Insert Buffer失去了意义。
目前Insert Buffer存在一个问题是:在写密集的情况下,插入缓冲会占用过多的缓冲池内存,默认最大可以占用到1/2的缓冲池内存。这对于其他的操作可能会带来影响。2.Change Buffer
从1.0.x版本开始引入了Change Buffer,可视为InsertBuffer的升级。从这个版本开始,可以对DML操作——INSERT、DELETE、UPDATE都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge Buffer。Change Buffer适用的对象依然是非唯一的辅助索引。
UPDATE操作可能分为两个过程:
□ 将记录标记为已删除。缓冲在Delete Buffer中;
□ 真正将记录删除。缓冲在Purge Buffer中。
参数innodb_change_buffering,用来开启各种Buffer的选项。
参数innodb_change_buffer_max_size,用来控制Change Buffer最大使用内存的数量,默认为25,最大有效值为50。3. Insert Buffer的内部实现
Insert Buffer的数据结构是一棵全局的B+树。存放在共享表空间中,默认也就是ibdata1中。
非叶节点存放的是查询的search key(键值),其构造如下:


mysql 内核 调度 参数_mysql 内核 调度 参数_03


search key共占用9个字节,其中space表示表空间id,每个表有一个唯一的space id,占用4字节。marker占用1字节,它是用来兼容老版本的Insert Buffer。offset表示页所在的偏移量,占用4字节。
对于插入到Insert Buffer B+树叶子节点的记录,并不是直接将待插入的记录插入,而是需要根据如下的规则进行构造:


mysql 内核 调度 参数_存储引擎_04


metadata占用4字节,内容如下:


mysql 内核 调度 参数_存储引擎_05


IBUF_REC_OFFSET_COUNT是用来排序每个记录进入Insert Buffer的顺序,通过这个顺序回放(replay)才能得到记录的正确值。
metadata之后就是实际插入记录的各个字段了。因此较之原插入记录,Insert Buffer B+树的叶子节点记录需要额外13字节的开销。
为了保证Merge InsertBuffer页必须成功,还需要一个特殊的页(Insert BufferBitmap)来标记每个辅助索引页(space,page_no)的可用空间。
每个Insert Buffer Bitmap页用来追踪16384个辅助索引页,也就是256个区(Extent)。每个辅助索引页在Insert Buffer Bitmap页中占用4位(bit),存储的信息如下:


mysql 内核 调度 参数_存储引擎_06


4. Merge Insert Buffer
Insert Buffer中的记录何时合并(merge)到真正的辅助索引中呢?概括地说,Merge Insert Buffer的操作可能发生在以下几种情况下:
□ 辅助索引页被读取到缓冲池时;
□ Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时;
□ Master Thread。
第一种情况,需要根据Insert Buffer Bitmap页确认该辅助索引页是否在Insert Buffer中有记录。若有,则将InsertBuffer中该页的记录插入到该辅助索引页中。该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,性能会有大幅提高。
第二种情况,若插入辅助索引记录时检测到插入记录后可用空间会小于1/32页,则会强制进行一个合并操作。
第三种情况,Master线程每秒或每10秒会进行一次Merge Insert Buffer的操作,不同之处在于每次进行merge操作的页的数量不同。

2.4.2 双写

Doublewrite(双写)带给InnoDB存储引擎的是数据页的可靠性。
当发生数据库宕机时,可能某个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效(partial page write)。在未使用doublewrite技术前,部分写失效会导致数据丢失。
大家先想到的就是通过重做日志进行恢复,但因为重做日志中记录的是对页的物理操作,发生部分写失效时,如果这个页本身损坏,再对其进行重做是没有意义的。
在应用(apply)重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是doublewrite。在InnoDB中doublewrite的体系架构如下图:


mysql 内核 调度 参数_缓冲池_07


doublewrite由两部分组成:

  • 内存中的doublewrite buffer,大小为2MB;
  • 物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB。

在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。在这个过程中,因为doublewrite页是连续的,顺序写的,开销并不大。在完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中(为什么不是写Insert Buffer?),此时的写入则是离散的。
可通过SHOW GLOBAL STATUS LIKE 'innodb_dblwr'查看doublewrite的运行情况。如果发现系统在高峰时的Innodb_dblwr_pages_written : Innodb_dblwr_writes远小于64∶1,说明系统写入压力并不高。
参数skip_innodb_doublewrite可以禁止使用doublewrite功能。主服务不建议启用这个参数,从服务可以启用来提供较高的性能。
有些文件系统提供了部分写失效的防范机制,如ZFS文件系统,就不要启用doublewrite了。

2.4.3 自适应哈希索引

哈希(hash)的查找非常快,时间复杂度为O(1)。而B+树的查找复杂度取决于B+树的高度,O(log n),在生产环境中,B+树的高度一般为3~4层,故需要3~4次的查询。
InnoDB会监控对表上各索引页的查询。如果观察到建立哈希索引提升速度,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI)。AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。InnoDB会自动根据访问的频率和模式来自动地为热点页建立哈希索引。
AHI如下的要求:
□ 对这个页的连续访问模式必须是一样的,即where条件一样;
□ 以该模式访问了100次;
□ 页通过该模式访问了N次,其中N=页中记录*1/16。
官方文档显示,启用AHI后,读取和写入速度可以提高2倍,辅助索引的连接操作性能可以提高5倍。AHI是非常好的优化模式,其设计思想是数据库自优化的(self-tuning)。
哈希索引只能用来搜索等值的查询,如WHERE index_col='xxx'。而对于如范围查找,不能使用哈希索引。
命令SHOW ENGINE INNODB STATUS可以查看AHI的使用状况。
参数innodb_adaptive_hash_index用来禁用或启动此特性,默认AHI为开启状态。

2.4.4 异步IO

为了提高磁盘操作性能,数据库系统都采用异步IO(Asynchronous IO,AIO)的方式来处理磁盘操作,InnoDB也是。
异步IO就是,在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成。
AIO的另一个优势是可以进行IO Merge操作,多个IO合并为1个IO,可以提高IOPS的性能。
在1.1.x之前,AIO的实现是通过代码来模拟实现。而从1.1.x开始(InnoDB Plugin不支持),提供了内核级别AIO的支持,称为Native AIO。Native AIO需要操作系统提供支持。Windows系统和Linux系统都提供Native AIO支持,而Mac OSX系统则未提供。
参数innodb_use_native_aio用来控制是否启用Native AIO,在Linux下,默认值为ON。
官方的测试显示,启用Native AIO,恢复速度可以提高75%。

2.4.5 刷新邻接页

InnoDB还提供了Flush Neighbor Page(刷新邻接页)的特性。原理是:当刷新一个脏页时,会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。
这样做的好处显而易见。对传统机械磁盘有显著的优势。但是需要考虑到下面两个问题:
□ 是不是可能将不怎么脏的页进行了写入,而该页之后又会很快变成脏页?
□ 固态硬盘有着较高的IOPS,是否还需要这个特性?
为此,从1.2.x版本开始提供了参数innodb_flush_neighbors,用来控制是否启用该特性。对于传统机械硬盘建议启用该特性,而对于固态硬盘有着超高IOPS性能的磁盘,则建议关闭。