InnoDB存储引擎

InnoDB 存储引擎是多线程的模型,后台有多个不同的后台线程,负责处理不同的任务。

后台线程

Master Thread
主线程是一个核心后台线程,负责缓冲池中的数据异步刷新到磁盘,保证数据的一致性。
IO Thread
在 InnoDB 中大量使用了 AIO 即异步 IO 来处理写请求,这样可以极大提高数据库的性能。而 IO Thread 的工作主要是负责这些 IO 请求的回调处理。
Purge Thread
事物被提交后,所使用的 undolog 可能不再需要,就由 PurgeThread 来回收已经使用并分配的 undo 页。从 InnoDB 1.2 版本后,支持多个 Purge Thread ,是为了进一步加快 undo 页的回收。由于 Purge Thread 需要离散的读取 undo 页,这样也能更进一步利用磁盘的随机读取性能。
Page Cleaner Thread
1.2 版本后引入的,作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。目的是为了减轻原来主线程的工作以及对用户查询线程的阻塞,进一步提高引擎的性能。

内存

缓冲池

InnoDB 存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看做基于磁盘的数据库系统。由于 CPU 和磁盘的速度鸿沟,所以要使用缓冲池技术来提高整体性能。
缓冲池通过内存的速度来提高性能。
进行读取页的操作时,首先将从磁盘读到的页放入缓冲池中。下次再读到相同的页时,首先判断该页是否存在于缓冲池中。如果在缓冲池中,则该页被命中,直接读取该页。否则,读取磁盘上的页。
进行页的修改操作时,首先修改缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里要注意,页从缓冲池刷新到磁盘的操作并不是每次页发生更新时触发,而是通过 Checkpoint 的机制刷新回磁盘。也是为了提高数据库的整体性能。
所以缓冲池的大小直接影响数据库的整体性能,其大小可以通过参数 innodb_buffer_pool_size 来设置。
如查看缓冲池大小为 134217728 字节,即 128M 大小。

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size'\G;
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_size
        Value: 134217728
1 row in set (0.00 sec)

缓冲池中缓存的数据页类型有:索引页,数据页,undo 页,插入缓冲,自适应哈希索引,锁信息,数据字典信息等。
从 1.0 之后开始,允许有多个缓冲池实例,每个页根据哈希值平均分配到不同的缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。由 innodb_buffer_pool_instance 参数控制,默认为 1 个:

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances'\G;
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_instances
        Value: 1
1 row in set (0.00 sec)

也可以从 information_schema 中的 INNODB_BUFFER_POOL_STATS 表中来观察缓冲状态。

LRU/Free/Flush

通常,数据库中的缓冲池是通过 LRU(Latest Recent Used 最近最少使用)算法管理的。即使用最频繁的页在 LRU 列表的前端,而最少使用的页在 LRU 列表的尾端。当缓冲池中不能存放新读取的页时,首先释放 LRU 列表中尾端的页。
InnoDB 中缓冲池中的页大小默认为 16KB,同样使用 LRU 算法来管理缓冲池,并进行了一些优化。加入了 midpoint 位置。新读取到的页,虽然是最新访问的页,但并不是直接放入 LRU 列表的首部,而是放入 midpoint 位置。这个算法在 InnoDB 引擎中是 midpoint insertion strategy 。默认配置下,位置在 LRU 列表长度的 5/8 处。由参数 innodb_old_blocks_pct 控制:

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct'\G;
*************************** 1. row ***************************
Variable_name: innodb_old_blocks_pct
        Value: 37
1 row in set (0.00 sec)

默认值为 37 表示新读取的页插入到距离 LRU 列表尾端的 37% 的位置,即 3/8 的位置。之后的列表,都叫 old 列表,这位置之前的列表称为 new 列表。
不采用朴素的 LRU 算法,是因为在某些查询中,可能会访问到表中的许多页,甚至是全部页,而这些页通常不是活跃的热点数据,那么如果在列表头部开始添加,很可能之前的热点数据被冲出缓冲池,造成下一次访问该数据时要从磁盘读取。
另外一个参数 innodb_old_blocks_time ,可以进一步管理 LRU 列表,表示页读取到 mid 位置后,等待多久才会被加入到 LRU 列表的热端。
所以就有2种情况:
- 如果在业务中做了大量的全表扫描,那么你就可以将 innodb_old_blocks_pct 设置减小,增大innodb_old_blocks_time 的时间,不让这些无用的查询数据进入 old 区域,尽量不让缓存再 new 区域的有用的数据被立即刷掉。(这也是治标的方法,大量全表扫描就要优化 sql 和表索引结构了)
- 如果在业务中没有做大量的全表扫描,那么你就可以将 innodb_old_blocks_pct 增大,减小 innodb_old_blocks_time 的时间,让有用的查询缓存数据尽量缓存在 innodb_buffer_pool_size 中,减小磁盘 io ,提高性能。
LRU 列表用来管理已经读取的页,当数据库刚启动时,LRU 列表是空的,没有任何页。这时页都存放在 Free 列表中,当需要从缓冲池中分页时,将该页从 Free 列表中删除,放入 LRU 列表中。

LRU 中的压缩页
从 1.0 版本之后,支持压缩页功能,16K 的页可以压缩为 1,2,4,8KB。压缩页是通过 unzip_LRU 来管理的。在 LRU 中的页包含有 unzip_LRU 页。

脏页
当 LRU 中的页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页数据不一致,这时会通过 checkpoint 机制来将脏页刷新回磁盘,而 Flush 列表中的页即为脏页列表。脏页既存在于 LRU 中,又存在于 Flush 列表中。二者作用不同互不影响。innodb_buffer_page_lru 表中可以看到 oldest_modification>0 的就是脏页。

重做日志缓冲
设置重做日志缓冲区的目的就是为了在数据库崩溃的时候可以进行恢复数据库。
参数 innodb_log_buffer_size 控制,默认 8MB,主线程每秒钟就会将重做日志缓冲刷新到重做日志文件,而且在每个事物提交时会将重做日志缓冲刷新到重做日志文件,另外,当重做日志缓冲池空间小于一半时,也将刷新到文件。

checkpoint

缓冲池的设计能够提高操作速度。当页发生更改操作时,变为脏页,如果每次都刷新回磁盘,开销是很大的。同时由于 Write Ahead Log 策略,当事物提交时,先写重做日志,再修改页的机制,能够通过日志来恢复数据。
检查点的目的:
- 缩短数据库的恢复时间
- 缓冲池不够用时,将脏页刷新到磁盘
- 重做日志不可用时,刷新脏页
当数据库宕机时,数据库不需要重做所有日志,因为检查点之前的页都已经刷新回磁盘,只需要对检查点之后的重做日志进行恢复。这样大大缩短恢复的时间。
当缓冲池不够用时,LRU 尾部的页如果是脏页,将被冲掉,此时会强制执行检查点,刷新回磁盘。

Master Thread

主线程中由多个循环组成,主循环,后台循环,刷新循环,暂停循环。主线程会根据数据库运行的状态在四种循环中进行切换。

1.0以前版本的主线程

包括每秒一次和每十秒一次的操作:
每秒一次的操作:
- 日志缓冲刷新到磁盘,即使这个事务还没有提交,引擎仍然会每秒将重做日志缓冲中的内容刷新到重做日志文件。
- 合并插入缓冲,判断当前一秒内的 IO 次数小于 5 次,那么认为压力很小,可以执行合并插入缓冲操作。
- 最多刷新 100 个缓冲池中的脏页到磁盘,会判断脏页的比例,是否超过了 innodb_max_dirty_pages_pct 参数,如果超过了阈值,开始同步。
- 如果当前没有用户活动,切换到后台循环。
每10秒一次的操作:
- 刷新 100 个脏页到磁盘;
- 合并最多 5 个插入缓冲;
- 将日志缓冲刷新到磁盘;
- 删除无用的 undo 页;
- 刷新 100 个或者 10% 脏页到磁盘。
在以上的过程中,InnoDB 存储引擎会判断过去 10 秒内,磁盘的 IO 能力是否小于 200 次,如果是,判断能有足够的磁盘 IO 操作能力来进行刷新脏页到磁盘。另外,执行 full purge 操作,每次最多尝试回收 20 个 undo 页。然后,判断脏页的比例,如果大于 70% ,就刷新 100 个脏页到磁盘,如果小于 70%,那么就刷新 10% 的脏页。

1.2以前版本的主线程

由于固态硬盘出现,如果在写入密集的程序中,每秒脏页会特别多,主线程忙不过来。所以,提供了 innodb_io_capacity 参数,刷新到磁盘的量,会根据这个值来控制:
- 在合并插入缓冲时,合并插入缓冲的数量为该值的 5%
- 从缓冲区刷新脏页时,刷新脏页的数量为该值。
innodb_max_dirty_pages_pct 参数默认改为了 75,加快脏页刷新的频率,又保证负载。
innodb_purge_batch_size 参数,控制每次 full purge 回收的 undo 页的数量,默认值为 20,可以修改。

1.2版本的主线程

又进行了一定优化,对于刷新脏页的操作,从主线程分离出去一个单独线程处理,进一步提高并发性。

InnoDB的关键特性

插入缓冲

插入缓冲和数据页一样,是物理页的组成部分。在 InnoDB 中,主键是行唯一标识符,通常记录的插入顺序是主键递增顺序。但是对于非聚集且不唯一的索引,在执行插入时,对于非聚集索引的叶子结点的插入不再是顺序的,就需要离散的访问非聚集索引页。由于随机读取的存在导致了插入操作的性能下降。这是因为 B+ 树的特性决定了非聚集索引插入的离散型。
对于这种非聚集索引的插入和更新,不是每一次都直接插入到索引页,而是先判断插入的非聚集索引页是否在缓冲池中,若在,直接插入,若不在,先放入到一个插入缓冲中。实际上并没有查到叶子结点。之后再以一定的频率和情况进行插入缓冲和辅助索引页子节点的合并操作,这是通常能将多个插入合并到一个操作中,大大提高了对于非聚集索引插入的性能。
插入缓冲的使用要满足以下两个条件:
- 索引是辅助索引;
- 索引不是唯一的。

两次写

两次写由两部分组成,一部分是内存中的两次写缓冲,大小为 2MB,另一部分是物理磁盘上共享表空间中连续的 128 个页,即 2 个区,同样为 2MB。对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的两次写缓冲,之后再分两次,每次 1MB 顺序地写入共享表空间的物理磁盘上,然后马上调用 fsync 函数,同步磁盘,避免缓冲写带来的问题。

自适应哈希索引

哈希的查找时间复杂度为 O(1) ,而 B+ 数一般为 3-4 层,需要3,4次查询。InnoDB 引擎会监控表上各索引页的查询,如果观察到可以建立哈希索引来提升速度,就建立哈希索引,叫自适应哈希索引。是通过缓冲池的 B+ 树页构造而来,所以建立的速度很快,不需要对整张表构建哈希索引。会根据访问的频率和模式来自动地为某些热点页建立哈希索引。

异步IO

read ahead 方式的读取都是通过 AIO 来完成,还有脏页的刷新,磁盘的写入等。

刷新邻接页

每当刷新一个脏页时,引擎会检测该页所在区的所有页,如果是脏页,一起进行刷新。但对于固态硬盘,不需要此性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值