InnoDB 存储引擎之关键特性

最近在学习《Mysql技术内幕:InnoDB 存储引擎》一书,将第二章关于 InndoDB 存储引擎的概述及相关特性总结如下:

简介

  • InnoDB 存储引擎支持事务,其特点是行锁设计,支持事务,支持非锁定读。
  • 5.5.8 版本开始,InnoDB 是 mysql 默认的存储引擎。
  • InnoDB 使用多版本并发控制(MVCC)来获得并发性。
  • InnoDB 实现了 SQL 的四种隔离级别,默认是 REPEATABLE。
  • InnoDB 使用 next-key-locking 策略来避免幻读现象产生。
  • InnoDB 还提供了插入缓存,二次写,自适应哈希索引,预读等高性能和高可用功能。
  • InnoDB 是第一个完全支持 ACID 事务的 mysql 存储引擎。

关键特性一览

以下是 InnoDB 存储引擎主要几个关键特性,后面会做一一详解:

  • 脏页:缓存中的页面被修改后的页面,我们称之为脏页。
  • 重做日志:用来记录用户对数据的修改过程,定时刷新到磁盘,用于断电重启时对缓存中的脏页进行恢复。
  • checkPoint:将脏页数据刷新到磁盘的技术。
  • LSN(Log Sequence Number): 每个缓存页/重做日志/checkPoint都有 LSN,用来记录顺序节点位置。
  • undo 页:记录未被提交的事务操作过程。
  • pull merge:主要用来删除无用的 undo 页。
  • Insert Buffer:用于非聚集索引页的插入和更新操作,提高插入性能。
  • 二次写:为了确保在宕机时刷新脏页不会出现问题,先将原始页做一份备份到磁盘,再写脏页,出现问题可以通过备份进行恢复。
  • 自适应哈希索引:为了提高辅助索引的查询效率,对于频繁查询的辅助索引会自动建立 hash 索引。
  • 异步IO:为了提高磁盘操作的性能, InnoDB 存储引擎采用异步 IO(AIO)的方式来处理磁盘操作。
  • 刷新邻接页:在刷新脏页时,检查该页所在区的所有页是否存在脏页,存在则一起刷新。
后台线程

InnoDB 是多线程模型,主要分为以下三个线程:

  • Master Thread
Master Thread 是最高的线程优先级别,其内部由多个循环组成:主循环/后台循环/刷新循环/暂停循环,会在这多个循环之间切换。
- 主循环(loop)主要以每一秒和每十秒的频率执行刷新日志缓存,合并插入缓存,刷新脏页缓存,删除无用 undo 页等操作
- 如果当前没有用户活动,则进入后台循环流程(backgroud loop),主要执行删除无用的 undo 页,合并插入缓存
- 如果没有什么事情可以做了,便进入了暂停循环(suspend loop),等待事件循环唤起
  • IO Thread
主要用于对于异步 IO 请求的处理结果的回调处理,包括 write/read/insert buffer/log 四种 IO 线程,其中 write/read 线程的个数可以通过参数 innodb_read_io_threads 和 innodb_write_io_threads 参数进行调节。
  • Purge Thread
回收已经使用并分配的 undo 页,从 Master Thread 线程中分离出来,提高 CPU 的利用率。innodb 1.2 版本以后,可以通过参数 innodb_purge_threads 来设置 Purge 线程的个数
  • Page Cleaner Thread
负责脏页的刷新,innodb 1.2 版本以后从 Master Thread 线程中分离出来,提高 CPU 的利用率,减轻 Master Thread 的压力。
缓存池:

为了提高数据库的性能,引入缓存池的概念,通过参数 innodb_buffer_pool_size 可以设置缓存池的大小,参数 innodb_buffer_pool_instances 可以设置缓存池的实例个数。缓存池主要用于存储以下内容:

  1. 索引页
  2. 数据页
  3. undo 页
  4. 插入缓存(insert buffer)
  5. 自适应哈希索引(adaptive hash index)
  6. 锁信息
  7. 数据字典信息

缓存分配管理:

  • LRU(Lastest Recent Used,最近最少使用)列表算法

LRU 列表只存放索引缓存和数据缓存,其它插入缓存,哈希索引等是不会被存在 LRU 列表中的

innoDB 中缓存池中页的大小默认是 16KB,使用 LRU 算法对缓存池进行管理,在普通 LRU 算法的基础上做了优化,加入 midpoint 位置,每次并不是直接插入到 LRU 列表头部,而是放在 midpoint 位置,默认情况下是在 列表的 5/8 处,可以通过参数 innodb_old_blocks_pct 设置 midpoint 的位置。midponit 之前的列表是 new 列表,存放的活跃的热点数据,midpoint 之后的列表是 old 列表。当该页面被再次访问时,便将改页面移动到列表头部。但是我们可以通过参数 innodb_old_blocks_time 设置只有超过一定时间的 old 列表中的数据才会被移动到热点数据。
  • Flush 列表
LRU 列表中的页面被修改后,称之为脏页,数据库会通过 CHECKPOINT 机制将脏页刷新到磁盘,而 Flush 便是脏页的存放列表。脏页既存储在 LRU 列表中,也存放在 Flush 列表中。
  • Free 列表
数据库刚启动的时候, LRU 列表和 Flush 列表都是空的,所有的页都存放在 Free 列表中,需要分配 LRU 页的时候,Free 列表不为空的情况下,从 Free 列表中获取,否则淘汰 LRU 尾部的列用于分配新的页。
重做日志缓存(redo log buffer)
  • 重做日志缓存不放在缓存池中,是另外一份单独的缓存
  • 重做日志缓存不需要特别大,因为 Master Thread 每秒都会将数据刷新到磁盘
  • 通过参数 innodb_log_buffer_size 来修改重做日志缓存的大小,默认为 8MB
  • 以下三种情况,重做日志缓存都会被刷新到磁盘:
- Master Thread 会每秒进行刷新
- 每个事务提交时会刷新
- 重做日志缓存空间小于 1/2 时会强制刷新
Checkpoint 技术

Checkpoint 技术主要是用于将缓存区的脏页刷新到磁盘,及时将缓存脏页数据刷新到磁盘,有以下好处:

  • 可以减少数据库的恢复时间
  • 可以删除没必要的重做日志
  • 解决缓存池不够用问题

以下四种情况下会执行刷新脏页到磁盘:

  • Master Thread CheckPoint
Master Thread 会以每一秒或者每十秒的速度将一定比例的脏页刷新到磁盘
  • FLUSH_LRU_LIST CheckPoint
至少要保证 LRU 列表中有 100 个空闲页可供使用,如果小于 100 个空闲页的时候,会将列表末端的页移除,如果有脏页则刷新
  • Async/Sync Flush CheckPoint
当重做日志不可用的情况下,会强制将一些脏页刷新到磁盘
  • Dirty Page too much CheckPoint
当脏页的数量太多,导致 innoDB 存储引擎强制 Checkponit,通过参数 innodb_max_dirty_pages_pct 控制,默认是 75%
插入缓存(Insert Buffer)
  • 聚集索引和非聚集索引
聚焦索引:
- 聚集索引按照主键递增顺序连续插入,是唯一的,一般是顺序存放,不需要磁盘的随机读取
- 如果聚集索引插入了指定值而非 NULL 值,可能导致插入并非连续的情况
- 非聚集索引是指不是唯一的索引,B+ 树的存放特性决定了其插入的离散型
  • Insert Buffer

Insert Buffer 用于非聚集索引的插入和更新操作。先判断插入的非聚集索引是否在缓存池中,如果在则直接插入,否则插入到 Insert Buffer 对象里。再以一定的频率进行 Insert Buffer 和辅助索引叶子节点的 merge 操作,将多次插入合并到一个操作中,提高对非聚集索引的插入性能。可以通过 innodb_change_buffer_max_size 参数设置 Insert Buffer 的最大内存使用量,默认是 1/4,最大可以设置为 1/2,如果 Insert Buffer 内存太大的情况下会影响其它缓存的内存使用。

使用 Insert Buffer 要满足以下两个条件:

- 索引是辅助索引
- 索引不唯一,如果索引唯一还要去查找索引页进行检查唯一性,就失去了 Insert Buffer 离散插入的性能

Insert Buffer 中的记录何时被插入到真正的辅助索引页(Merge Insert Buffer)中:

- 当辅助索引页被读取到缓存池时,检查 Insert Buffer Bitmap 页是否有该页对应的 Insert Buffer,有则合并。
- 当每个辅助索引页的可用空间小于 1/32 时,强制进行一次合并操作。
- Master Thread 线程会每秒或者每 10 秒进行一次 Merge Insert Buffer。

Insert Buffer 的内部实现:

- Insert Buffer 存放在一颗 B+ 树中,非叶子节点存放的是search key(表空间,页的偏移量等信息),叶子节点存放的真正的记录
- Insert Buffer Bitmap 用来标记每个辅助索引页的可用空间以及该页是否已经在缓存中等信息,这样可以保证保证每次 Merge Insert Buffer 成功
二次写

如果数据库发生宕机时,可以通过重做日志对该页进行恢复,但是如果该页本身已经损坏了,进行重做恢复是没有意义的。因此引入了"二次写"方案,提高数据页的稳定性。

doublewrite 的整个流程如下:

  • 对缓存池的脏页进行刷新时,不直接写入磁盘,而是通过 memcpy 函数将脏页复制到内存中的 doublewrite buffer。
  • 将内存中的 doublewrite buffer 写入共享表空间的物理磁盘上(备份)。
  • 将 doublewrite buffer 中的数据真正的刷新到表磁盘中。
  • 如果写 doublewrite buffer 失败,那么这些数据不会写到磁盘,innodb 会载入磁盘原始数据和 redo 日志比较,并重新刷到 doublewrite buffer。
  • 如果写 doublewrite buffer 成功,但是刷新到磁盘失败,那么 innodb 就不会通过redo日志来恢复了,而是直接刷新 double write buffer 中的数据到磁盘。
  • skip_innodb_double_written 参数可以用来禁止 doublewrite(二次写) 功能。
自适应哈希索引(Adaptive Hash Index,AHI)

InnoDB 会监控对表上各个索引页的查询,如果观察到通过哈希索引可以带来性能提升,则自动建立哈希索引。AHI 通过缓存池的 B+ 树页构造而来,因此建立速度很快。

  • AHI 可以提高读写性能
  • AHI 只能用来搜索等值查询
  • 页的连续访问模式必须是一样的,才能建立 AHI。
  • 自适应哈希索引只是针对辅助索引会自适应建立
对于(a, b)这样的联合索引,访问模式有以下两种:
1. where a = xxx
2. where a = xxx and b = xxx
异步 IO

为了提高磁盘操作的性能, InnoDB 存储引擎采用异步 IO(AIO)的方式来处理磁盘操作:

  • 可以减少请求查询时间
  • 可以对多个 IO 进行合并操作,提供 IOPS 的性能
  • 在 InnoDB1.1.x 之前是通过代码模拟实现 AIO 的,在之后的版本中提供了内核级别的 AIO 支持(Native AIO)
  • 可以通过 innodb_use_native_aio 参数控制是否开启 Native AIO
刷新邻接页
  • 刷新邻接页功能是指在刷新脏页时,检查该页所在区的所有页是否存在脏页,存在则一起刷新,这样可以通过 AIO 进行脏页合并刷新
  • 对于传统的机械硬盘建议开启该功能,对于固态硬盘有着较高的 IOPS,可以不用开启
  • 通过 innodb_flush_neighbors 参数控制是否开启刷新邻接页功能
InnoDB 的启动/关闭/恢复
  • innodb_fast_shutdown 参数设置数据库关闭时的 InnoDB 的行为,可取 0,1,2三个值
- 0 表示完成所有的 pull merge 和 merge insert buffer,并将脏页刷新到磁盘操作以后再关闭
- 1 表示只完成将脏页刷新到磁盘即关闭
- 2 只是将日志写入文件就关闭数据库,下次启动时可以根据 redo log 进行恢复
  • innobb_force_recovery 参数控制数据启动时,InnoDB 恢复和还原的行为,可取值是 1-6, 其中默认值为0,每次启动时都会对发生问题的表进行恢复操作

本文涉及到的相关参数

  • innodb_read_io_threads:设置 read 线程个数
  • innodb_write_io_threads:设置 write 线程个数
  • innodb_purge_threads:设置 purge 线程个数
  • innodb_buffer_pool_size:设置缓存池的大小
  • innodb_buffer_pool_instances:设置缓存池的实例个数
  • innodb_log_buffer_size:设置重做日志缓存的大小
  • innodb_old_blocks_pct:LRU 算法的 midpoint 位置
  • innodb_old_blocks_time:在 midpoint 之后的页面需要多久才会被加入到 LRU 列表的热端
  • innodb_max_dirty_pages_pct: 缓存池中脏页占据比例为多少时,进行强制 Checkpoint
  • innodb_change_buffer_max_size:设置 Insert Buffer 和 Change Buffer 的最大内存使用量,默认是 1/4,最大可以设置为 1/2
  • skip_innodb_double_written:用来禁止 doublewrite(二次写)功能
  • innodb_adaptive_hash:是否开启自适应哈希索引
  • innodb_use_native_aio:是否开启 Native AIO 功能
  • innodb_flush_neighbors:控制是否开启刷新邻接页功能
  • innodb_fast_shutdown:设置数据库关闭时的 InnoDB 的行为,可取 0,1,2三个值
  • innobb_force_recovery:数据启动时,InnoDB 恢复和还原的行为,可取值是 1-6

你可能感兴趣的:(数据库)