MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎

1、InnoDB存储引擎概述

从MySQL5.5.8版本开始是默认的表存储引擎,该存储引擎是第一个完整支持ACID事务的MySQL存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效地利用以及使用内存和CPU。

2、InnoDB体系架构

InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:
1.维护所有进程/线程需要访问的多个内部数据结构。
2.缓存磁盘上的数据,方便快速的读取,同事在对磁盘文件的数据修改之前在这里缓存。
3.重做日志(redo log)缓冲。
MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第1张图片

2.1 后台线程

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

(1)Master Thread
非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新,合并插入缓冲(insert buffer)、undo页的回收等。

(2)IO Thread
在InnoDB存储引擎中大量使用了 AIO(Async IO 异步的) 来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要负责这些IO请求的回调(call back)。

InnoDB 1.0版本之前共有4个IO Thread,分别是: write 、read 、 insert buffer 、log IO thread。

1.查看InnoDB的版本: show variables like ‘innodb_version’
2.查看MySQL的版本: select version()
3.查看read、write的线程数量:show variables like ‘innodb_%io_threads’
4.查看IO Thread情况:show engine innodb status (读的线程id总是小于写的线程)

(3)Purge Thread
事务被提交后,其所使用的undolog可能不再需要,因此需要 PurgeThread来回收已经使用并分配的undo页

InnoDB 1.1版本以前,这个操作由Master Thread来完成,1.1版本以后该操作可以独立到单独的线程中进行,可以提高性能。

通过在配置文件中添加该命令启动独立的Purge Thread:innodb_purge_threads=1
1.2版本以后,支持多个 PurgeThread 通过命令查看数量:show variables like ‘innodb_purg_threads’

(4)Page Cleaner Thread
1.2版本以后引入的,作用是将之前版本中脏页的刷新操作都放入单独的线程中完成,目的是为了减轻原Master Thread 的工作及对于用户查询线程的阻塞。

及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

2.2 内存

(1)缓冲池
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理,由于CPU和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。缓冲池就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。

数据库中进行读取页操作:
1、将从磁盘读取的页存放在缓冲池中(将页FIX在缓冲池中)
2、下次读同样的页时,判断该页是否在缓冲池中,如果在则称为被命中,否则,进行第1步

数据库中页的修改操作:
1、修改缓冲池中的页
2、以一定的频率刷新到磁盘上。通过Checkpoint的机制刷新回磁盘。

缓冲池中缓存的而数据页类型有:索引页,数据页,undo页,插入缓冲(insert buffer),自适应哈希索引(adaptive hash index),InnoDB存储的锁信息(lock info)、数据字典信息(data dicttionary)等。除了缓冲池(innodb_buffer_pool)还有重做日志缓冲(redo log_buffer)和额外内存池(innodb_addtional_mem_pool_size)。InnoDB存储引擎中内存结构情况如下:
MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第2张图片

缓冲池的大小通过来 innodb_buffer_pool_size 设置:show variables like ‘innodb_buffer_pool_size’;

从InnoDB 1.0以后,允许有多个缓冲池实例,每个页根据哈希值平均分配到不同缓冲池实例中。

可以通过innodb_buffer_pool_instances在配置文件中进行配置:show variables like ‘innodb_buffer_pool_instances’

MySQL5.6以后 ,可以通过information_schema 架构下的表 innodb_buffer_pool_stats 观察缓冲的状态use information_schema;
select pool_id,pool_size,free_buffers,database_pages from innodb_buffer_pool_stats;

(1)缓冲池中内存的管理——LRU list,Free list,Flush list
1、LRU list
通常来说,数据库中的缓冲池是通过LRU(Lastest Recent Used,最近最少使用)算法进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端,当缓冲池不能存放新读取的页时,将首先释放LRU列表中尾端的页。

在InnoDB存储引擎中,缓冲池中页的大小默认为16KB,最新访问的页并不是直接放入到LRU列表的首部,而是放入LRU列表的midpoint位置。这个算法在InnoDB存储引擎下称为 midpoint insertion strategy,默认midpoint是在LRU列表长度的5/8处,midpoint位置可由参数 innodb_old_blocks_pct 控制,默认值为37,表示新读取的页插入到LRU列表尾端的37%的位置(差不多3/8的位置)。在InnoDB存储引擎中,把midpoint之后的列表成为old列表,之前的列表成为new列表,可以简单的理解为new列表中的页都是最为活跃的热点数据。

mysql> show variables like ‘innodb_old_blocks_pct’;
+————————+——-+
| Variable_name | Value |
+————————+——-+
| innodb_old_blocks_pct | 37 |

这样做的目的:
防止出现偶尔使用的大数据量的读操作,把缓冲池中大量的甚至全部的页释放掉,由于这样的操作是偶尔进行的,所以读到的页并不是活跃的热点数据,如果放在LRU列表的开头可能会把真正的热点数据清除掉,使下一次热点数据的读取需要访问磁盘。

可以通过调小innodb_old_blocks_pct ,如: set global innodb_old_blocks_pct=20 减少热点页被刷出的概率。InnoDB存储引擎引入了另一个参数来进一步管理LRU列表:innodb_old_blocks_time。用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端:show variables like ‘innodb_old_blocks_time’。

2、Free list
LRU列表管理的是已经读取的页。当数据库刚启动时,LRU列表是空的,即没有任何的页,这时的页都存放在Free列表中。当需要从缓冲池中分页时:
1、从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表删除,放入到LRU列表中
2、否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页

当页从LRU列表的old部分加入到new部分,称为:page made young (显示了LRU列表中移动到前端的次数),因为innodb_old_blocks_time的设置,导致页没有从old部分移动到new部分的操作称为:page not made young。

buffer pool hit rate 缓冲池的命中率,小于95%时需要观察是否由于全表扫描引起的LRU列表被污染的问题。

InnoDB 1.2以后,可以通过information_schema 架构下的表 innodb_buffer_pool_stats 观察缓冲池的状态
运行。
use information_schema;
select pool_id,pool_size,free_buffers,database_pages from innodb_buffer_pool_stats;
还可以通过表innodb_buffer_page_lru 观察每个LRU列表中每个页的具体信息:
select * from innodb_buffer_page_lru limit 1

InnoDB 1.0以后,开始支持压缩页的功能,即将原本16KB的页压缩为1KB、2KB、4KB和8KB。对于非16KB的页,是通过unzip_LRU列表进行管理的。同样可以通过information_schema架构下的表innodb_buffer_page_lru来观察unzip_LRU列表中的页。

3、Flush list
LRU列表中的页被修改后,该页称为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时,数据库会通过checkpoint机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。注意:脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,FLush列表用来管理将页刷新回磁盘,二者互不影响。

因为脏页同样存在于LRU列表中,故用户可以通过源数据表 innodb_buffer_page_lru 来查,需要加入 oldest_modification 大于 0 的查询条件:
select * from innodb_buffer_page_lru where oldest_modification > 0
通过 show engine innodb status 可以查看缓冲池中这些列表的信息,但不是实时的。

(3)重做日志缓冲

InnoDB存储引擎内存区域除了有缓冲池外,还有重做日志缓冲———— redo log buffer。InnoDB存储引擎首先把重做日志信息放入这个缓冲区,然后按一定频率将其刷新到重做日志文件。一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。
show variables like ‘innodb_log_buffer_size’ \G 默认为8M
将重做日志缓冲刷新到外部磁盘的重做日志文件中:
(1) Master Thread 每一秒将重做日志缓冲刷新到重做日志文件;
(2)每个事务提交时会将重做日志缓冲刷新到重做日志文件;
(3)当重做日志缓冲池剩余空间小于1/2 时。

(4)额外的内存池
在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆(heap)的方式进行的。在对一些数据结构本身进行分配时,需要从额外的内存池中进行申请。当该区域的内存不够时,会从缓冲池中进行申请。例如:
分配了缓冲池(innodb_buffer_pool),但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象(buffer control block),这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外内存池中申请。因此,在申请了很大的InnoDB缓冲池时,也应考虑相应地增加这个值。

3.Checkpoint 技术

缓冲池的设计目的是为了协调CPU速度与磁盘速度的鸿沟,为了避免在缓冲池将新版本数据刷新到磁盘时发生宕机,从而使数据无法恢复,当前事务数据系统普遍采用 Write Ahead Log 策略,即当事务提交时,先写重做日志,再修改页。这样就可以通过重做日志来完成数据的恢复,达到事务ACID中D(Durability 持久性)的要求。如此来说要恢复数据需要两个前提条件:
1、缓冲池可以缓存数据库中所有的数据;
2、重做日志可以无限增大。

即便是上述两个条件都满足,那么还有一个情况:宕机后数据库的恢复时间。 (如果是运行了几个月甚至几年的数据库,这是发生宕机,重新应用重做日志的时间会非常久)。
因此Checkpoint(检查点)技术的目的是解决以下几个问题:
1、缩短数据库的恢复时间;
2、缓冲池不够用时,将脏页刷新到磁盘;
3、重做日志不可用时,刷新脏页。

因此,当数据库发生宕机,需要恢复数据时,只需要对Checkpoint后的重做日志进行恢复,因为Checkpoint之前的页都已经刷新回磁盘。此外,当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的最新版本刷回磁盘。

重做日志出现不可用的情况:
1、当前事务数据库系统对重做日志的设计都是循环使用的;
2、重做日志可以被重用的部分是指数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用;
3、若此时重做日志还需要使用,那么必须强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。

对于InnoDB存储引擎,通过LSN(Log Sequence Number) 来标记版本。LSN是8字节的数字,其单位是字节,每个页有LSN,重做日志也有LSN,Checkpoint也有LSN。可以通过 show engine innodb status ; 查看Checkpoint发生的时间、条件及脏页的选择等都非常复杂,无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。

两种CheckPoint:
1.Sharp CheckPoint
2.Fuzzy CheckPoint

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第3张图片

4.Master Thread工作方式

Innodb 1.2.x之前:主要包括主loop、background loop、flush loop和suspend loop。其中的参数可以配置。

while(true){

    //差不多1s一次
    for(int i in 0..9){
        刷新日志缓存到磁盘
        //1s内的统计值
        if IO < 5
            合并插入缓存
        if 脏页比例 > 预定值
            刷新部分脏页(不超过100if  没有用户活动
            进入background loop{
                删除无用undo页
                合并20个插入缓冲
                可能跳到flush loop{
                    可能跳到suspend loop
                }
                跳回主loop
            }
        sleep 1s;
    }
    //差不多10s一次
    if IO < 200 //10s内
    刷新100个脏页到磁盘
    合并最多5个插入缓冲
    刷新日志缓冲
    删除无用undo
    刷新10010个脏页
}

Innodb 1.2.x:Master Thread中的脏页刷新功能完全由Page Cleaner Thread执行。

if innodb is idle
    执行每10s一次的操作
else
    执行每秒执行的操作

5.Innodb关键特性

5.1插入缓冲

对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入,若不在,则先放入到一个Insert Buffer对象中。数据库这个非聚集的索引已经插到叶子结点,而实际没有。只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。
使用Insert Buffer需要同时满足条件:
1.索引是辅助索引
2.索引不是唯一的

change buffer
change buffer 适用的对象依然是非唯一的辅助索引。对一条记录进行update操作可能分为两个过程:
1.将记录标记为已删除(delete buffer)
2.真正将记录删除(purge buffer)

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第4张图片

Insert Buffer 内部实现
Insert Buffer 是一棵B+ 树,由叶节点和非叶节点组成。非叶节点存放的是查询的键值。
MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第5张图片

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第6张图片

merge insert buffer
merge insert buffer 的操作可能发生在以下几种情况下:
1.辅助索引页被读取到缓冲池中
2.insert buffer bitmap 页追踪到该辅助索引页已无可用空间时
3.master thread
MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第7张图片

5.2两次写

在对脏页刷新到磁盘时,如果某一页还没写完就宕机,此时该页数据已经混乱无法通过redo实现恢复。innodb提供了doublewrite机制。

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第8张图片

5.3自适应哈希索引

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第9张图片
AHI有一个要求,即对这个页的连续访问模式必须是一样的。

5.4 异步IO

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第10张图片
AIO的另一个优势是可以进行io merge 操作,也就是将多个io合并为1个io。

5.5刷新邻接页

MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第11张图片

6.启动、关闭与恢复

InnoDB是mysql数据库的存储引擎之一,因此InnoDB存储引擎的启动和关闭,更准确的是指在mysql实例的启动过程中对InnoDB存储引擎的处理过程。
MySQL技术内幕InnoDB存储引擎-02InnoDB存储引擎_第12张图片

你可能感兴趣的:(MySQL技术内幕,InnoDB存储引擎,第2版)