一、InnoDB体系结构
InnoDB 存储引擎有多个内存块,这些内存块组成了一个大的内存池,负责的工作如下:
- 维护所有进程/线程需要访问的多个内部数据结构。
- 缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存。
- 重做日志缓冲(redo log)。
后台线程的作用:负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态。
二、后台线程
InnoDB存储引擎是多线程模型,因此后台有多个不同的后台线程,负责处理不同的任务。
1、Master Thread
- Master Thread 是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、undo 页的回收等。
2、IO Thread
- 在InnoDB中大量使用了 AIO(Async IO)来处理IO请求,这样可以极大提高数据库的性能。IO Thread 的主要工作是负责这些IO请求的回调处理。
- InnoDB 1.0 版本之前共有四个IO Thread,分别是write、read、insert buffer 、log IO Thread。
- InnoDB 1.0.x 版本开始,read thread 和 write thread 分别增大到4个,分别使用 innodb_read_io_threads 和 innodb_wirte_io_threads 参数进行设置。
- 查看参数:SHOW VARIABLE LIKE ‘innodb_%io_threads’\G;
3、Purge Thread
- 事务被提交后,所使用的 undolog 日志可能不再需要,因此需要 Purge Thread 线程来回收已经使用且分配的undo页。InnoDB 1.1 版本开始,purge 操作可以独立到单独的线程中进行,可以减轻 master thread 的工作,提高CPU的使用率和提升存储引擎的性能。
- 启用purge:innodb_purge_threads=1。
- InnoDB 1.2 版本开始,可以启用多个 purge thread,这样做的目的是为了加快 undo 页的回收。同时由于 purge thread 需要离散的读取 undo 页,这样也能进一步利用磁盘的随机读取性能。
三、内存
1、缓冲池
- InnoDB 存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。在数据库系统中,由于CPU速度和磁盘速度之间存在较大的差异,所以基于磁盘存储的数据库系统常用缓冲池技术来提高数据库的整体性能。缓冲池就是一块内存区域。
- 在数据库中进行读取数据页的操作,首先将从磁盘读到的页放入缓冲池中,也就是将页 “FIX” 在缓冲池中。下一次要读取该页时则先从缓冲池进行读取,若缓冲池中没有才读取磁盘上的数据。
- 对于数据库中页的修改操作,则首先是在缓冲池中修改对应的页,然后再按照一定的频率刷新到磁盘上。页从缓冲池刷新到磁盘并不是每次页发生更改时触发,而是通过 “checkpoint” 机制刷新回磁盘。通过这种方式可以提高数据库的整体性能。
- 缓冲池大小设置:参数 innodb_buffer_pool_size 可以设置缓冲池的大小。
- 缓冲池中缓存的数据页类型:索引页、数据页、undo 页、插入缓冲(insert buffer)、自适应哈希索引、innodb存储的锁信息、数据字典信息等。其中索引页和数据页占缓冲池的很大一部分内存。
- 从 InnoDB 1.0.x 版本开始,可以设置多个缓冲池,将每个页根据哈希值平均分配到不同缓冲池实例中,可以减少数据库内部的竞争,增加数据库的并发处理能力。可以通过参数 “innodb_buffer_pool_instances” 来配置(可通过 “show engine innodb status” 查看缓冲池状态)。
2、内存区域管理:LRU List、Free List、Flush List
- 数据库的缓冲池通过 LRU 算法进行管理的。当缓冲池不能存放新读取的页时,首先将 LRU 列表中尾端的页,再将新页放在 LRU 列表中前端。在 InnoDB 存储引擎中,缓冲池中页的默认大小是 16KB。
- InnoDB 对传统的 LRU 算法做了优化,在 LRU列表中加入了 midpoint 位置。新读取的页并不是直接放入 LRU 列表的首部,而是放到 LRU 列表的 midpoint 位置。在默认配置下,midpoint 位置在 LRU 列表长度的 5/8 处,midpoint 位置可以由参数 “innodb_old_blocks_pct” 控制。把 midpoint 之前的列表称为 new 列表(热点数据),之后的列表称为 old 列表。
- 为什么要对 LRU 算法进行改进?
将读取的页直接放入 LRU 的首部,常见的这些操作作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部页,这些页通常来说只是在本次查询中使用,并不是活跃的热点数据。如果将页放入 LRU 列表的首部,那么非常可能将所需要的热点数据页从列表中移除,在下一次需要读取这些页时,需要再次访问磁盘,从而影响缓冲池的效率。 - InnoDB 存储引擎还引入了另一个参数 “innodb_old_blocks_time” 来进一步管理 LRU 列表。用于表示页读取到 mid 位置后需要等待多久才会被加入到 LRU 列表的首部。
- LRU 列表用来管理已经读取的页,当数据库启动时,列表是空的,这时页都存放在 Free 列表中。当需要从缓冲池中分页时,首先从 Free 列表中查询是否有可用的空闲页,若有则从 Free 列表中移除,放入到 LRU 列表中;否则,根据 LRU 算法淘汰 LRU 末尾的页,将该内存分配非新的页。当页从 old 部分加入到 new 部分时,此时发生的操作称作 “page made young” ;因为 “innodb_old_blocks_time” 参数的设置页没有从 old 部分加入到 new 部分时,此时发生的操作称作 “page not made young”。LRU 列表和 Free 列表的使用情况和运行情况如下所示:
- I