第二章 InnoDB 存储引擎

  1. InnoDB 体系架构

 

  1.1 后台线程

  后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中缓存的是最近的数据。此外,还会将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行的状态。

  InnoDB存储引擎是多线程的模型,其后台有多个不同的后台线程,负责处理不同的任务

  1) Master thread

  负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页(由于磁盘的读写速度远赶不上内核的读写速度,系统把读写频繁的数据放在内存中,称为高速缓存。当进程修改了高速缓存中的数据时,该页被内核标为脏页)的刷新、合并插入缓冲、undo页的回收等。

  Master thread具有最高线程优先级。内部由多个循环(loop)组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。Master Thread会根据数据库运行状态在不同循环之间切换。其操作伪码如下:

void master_thread(){
    goto loop;
loop:
    for(int i = 0; i<10; i++){
    thread_sleep(1)  // sleep 1 second
    do log buffer flush to disk    // 日志缓冲刷新到磁盘
    if (last_one_second_ios < 5% innodb_io_capacity(磁盘吞吐量))   // 当前一秒发生的IO次数
        do merge 5% innodb_io_capacity insert buffer              // 合并插入缓冲
    if (buf_get_modified_ratio_pct (缓冲池中脏页比例) > innodb_max_dirty_pages_pct)  
        do buffer pool flush 100% innodb_io_capacity dirty page
    else if enable adaptive flush
        do buffer pool flush desired amount dirty page
    if ( no user activity )
        goto background loop
    }
    
    if (last_ten_second_ios < innodb_io_capacity)
        do buffer pool flush 100% innodb_io_capacity dirty page
    do merge 5% innodb_io_capacity insert buffer
    do log buffer flush to disk
    do full purge                                                 // 删除无用的undo页 对表进行update delete操作时,原先的行被标记为删除
                                                                  // 由于一致性读的关系需要保留这些行版本信息。但在full purge时,会判断
                                                                  // 这些标记为删除的行是否可以删除(有时,会有查询操作还需要读之前版本的
                                                                  // undo信息)
    if ( buf_get_modified_ratio_pct > 70% )
        do buffer pool flush 100% innodb_io_capacity dirty page
    else 
        do buffer pool flush 10% innodb_io_capacity dirty page
    goto loop
    
    background loop:
    do full purge
    do merge 100% innodb_io_capacity insert buffer
    if not idle:
    goto loop:
    else:
        goto flush loop
        
    flush loop:
    do buffer pool flush 100% innodb_io_capacity dirty page
    if ( buf_get_modified_ratio_pct>innodb_max_dirty_pages_pct)
        goto flush loop
    goto suspend loop
    
    suspend loop:
    suspend_thread()
    waiting event
    goto loop;
    }

  2) I/O thread

  InnoDB存储引擎中大量使用AIO(Async IO)来处理写IO请求,以提高数据库性能

  3) Purge thread

  事务提交后,所使用的undo log(用于事务回滚,将数据库恢复到修改前的样子,在第七章 事务会详细介绍)可能不在需要了,因此需要Purge Thread来回收这些undo 页

  4) Page Cleaner thread

  为了减轻Master Thread 的工作以及用户查询线程的阻塞,1,2.x版本将脏页的刷新操作放到单独的线程(Page Cleaner thread)来完成

  1.2 内存

  InnoDB内存数据对象

      

  1) 缓冲池

  InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理,即基于磁盘的数据库系统。

  通常将部分或全部的页备份到缓冲池(一块内存区域)中,在读取页时,先判断缓冲池中是否有需要的页,如果有,称为缓存命中;否则,从磁盘中读取相应的页。

  而对于数据库中页的修改,首先修改缓冲池中的页,然后再通过checkpoint技术(本章第二节介绍)将页刷新会磁盘中。

  2) 缓冲池内存管理

  LRU list

  数据库中的缓冲池通过LRU(least recently used)算法进行管理。最频繁使用的的页在LRU列表的前端,最少使用的页在尾端。当缓冲池不能存放新读到的页时,将首先释放LRU列表中尾端的页。InnoDB也对LRU算法做了改进,新读取到的页放到了LRU列表的midpoint位置(距列表尾端3/8的位置)。这是为了防止进行诸如索引或数据扫描等,需要访问表中很多页的操作,在插入到首部时,将原本的热点数据挤出LRU列表,这种操作访问的数据通常仅在本次查询中用到,并非热点数据。在下次查询时,InnoDB需要再次访问磁盘将热点数据拷贝回缓冲池。

  innodb_old_blocks_time用于表示页读取到mid位置后,需要多久才会被加入到LRU列表的热端。当页从LRU列表的old部分(midpoint之后的列表)加入到new部分时,称为page made young.

  Free list

  数据库刚启动时,LRU列表是空的。这时页都存在Free list中。当需要从缓冲池中分页时,首先从Free list查找是否有可用的空闲页,若有则将该页从free list中删除,并放入到LRU list中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新页。

  Flush list

  flush list 用来管理将脏页刷新回磁盘。

  1.3 重做日志缓冲

  存储引擎一般先将重做日志信息放到重做日志缓冲(redo log buffer),然后按一定频率刷新到重做日志文件:

  • Master thread 每一秒将重做日志缓冲刷新到重做日志文件
  • 每个事务提交时会将重做日志缓冲刷新到重做日志文件
  • 当重做日志缓冲池剩余空间小于1/2时,将重做日志缓冲刷新到重做日志文件

  1.4 额外的内存池

  缓冲池的帧缓冲还有对应的缓冲控制对象(记录了LRU、锁、等待等信息)存储在额外内存池中。在申请了很大的InnoDB缓冲池时,也应考虑相应增加额外内存池的大小

  2. Checkpoint 技术

  Checkpoint 技术解决以下几个问题:

  • 缩短数据库恢复时间
  • 缓冲池不够用时,将脏页刷新到磁盘
  • 重做日志不可用时,刷新脏页(事务数据库普遍采用Write Ahead Log策略,先写重做日志,再修改页,避免宕机造成的数据丢失)

  1) InnoDB存储引擎确保LRU list有默认1024个页可用,如果没有,根据LRU算法会溢出最近最少使用的页,若此页为脏页,会执行checkpoint,将脏页刷新回磁盘

  2) 重做日志不可用的情况出现是因为当前事务数据库系统对重做日志的设计都是循环使用的,而不是允许其无限增大。当重做日志不可用时需要强制将一些页刷新回磁盘

  3) Master thread 中发生的checkpoint,每秒或每十秒从缓冲池中的脏页列表刷新一定比例的页回磁盘

  4) 缓冲池中脏页占75%时,强制进行checkpoint 

  3. InnoDB关键特性

  3.1 插入缓冲 (insert buffer / change buffer(MySQL 5.5之后版本的叫法))

  该部分内容亦参考了博客 https://blog.csdn.net/qq_36652619/article/details/89460786    http://mysql.taobao.org/monthly/2015/07/01/

  插入聚集索引(clustered index)一般是顺序的,不需要磁盘的随机读取。二级索引(secondary index,亦称非聚集索引)通常不是唯一的,顺序相对随机,删除和更新可能会影响不在索引树中相邻的二级索引页。

  对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在则直接插入;若不在,则先放入到一个Insert buffer对象中,以此减少二级索引的随机IO,并达到操作合并的效果。Merge Insert/Change Buffer可能发生在以下几种情况:

  • 辅助索引页被读取到缓冲池中 (执行SELECT操作将页缓冲到缓冲池中,需要检查该辅助索引页是否有记录存放在Insert Buffer B+树中,有则一次性更新操作到辅助索引页中)
  • Insert Buffer Bitmap 页追踪到该辅助索引页已无可用空间(至少有1/32的可用空间)
  • Master Thread (1.1 中master thread介绍过,每秒或每十秒进行一次merge insert buffer) 

  change buffer是一颗B+树,ibuf btree通过三列(space id(每张表有唯一的ID), page no(表中页的偏移量), counter)作为主键来唯一决定一条记录,其中counter是一个递增值,目的是为了维持不同操作的有序性,例如可以通过counter来保证在merge时执行如下序列时的循序和用户操作顺序是一致的:INSERT x, DELETE-MARK x, INSERT x

  

  3.2 两次写

  如果说Insert Buffer带给InnoDB存储引擎性能上的提升,doublewrite带给InnoDB存储引擎的是数据页的可靠性。

  但数据库宕机时,可能InnoDB存储引擎正在写入某个页到磁盘中,比如,16KB的页,只写了前4页,之后就发生了宕机,这种情况称为部分写失效。

   

  由上图可知,doublewrite由两部分组成,一部分是内存中的doublewrite buffer,大小为2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即两个区,大小同样为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer分两次,每次1MB顺序写入共享表空间的物理磁盘上(doublewrite页是连续的,所以写开销并不是很大),在完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中。如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复的过程中,InnoDB存储引擎可以从共享表空间中的doublewrite中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。

  3.3 自适应哈希索引

  B+ 树中,哈希查找的次数取决于B+树的高度。InnoDB存储引擎会监控表上各索引页的查询,并根据以下两种情况建立自适应哈希索引(adaptive hash index):

  • 以某一模式连续访问了100次
  • 页通过该模式访问了N次,N=页中记录/16

  需要注意的是,哈希索引只能用来搜索等值查询,如 SELECT * FROM table WHERE index_col = XXX,不能用于范围查询。

  3.4 异步IO

  在同步I/O情况下,查询线程将I/O请求放入队列,innodb后台线程会遍历请求队列,每次处理一个请求。并行处理的请求个数受到后台线程的数量控制(参数innodb_read_io_threads)。AIO情况下,查询线程直接将I/O请求分发给操作系统,从而避免的后台线程数量对并发数的控制。innodb后台线程只需要等待操作系统对IO请求的处理反馈信息。另一个优点是IO Merge,即将多个IO合并为一个IO。例如:用户访问的页(space,page_no)为:(8,6)、(8, 7)、(8,8),每个页大小为16KB,同步IO需要进行3次IO操作。而AIO会判断这三个页是连续的。因此发送一个IO请求,从(8,6)开始,读取48KB的页。

  Read ahead(预读),脏页的刷新(磁盘的写入操作)都是通过AIO完成的。

  3.5 刷新邻接页

  当刷新一个脏页时,InnoDB存储引擎会检测该页所在区的所有页,如果是脏页,那么通过AIO一起进行刷新。但也存在两个问题:

  • 有些刷新到磁盘的脏页,可能很快又变成脏页
  • 固态磁盘有着较高的IOPS(input/output operations per second),不太需要该特性(一般机械磁盘建议开启该特性, innodb_flush_neighbors)
posted @ 2021-08-04 19:40  慕仙白  阅读(98)  评论(0)    收藏  举报