2.1 InnoDB存储引擎概述
- 支持ACID事务,特点是行锁设计
- 支持MVCC
- 支持外键
- 提供一次性非锁定读
2.2 InnoDB存储引擎的版本
2.3 InnoDB体系结构
后台线程总览
- 主要作用是负责刷新内存池中的数据,保证缓冲池中的数据是最近的数据
- 将已修改的数据文件刷新到磁盘文件
- 保证异常发生时InnoDB能恢复到正常状态
内存池总览
- 维护所有进程/线程需要访问的多个内部数据结构
- 缓存磁盘上的数据,方便快速的读取,同时在对磁盘文件的数据修改之前在这里缓存
- 重做日志(redo log)缓冲
2.3.1 后台线程(建议先看2.3.2)
后台线程由Master Thread
,I/O Thread
, Purge Thread
, Page Cleaner Thread
组成,详细如下
2.3.1.1 Master Thread
主要负责将缓冲池中的数据异步刷新到磁盘,保持数据的一致性,包括脏页的刷新、合并插入缓冲、UNDO页的回收等。
Master Thread总体上分两类操作,分别是每秒进行的,以及每10秒进行的。
每秒进行的操作:
- 日志缓冲刷新到磁盘,即使这个事务还没有提交
- 合并插入缓冲(可能发生)当前1秒内发生IO次数小于5则合并
- 当
buf_get_modified_ratio_pct
>innodb_max_dirty_pages
时,刷新脏页到磁盘(可能发生,至多100个脏页) - 如果当前没有用户活动,则切换到background loop
每10秒进行的操作:
- 刷新100个脏页到磁盘(可能)
- 合并之多5个插入缓冲(总是)
- 将日志缓冲刷新到磁盘
- 删除无用的undo页
- 刷新100个或者10个脏页到磁盘
Master Thread具体分为loop, background loop,flush loop, suspend loop四个循环
2.3.1.1.1 InnoDB 1.0.x版本之前 Master Thread的伪代码
|
|
2.3.1.1.2 InnoDB 1.2.x版本之前
- 解决硬编码问题
- 改用百分比,引进
innodb_io_capacity
表示IO吞吐量,默认值为200 - 合并插入缓冲时,合并插入缓冲的数量为
innodb_io_capacity
值的5% - 从缓冲区刷新脏页时,刷新脏页的数量为
innodb_io_capacity
- 从1.0.x版本开始
innodb_max_dirty_pages_pct
默认值变为了75,加快刷新脏页的速度,同时适当保证IO的负载 - 添加参数
innodb_max_adaptive_flushing
(自适应的刷新),之前的策略是通过简单的比大小来进行刷新脏页,现在通过buf_flush_get_desired_flush_rate
的函数来判断需要刷新脏页最适合的数量 - 1.0.x版本引进
innodb_purge_batch_size
来控制每次full merge回收的undo页的数量,默认20,可以动态修改
伪代码变为:
|
|
2.3.1.1.3 InnoDB 1.2.x版本
对刷新脏页的操作从Master Thread线程分离到一个单独的page cleaner thread中
2.3.1.2 I/O Thread
InnoDB使用了大量AIO(async IO)来处理 写 IO请求。IO Thread的工作主要是负责这些IO请求的回调处理。I/O Thread主要分为4种:
- write, 默认有4个,可以通过innodb_write_io_threads进行更改
- read, 默认有4个,可以通过innodb_read_io_threads进行更改
- insert buffer
- log IO thread
2.3.1.3 Purge Thread
事务被提交后,其所使用的undo log可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页
在配置文件中可以添加如下命令来启用独立的PurgeThread:
|
|
2.3.1.4 Page Cleaner Thread
负责脏页的刷新工作
2.3.2 内存
内存块由三大部分组成,分别是缓冲池,重做日志缓冲,额外的内存池三部分组成
2.3.2.1 缓冲池
缓冲池用来解决磁盘读取速度与CPU速度之间的巨大鸿沟
- 读操作:从磁盘读取数据页到缓冲区(称为fix),读取时先看是否命中缓存,再考虑是否读磁盘中的页
- 写操作:首先修改缓存中的页,然后以一定的频率刷新到磁盘上。一定的频率指的是checkpoint技术(在下文进行讨论)
可以通过修改参数innodb_buffer_pool_size
来设置缓冲池大小,可以通过修改参数innodb_buffer_size
来设置缓冲池的个数
缓冲池存储的数据页类型有:
- 索引页
- 数据页
- undo页
- 插入缓冲(insert buffer)
- 自适应哈希索引(adaptive hash index)
- 锁信息(lock info)
- 数据字典信息(data dictionary)
2.3.2.1.1 管理缓冲池的相关技术:
LRU List
:
- LRU列表用来管理已经读取的页
- InnoDB使用了一种改进的LRU算法,新的页不是放在最前面,而是放在为midpoint处,midpoint为从后往前数3/8处
- 使用midpoint的原因(即改进LRU的原因):常见的索引或数据的扫描操作会连续读取大量的页,甚至是全表扫描,这就导致缓冲区被全部替换,导致一些latest recent use也被替换了(即导致重读磁盘)。若将前面部分保留,将新的数据插到中后位置则可以避免这种情况。靠后的页仍旧被淘汰。可以通过参数
innodb_old_blocks_pct
修改midpoint,默认为37,表示为37%,即3/8 - midpoint将内存块分为两部分,前面的为new表,后面的为old表。innodb_old_blocks_time用于表示当读到old表中的数据时,需要等待多久时间将这些old表中的数据提到LRU表头部(即new表头部)。默认为0,即不等待。从old表提到new表中的这种操作叫做
page made young
。old表中没有被提到new表中的则为page not not made young
- 压缩页。从InnoDB 1.0.x版本开始支持压缩页功能,将原本的16kB的页压缩成1KB、2KB、4KB、8KB。对于非16KB的页,使用
unzip_LRU
列表对不同大小的页进行分类管理。使用伙伴算法进行内存分配,例如分配一个4KB大小的页面,进行如下步骤:1)检查unzip_LRU中是否有4KB的空闲页 2)若有则用,否则从8KB分裂为2个4KB,否则从16KB分裂为1个8KB、2个4KB
Free List
:
- 数据库刚启动时LRU表是空的,没有任何页,这时页都放在Free列表中。
- 当需要从缓冲池中分页时,从Free表中查看是否有空闲页,若有就将其删除,放到LRU表中(即维护一个空白内存池?)
Flush List
:
- LRU列表中被修改的页被称为脏页(即内存与磁盘中的数据不一致),Flush List便是用来存储脏页的
- 脏页既存在LRU列表中,也存在Flush列表中
- LRU用来管理页的可用性,Flush用来管理将页刷新回磁盘,两者不影响
2.3.2.2 重做日志缓冲
重做日志信息先被放到缓冲区,然后按照一定频率刷新到重做日志文件中
参数innodb_log_buffer_size管理重做日志缓冲区的大小,默认为8MB
8MB一般够用,因为:
- Master Thread每一秒将重做日志缓冲刷新到重做日志文件中
- 每个事务提交时会将重做日志缓冲刷新到重做日志文件
- 当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件
2.3.2.3 额外的内存池
-
一些数据结构本身的内存分配时,需要从额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请
-
申请了很大的InnoDB缓冲池也应该相应的增加额外内存池的大小
2.4 checkpoint技术
checkpoint技术的目的是解决这几个问题:
- 缩短数据库恢复的时间(checkpoint之前的数据已经刷新到磁盘,故可以大幅度缩减时间)
- 缓冲池不够用时,将脏页刷新到磁盘(缓冲池不够用时,LRU将末尾的页抛弃,若它们为脏页,则强制checkpoint写回磁盘)
- 重做日志不可用时,刷新脏页。重做日志被设计为循环使用(因为空间有限)。不可用,指的是仍被占用,不可以重新循环使用。部分重做日志可以重用,是因为它们的对应的数据已经写入磁盘。若想要使用那些仍被占用的页,则需要强制checkpoint
具体的两种checkpoint技术sharp checkpoint
和fuzzy checkpoint
2.4.1 sharp checkpoint
- 在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数
innodb_fast_shutdown=1
- 若时时刻刻都sharp checkpoint将严重影响mysql性能,故有了fuzzy checkpoint
2.4.2 四种fuzzy checkpoint
Master Thread Checkpoint
:
- 以一定频率从缓冲池的脏页列表中刷新一定比例的页回磁盘
FLUSH_LRU_LIST checkpoint
:
- InnoDB需要保证差不多有100个空闲页,当不足时将LRU表的末尾页强制checkpoint(当他们时脏页时),然后丢弃
- 从InnoDB1.2.x开始,用一个Page Cleaner线程进行以上操作,使用参数innodb_lru_scan_depth控制LRU列表中可用页的数量,默认为1024
Async/Sync Flush Checkpoint
- 发生在重做日志不可用时,这时候需要将一些页刷新回磁盘,此时脏页是从脏页列表中选取。
- 具体的执行步骤定义如下:
定义: LSN(log sequence number),8byte长,一种记号方法。
记已经写入到重做日志的页的LSN为redo_lsn
记已经刷新回磁盘的页的LSN为checkpoint_lsn, 有如下定义:
checkpoint_age = redo_lsn - checkpoint_lsn
async_water_mark = 0.75 * total_redo_log_file_size
sync_water_mark = 0.9 * total_redo_log_file_size
则:
- 当checkpont_age < async_water_mark时,不需要刷新任何脏页到磁盘
- 当async_water_mark < checkpoint_age < sync_water_mark时,触发async flush, 从Flush列表中刷新足够多的脏页回磁盘,使得刷新后满足checkpoint_age < async_water_mark
- checkpont_age > sync_water_mark这种情况,一般出现很少(除非设置的重做文件太小,并进行类似LOAD DATA的BULK INSERT操作),触发sync flush,同样从Flush列表中刷新脏页回磁盘,使得刷新后满足checkpoint_age < async_water_mark
Dirty Page too much checkpoint
:
- 脏页太多触发checkpoint
- 可由参数
innodb_max_dirty_pages_pct
进行控制
2.5 InnoDB 关键特性
2.5.1 Innsert Buffer和change buffer(待续)
- 缓冲区中有insert buffer的信息, 但是insert buffer和数据页一样,也是物理页的一个组成部分
- 对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先到缓冲池中寻找,若不在,则放入到一个insert buffer中,然后再以一定频率进行insert buffer和辅助索引页子节点的merger操作
- insert buffer使用的条件:(1)索引是辅助索引(2)索引不是唯一(unique)的
- 若数据库发生宕机,大量insert buffer中的数据没有合并到实际的非聚集索引中,这时恢复时间可能需要很长时间
IBUF_POOL_SIZE_PER_MAX_SIZE
代表insert buffer占缓冲池内存的大小
change buffer
:
- 从1.0.x版本引进,可视为insert buffer 的升级版
- 使用参数innodb_change_buffering来开启各种buffer选项,选项内容为:inserts,deletes,purges,changes,all,none,默认是all
- 从1.2.x版本开始,可以通过参数
innodb_change_buffer_max_size
来控制change buffer最大使用内存的数量, 默认值为25,表示占用1/4的缓冲池空间
insert buffer
的内部实现(待续)
2.5.2 两次写
两次写提升数据页的可靠性,解决部分写失效的问题。
部分写问题的定义:
- 写到一半发生宕机
- 用重做日志无法解决这个问题,因为重做日志中记录的是对页的物理操作,而这时可能页已经损坏了。
double write的大致描述:
应用(apply)重做日志之前,用户需要一个页的副本,当写入失效时,先通过页的副本还原该页,再进行重做
double write具体实现:
- double write由两部分组成,一部分是内存中的double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB
- 对缓冲池中的脏页进行刷新时,用memcpy函数将脏页复制到double write buffer中,再从double write buffer中分两次,每次1MB顺序的写入到共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题
- 错误恢复:如果写磁盘的过程中发生了崩溃,在恢复的过程中,可以从共享表空间中的doublewrite中找到该页的一个副本,将其复制到表空间文件,然后再应用重做日志
可使用show global status like 'innodb_dblwr%';
查询double write的运行情况, 生产环境中可以通过查看Innodb_dblwr_pages_written
来统计写入的量
2.5.3 自适应哈希索引
- InnoDB存储引擎会监控对表上各索引页的查询,若观察到建立哈希索引能带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index, AHI)
- AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构造哈希索引
- InnoDB会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引
- AHI开启的要求: 1)对这个页的访问模式必须一致 2)以该模式访问了100次 3)页通过该模式访问了N次,N=页的记录/16
2.5.4 异步IO
- 异步,如字面意思,避免阻塞带来的低效率
- AIO的另一个优势是可以执行IO merge操作,如用户需要访问(space, page_no)这样格式的三个页:(8,6),(8,7),(8,8),则可以合并为读取一次。
- 通过
innodb_use_native_aio
用来控制是否启用native AIO
2.5.5 刷新邻接页
- 刷新脏页时,可以检测其他脏页中是否有这个脏页的相邻页,若有就一起刷新(即多个IO合成一个)
- 提供参数
innodb_flush_neighbors
来控制是否启用这个特性 - 推荐机械硬盘开启这个特性,而像固态硬盘那样有着超高IOPS性能的磁盘,建议关闭。
2.6 启动关闭与恢复
关闭时,参数innodb_fast_shutdown
影响InnoDB的行为,可取值0,1,2;默认1:
- 值为0时, InnoDB将完成所有的full purge和merge insert buffer,并将所有的脏页刷新回磁盘。这可能需要很长时间来完成。升级InnoDB时必须选择此项
- 值为1时, 不需要完成full purge和merge insert buffer,但是缓冲池中的一些数据脏页还是会刷新回磁盘
- 值为2时, 不完成full purge,merge insert buffer, 数据脏页也不处理,而是将日志写入日志文件,保证不丢失事务,下次启动时进行恢复
参数innodb_force_recovery
影响整个InnoDB存储引擎的恢复状况,默认为0:
- 值为1时(SRV_FORCE_IGNORE_CORRUPT)忽略检查到的corrupt页
- 值为2时(SRV_FORCE_NO_BACKGROUND)阻止Master Thread线程的运行,如Master Thread线程需要进行full purge操作,而这会导致crash
- 值为3时(SRV_FORCE_NO_TRX_UNDO)不进行事务回滚
- 值为4时(SRV_FORCE_NO_IBUF_MERGE)不进行插入缓冲的合并
- 值为5时(SRV_FORCE_NO_UNDO_LOG_SCAN)不查看undo log,将未提交的事务视为已提交
- 值为6时(SRV_FORCE_NO_LOG_REDO)不进行前滚的操作
当设置的参数值大于0后,用户可以进行select,create和drop操作,其他DML操作如insert,update,delete是不允许的