InnoDB - Architecture
Created by : Mr Dk.
2020 / 10 / 09 11:18
Nanjing, Jiangsu, China
Introduction
InnoDB 从 MySQL 5.5 开始是默认的表存储引擎。该存储引擎支持事务,并且是第一个完整支持 ACID 事务的 MySQL 存储引擎。特点是:
- 行锁设计
- 支持 多版本并发控制 (MVCC)
- 支持外键、一致性非锁定读
- 高效利用内存和 CPU
创始人为 Teikki Tuuri,毕业于芬兰 赫尔辛基大学。
InnoDB 是一个高性能、高可用、高可扩展的存储引擎,设计目标面向 OLTP 场景。
OLTP (On-Line Transaction Processing),联机事务处理 / OLAP (On-Line Analytical Processing) 联机分析处理
Architecture
InnoDB 存储引擎中有一个大的内存池,包含大量的 内存块。这些内存块的功能如下:
- 维护进程 / 线程的数据结构
- 缓存磁盘数据 (读 / 写)
- Redo 日志的 buffer
MySQL 表现为单进程多线程架构。多个后台线程负责刷新内存池中的数据,并将内存池中已被修改的数据刷新到磁盘上的文件中,保证数据库发生异常的情况下 InnoDB 能恢复到正常运行的状态。
Threads
Master Thread
核心后台线程,负责将内存池中的数据异步刷新到磁盘,保证数据的一致性。
I/O Thread
使用 AIO (Async I/O) 来处理写 I/O,以极大提升数据库性能。这个线程就是异步 I/O 的 call back。包含不同类型的 I/O 线程:
- Write I/O thread
- Read I/O thread
- Insert buffer I/O thread
- Log I/O thread
线程的个数可以进行调整。
Purge Thread
事务在被提交后,回滚需要用到的 undo 日志不再需要,由该线程回收已经分配并使用的 undo 页面。该线程要做的事情原先由 master thread 完成,现在独立到了单独的线程中,从而提高 CPU 利用率。多个 purge thread 离散地读取多个 undo 页可以进一步利用磁盘的随机读取性能。
Page Cleaner Thread
将 master thread 中原先的脏页刷新工作独立到这个单独的线程中,减轻 master thread 对用户查询线程的阻塞。
Memory
缓冲池管理
InnoDB 存储引擎是基于磁盘存储的,其中的记录以 页 为单位进行管理,是一个 Disk-base Database。这类 DBMS 通常使用缓冲池技术来提升整体性能。
当对数据库进行页读取操作时,首先将磁盘读到的页 FIX 到缓冲池中,下次读取相同页时,如果该页在缓冲池中,就可以被直接读取 (磁盘操作变为内存操作)。
对于页面的修改操作,直接修改内存缓冲池中的页,再以一定的频率写回磁盘。写回磁盘的策略并不是每修改一个页就触发,而是以 Checkpoint 机制作为触发时机。
缓冲池的大小直接影响数据库的整体性能,通过参数可配置。另外,InnoDB 还允许有多个缓冲池实例,每个页根据哈希映射到不同的缓冲池实例上,以减少数据库内部的资源竞争,增加数据库的并发处理能力。
缓冲池中缓存的页类型:
- 索引页 + 数据页 (占大头)
- Undo 页
- Insert buffer 页
- 自适应哈希索引页
- InnoDB 锁信息页
- 数据字典页
- ...
上述的内存缓冲池 (不包括后几种内存页) 是通过 LRU (Least Recently Used) 算法来进行管理的 - 最频繁使用的页位于 LRU 列表的前端,最少使用的页位于 LRU 列表尾部。当缓冲池中的页不够用时,优先淘汰处于 LRU 列表尾部的页。InnoDB 默认页大小为 16KB。
InnoDB 对朴素的 LRU 算法进行了改进,引入了一个所谓 midpoint 的位置。一个新读取的页,虽然是被最新访问的,但并不是放到 LRU 列表的首部,而是放入 LRU 列表的 midpoint 位置。这个位置默认位于 LRU 列表长度的 5/8 处,也就是尾端 37% 的位置,可以用参数进行配置。在 InnoDB 中,midpoint 之后的列表称为 old 列表,之前的列表称为 new 列表,显而易见 new 列表中的数据都是最为活跃的热点数据 (让我联想到了 JVM 中的新生代和老生代)。
这种改进的用意何在?某些 SQL 操作会大量扫描页 (甚至扫描所有的页),比如索引或数据扫描,但是这些页只会在这次查询中被用到。如果这些页直接进入 LRU 首部,那么大量热点数据将会被挤出 LRU,从而引发额外的 I/O 操作。为了解决这个问题,InnoDB 引入另一个参数 innodb_old_blocks_time
来管理 LRU 列表,表示一个页在进入 old 列表多久之后可以进入 LRU 的 new 列表。DBA 可以预估自己的热点数据比例调整参数 (甚至可以在执行某条 SQL 语句前),减少热点页可能被刷出的概率。
数据库启动时,LRU 列表是空的,所有的页都位于 Free 列表中。需要缓冲页时,首先从 Free 列表中查找是否有可用的空闲页,若有,则从 Free 列表中删除并加入 LRU 列表中。否则,根据 LRU 算法淘汰 LRU 列表末尾的策略。页从 LRU 列表的 old 部分加入的 new 部分的操作称为 Page Made Young。另外 InnoDB 还会维护一些缓冲池运行状态的变量,DBA 可以根据这些变量判断缓冲池的运行状态 - 比如,缓冲池命中率不应当低于 95%。
InnoDB 支持 压缩页 的功能,即原先的 16KB 页压缩为 1KB、2KB、4KB 和 8KB。非 16KB 页由一个单独的列表进行管理。由于每个页的压缩比例不同,该列表通过 伙伴算法 对不同大小的页进行管理。
伙伴算法 在 Linux 内存管理中也有运用。大致思想是维护多个不同块大小的链表,根据申请分配的内存大小,按需分配,以解决内存碎片的问题,主要面向大块内存的分配。小块内存的分配由 slab 内存分配器负责。
当 LRU 列表中的页被写操作修改后,就会成为所谓的 dirty page,需要通过 checkpoint 机制将这些页写回磁盘中。Flush 列表中的页就是所有脏页。脏页同时存在于 LRU 列表和 flush 列表中,两个列表分工不同,互不影响。
Redo 日志缓冲
InnoDB 存储引擎首先将 redo log 存放在这个缓冲区中,然后以一定频率写回到 redo log 文件。写回的发生时机有:
- Master thread 的每秒刷新
- 每个事务提交时的刷新
- Redo log buffer 剩余空间低于 1/2
额外内存池
缓冲池本身以外的数据结构,比如 LRU、锁等信息,会在额外的内存池中申请。当缓冲池的容量增加时,DBA 也需要考虑酌情增加额外内存池的值。