前言
- InnoDB 是事务安全的存储引擎,设计上采用类似于 Oracle 数据库的架构
- InnoDB 存储引擎是 OLTP 应用中核心表的首选存储引擎
InnoDB 存储引擎概述
- InnoDB 存储引擎是第一个完整支持 ACID 事务的 MySQL 存储引擎
- BOB 是第一个支持事务的 MySQL 存储引擎,现已停止开发
- 主要特点:行锁设计、支持 MVCC、支持外键、提供一致性非锁定读
- InnoDB 是一个 高性能、高可用、高可扩展的存储引擎
InnoDB 存储引擎的版本
- 从 MySQL 5.1 开始,可用支持两个版本的InnoDB
- 静态编译的 InnoDB 版本(老版本的 InnoDB
- 动态加载的 InnoDB 版本(InnoDB Plugin
- 各个版本中 InnoDB 存储引擎的功能对比
InnoDB 体系架构
- InnoDB 存储引擎体系架构
- InnoDB 存储引擎有多个内存块,这些内存块共同组成一个大的内存池
- 维护所有进程/线程需要访问的多个内部数据结构
- 缓存磁盘上的数据,同时在对磁盘文件的数据修改之前在这里缓存
- 重做日志(redo log)缓冲
- …
- 后台线程的主要作用
- 刷新内存池中的数据(保证内存缓存中的是最近的数据
- 将已修改的数据文件刷新到磁盘文件(保证发生异常时能恢复到正常运行状态
-
后台线程
- Master Thread
- 非常核心的后台线程
- 主要负责将缓冲池中的数据异步刷新到磁盘(保证数据一致性
- 脏页的刷新
- 合并插入缓冲
- UNDO 页的回收
- IO Thread
- InnoDB 大量使用了 AIO(Async IO)来处理写 IO 请求(提高数据库性能
- IO Thread 主要负责 这些 IO 请求的回调处理
- 在 INnoDB 1.0版本之前有工4个 IO Thread(write、read、insert buffer 和 log IO thread
- Purge Thread
- 用于 回收 已经使用并分配的 undo 页
- InnoDB 1.1 之前 purge 操作仅在 Master Thread 中完成,1.1版本以后可以独立到单独的线程
- 目的:减轻 Master Thread 的工作,提高 CPU 使用率以及提升存储引擎的性能
- Page Cleaner Thread
- InnoDB 1.2x 版本引入
- 作用:将之前版本中的脏页的刷新操作都放入单独的线程中完成
- 减轻 Master Thread 工作及对于用户查询线程的阻塞,提高性能
- Master Thread
-
内存
-
InnoDB 内存数据对象
-
缓冲池
- 由来:由于 CPU 速度与磁盘速度之间的鸿沟,通常使用缓冲池技术来提高数据库的整体性能
- 简单来说就是一块内存区域,通过内存的速度来弥补磁盘较慢对数据库的影响
- 操作
- 读:首先将从磁盘中读取到的页存放在缓冲池中(页“FIX”),再次读取时先从缓冲池中读取
- 写:首先修改缓冲池中的页,然后以一定频率刷新到磁盘上
- 并不是每一次页发生更新时都触发
- Checkpoint 机制
- 参数
innodb_buffer_pool_size
配置缓冲池大小
- InnoDB 1.0.x 版本开始,允许有多个缓冲池实例
- 每个页根据哈希值平均分配到不同缓冲池实例中
- 好处:减少数据库内部的资源竞争,增加并发处理能力
- 参数
innodb_buffer_pool_instances
配置缓冲池实例个数
- 由来:由于 CPU 速度与磁盘速度之间的鸿沟,通常使用缓冲池技术来提高数据库的整体性能
-
LRU List、Free List 和 Flush List
- 通常数据库中的缓冲池是通过 LRU 算法来进行管理
- LRU :Latest Recent Used(最近最少使用
- 频繁使用的页在列表前端,最少使用的页在尾端,内采不足时,首先释放尾端的页
- InnoDB 使用优化版的 LRU 算法:
midpoint insertion strategy
- LRU 列表中加入 midpoint 位置(新读取到的页放至在此处,而不是在列表首部
- 参数
innod_old_blocks_pct
配置 midpoint 位置(默认为 5/8 处 - 将 midpoint 之前的列表成为 new 列表,之后的列表称为 old 列表
- new 列表中的页都是最为活跃的热点数据
- 优势:避免索引或数据的扫描操作(大量)将缓冲池中的页过多的刷出,影响缓冲池效率
- 同时增加配置参数来控制 页 读取到 mid 位置后需要等待多久才会被加入到 LRU 列表的热端
- 参数:
innodb_old_blocks_time
page made young
:old 部分加入到 new 部分page not made young
:old 部分因为innodb_old_blocks_time
的设置未能加入到 new 部分
- 数据库刚启动时,LRU 列表时空的,页都存放在 Free 列表 中
- 当需要从缓冲池中分页时,首先会判断 Free 列表中是否存在有可用的空闲页
- 若存在,将该页从 Free 列表中删除,放入到 LRU 列表中
- 若不存在,利用 LRU 算法淘汰末端的页,并将该空间分配给新的页
Free buffers
与Datebase pages
的数量之和 不一定等于Buffer pool size
Datebase pages
:LRU 列表中页的数量- 原因:缓冲池的页还可能被分配给 自适应哈希索引、Lock信息、Insert Buffer 等(与 LRU 无关
Buffer pool hit rate
:缓冲池的命中率- 该值不应该小于 95%(若小于,可能是由于全表扫描引起的 LRU 列表被污染
SHOW ENGINE INNODB STATUS
:显示的不是当前的状态,而是过去某个时间范围内 InnoDB 的状态Per second calculated from the last 24 seconds
:24 秒内的状态
- 当需要从缓冲池中分页时,首先会判断 Free 列表中是否存在有可用的空闲页
- Flush 列表 中你那个的页即为脏页列表
- 脏页:在 LRU 列表中的页被修改后,即缓冲池中的页和磁盘上的页数据不一致
- 脏页既存在于 LRU 列表 也存在与 Flush 列表
- LRU 列表用来管理缓冲池中页的可用性
- Flush 列表用来管理将页刷新回磁盘
Modified db pages
:脏页的数量
- 通常数据库中的缓冲池是通过 LRU 算法来进行管理
-
重做日志缓冲(redo log buffer
- 流程
- InnoDB 首先将重做日志信息先放入到重做日志缓冲
- 然后以一定频率刷新到重做日志文件
- 参数
innodb_log_buffer_size
配置重做日志缓冲的大小- 默认为 8MB
- 以下三种情况下会刷新至重做日志文件中
- Master Thread 每一秒 刷新
- 每个事物提交时 刷新
- 当重做缓冲池剩余空间小于 1/2 时刷新
- 流程
-
额外的内存池
- InnoDB 对内存的管理是通过一种称为 内存堆 的方式进行的
- 对一些数据结构本身的内存进行分配时,需要从 额外的内存池 中进行申请
- 当该区域的内存不够时,会从缓冲池中进行申请
- 储存了帧缓冲还有对应的缓冲控制对象等
- LRU 、锁、等待 等信息
-
Checkpoint 技术
- 为了避免数据丢失的问题,当前事务数据库系统普遍都采用
Write Ahead Log
策略- 过程:当事务提交时,先写重做日志,再修改页
- 异常处理:当宕机时,通过重做日志来完成数据的恢复
- 这也是事务 ACID 中的 D(Durability 持久性)的要求
- Checkpoint 技术的目的及解决的问题
- 缩短数据库的恢复时间(宕机后只需要恢复 Checkpoint 后的重做日志
- 缓冲池不够用时,将脏页刷新到磁盘(若 LRU 算法溢出的页是脏页,则强制 Checkpoint
- 重做日志不可用时,刷新脏页
- Checkpoint 所做的事情无外乎是将 缓冲池 中的脏页刷新到磁盘,不同之处在于刷新的数量、地点、时间等
Sharp Checkpoint
:将所有的脏页都刷新回磁盘(数据库关闭时默认工作方式- 参数
innodb_fast_shutdown=1
- 数据库的可用性受到很大的影响
- 参数
Fuzzy Checkpoint
:只刷新一部分脏页- Master Thread Checkpoint
- 异步地 以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘
- FLUSH_LRU_LIST Checkpoint
- InnoDB 需要保证 LRU 列表中需要有差不多 100 个空闲页可供使用
- MySQL 5.6 以后,由 单独的
Page Clear
线程进行刷新- 参数
innodb_lru_scan_depth
配置 LRU 列表中可用页的数量(默认为 1024
- 参数
- Async/Sync Flush Checkpoint
- 重做日志文件不可用的情况,需要强制将一些页刷新回磁盘(脏页从脏页列表中选取
- 保证重做日志的循环使用的可用性
- MySQL 5.6 以后,由 单独的
Page Clear
线程进行刷新
- Dirty Page too much Checkpoint
- 脏页的数量太多,导致 InnoDB 存储引擎强制进行 Checkpoint
- 保证缓冲池中有足够可用的页(由参数
innodb_max_dirty_pages_pct
控制
- Master Thread Checkpoint
InnoDB 关键特性
- InnoDB 存储引擎的关键特性
- 插入缓冲(Insert Buffer)
- 两次写(Double Write)
- 自适应哈希索引(Adaptive Hash Index)
- 异步 IO(Async IO)
- 刷新邻接页(Flush Neighbor Page)
- 关键特性能为 InnoDB 带来更好的性能以及更高的可靠性
-
插入缓冲
- Insert Buffer
- 流程
- 对于非聚集索引的插入或者更新操作,首先判断插入的非聚集索引页是否存在
- 若存在,直接插入
- 若不存在,则先放入到一个 Insert Buffer 对象中
- 然后以一定频率和情况进行 Insert Buffer 和 辅助索引页子节点的合并操作
- 对于非聚集索引的插入或者更新操作,首先判断插入的非聚集索引页是否存在
- 通常能将多个插入合并到一个操作中(因为是在一个索引页中),提高了非聚集索引插入的性能
- Insert Buffer 使用需满足的条件
- 索引是辅助索引(secondary index
- 索引不是唯一(unique
- 缺点
- 大量插入操作时,若发生宕机,大量 Insert Buffer 未合并到实际的索引中,导致恢复时间过长
- 在写密集的情况下,插入缓冲会占用过多的缓冲池内存(默认最大能占用 1/2
- Change Buffer
- 1.0.x 版本开始引入 Change Buffer(Insert Buffer 的升级
- InnoDB 可以对 DML 操作都进行缓冲
- INSERT( Insert Buffer
- DELETE( Delete Buffer
- UPDATE( Purge Buffer
- Change Buffer 适用的对象依然是 非唯一的辅助索引
- 对一条记录进行 UPDATE 操作可能分为两个过程
- 将记录标记为已删除
- 真正将记录删除(Purge Buffer 对应这个过程
- 参数
innodb_change_buffering
配置开启各种 Buffer 的选项- 可选值:Inserts、deletes、purges、changes、all
- 默认值为 all
- 参数
innodb_change_buffer_max_size
配置 Change Buffer 最大使用内存的数量- 默认值为 25:最多使用 1/4 的缓冲池内存空间
- 最大有效值为 50
- 流程
- Insert Buffer
-
两次写
-
插入缓冲带来的是性能上的提升,两次写则带来的是数据页的可靠性
-
部分写失效:当发生数据库宕机时,InnoDB 存储引擎正在写入某个页到表中,但这个页只写了一部分
- 在未使用 doublewrite 技术前,可能出现因部分写失效导致数据丢失的情况
-
InnoDB 存储引擎 doublewrite 架构
-
流程
- 在对缓冲池的脏页刷新时,并不直接写磁盘,而是将脏页先复制到
doublewrite buffer
- 然后
doublewrite buffer
分两次(每次 1MB)顺序写入共享表空间的物理磁盘上- doublewrite 页是连续的,顺序写入,开销并不大
- 最后将
doublewrite buffer
中的页 写入各个表空间文件(离散写入
- 在对缓冲池的脏页刷新时,并不直接写磁盘,而是将脏页先复制到
-
崩溃恢复
- 首先从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件
- 重做日志中的记录是对页的物理操作,若这个页已经发生损坏,再对其进行重做就没意义了
- 应用重做日志
- 首先从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件
-
参数
skip_innodb_doublewrite
配置可以禁止使用 doublewrite 功能
-
-
自适应哈希索引
- InnoDB 存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引
- 自适应哈希索引 是通过缓冲池的 B+树页 构造而来,建立速度快,无需对整表构建哈希索引
- 自适应哈希索引 建立的要求
- 对这个页的连续访问模式必须是一样
- 例如:联合索引(a,b),交替访问 a = xx 或 a = xx and b = xxx 则不会构造 自适应哈希索引
- 以该模式访问了 100 次
- 页通过该模式访问了 N 次,其中 N = 页中记录 * 1/16
- 对这个页的连续访问模式必须是一样
- 只支持 等值匹配 ,不支持前缀匹配、范围查找等
- 参数
innodb_adaptive_hash_index
配置可以禁用或启动此特性(默认开启
-
异步 IO
- 为提高磁盘操作性能,当前数据库系统都采用异步 IO(AIO) 的方式来处理磁盘操作
- Sync IO
- 每进行一次 IO 操作,需要等待此次操作结束后才能继续接下来的操作
- AIO
- 可以在发出一个 IO 请求后立即再发出另一个 IO 请求,当全部请求发送完毕后,等待所有 IO 操作的完成
- 还可以进行 IO Merge 操作(将多个 IO 合并为 1个 IO),提高 IOPS 的性能
- read ahead 方式的读取、脏页的刷新等全部都是由 AIO 完成
-
刷新邻接页
- 当刷新一个脏页时,InnoDB 会检测该页所在区(extend)的所有页,如果是脏页,则一起刷新
- 优势:可以通过 AIO 将多个 IO 写入操作合并为一个 IO 操作
- 需要考虑的问题
- 可能将不怎么脏的页进行了刷新,然后该页又很快变成了脏页
- 固态硬盘有这较高的 IOPS,是否还需要该特性
- 参数
innodb_flush_neighbors
配置是否启用该特性- 传统机械硬盘 建议启用该特性
- 固态硬盘 有这超高 IOPS 性能的磁盘 建议关闭此特性