阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

InnoDB关键特性学习笔记

119次阅读
没有评论

共计 5740 个字符,预计需要花费 15 分钟才能阅读完成。

插入缓存

Insert Buffer

Insert Buffer 是 InnoDB 存储引擎关键特性中最令人激动与兴奋的一个功能。不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分。其实不然,InnoDB 缓冲池中有 Insert Buffer 信息固然不错,但是 Insert Buffer 和数据页一样,也是物理页的一个组成部分。

一般情况下,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引一般是顺序的,不需要磁盘的随机读取。因为,对于此类情况下的插入,速度还是非常快的。(如果主键类是 UUID 这样的类,那么插入和辅助索引一样,也是随机的。)

如果索引是非聚集的且不唯一。在进行插入操作时,数据的存放对于非聚集索引叶子节点的插入不是顺序的,这时需要离散地访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。这是因为 B + 树的特性决定了非聚集索引插入的离散性。

Insert Buffer 的设计,对于非聚集索引的插入和更新操作,不是每一次直接插入到索引页中,而是先判断插入非聚集索引页是否在缓冲池中,若存在,则直接插入,不存在,则先放入一个 Insert Buffer 对象中。数据库这个非聚集的索引已经插到叶子节点,而实际并没有,只是存放在另一个位置。然后再以一定的频率和情况进行 Insert Buffer 和辅助索引页子节点的 merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。

需要满足的两个条件:

  • 索引是辅助索引;
  • 索引不是唯一的。

辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致 Insert Buffer 失去了意义。

Change Buffer

InnoDB 从 1.0.x 版本开始引入了 Change Buffer,可将其视为 Insert Buffer 的升级。从这个版本开始,InnoDB 存储引擎可以对 DML 操作——INSERT、DELETE、UPDATE 都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge buffer。

当然和之前 Insert Buffer 一样,Change Buffer 适用的对象依然是非唯一的辅助索引。

对一条记录进行 UPDATE 操作可能分为两个过程:

  • 将记录标记为已删除(Delete Buffer);
  • 真正将记录删除(Purge buffer)。

因此 Delete Buffer 对应 UPDATE 操作的第一个过程,即将记录标记为删除。Purge Buffer 对应 UPDATE 操作的第二个过程,即将记录真正的删除。同时,InnoDB 存储引擎提供了参数 innodb_change_buffering,用来开启各种 Buffer 的选项。该参数可选的值为:inserts、deletes、purges、changes、all、none。inserts、deletes、purges 就是前面讨论过的三种情况。changes 表示启用 inserts 和 deletes,all 表示启用所有,none 表示都不启用。该参数默认值为 all。

从 InnoDB 1.2.x 版本开始,可以通过参数 innodb_change_buffer_max_size 来控制 Change Buffer 最大使用内存的数量,innodb_change_buffer_max_size 值默认为 25,表示最多使用 1 / 4 的缓冲池内存空间。而需要注意的是,该参数的最大有效值为 50。

在 MySQL 5.5 版本中通过命令 SHOW ENGINE INNODB STATUS,可以观察到类似如下的内容:

mysql> SHOW ENGINE INNODB STATUS\G;
……
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 34397, seg size 34399, 10875 merges

merged operations:

insert 20462, delete mark 20158, delete 4215

discarded operations:

insert 0, delete mark 0, delete 0
……

可以看到这里显示了 merged operations 和 discarded operation,并且下面具体显示 Change Buffer 中每个操作的次数。insert 表示 Insert Buffer;delete mark 表示 Delete Buffer;delete 表示 Purge Buffer;discarded operations 表示当 Change Buffer 发生 merge 时,表已经被删除,此时就无需再将记录合并(merge)到辅助索引中了。

Merge Insert Buffer

通过前面的小节读者应该已经知道了 Insert/Change Buffer 是一棵 B + 树。若需要实现插入记录的辅助索引页不在缓冲池中,那么需要将辅助索引记录插入到这棵实际 B + 树中。但是 Insert Buffer 中的记录何时合并(merge)到真正的辅助索引中呢?

概括地说,Merge Insert Buffer 的操作可能发生在以下几种情况下:

  • 辅助索引页被读取到缓冲池时;
  • Insert Buffer Bitmap 页追踪到该辅助索引页已无可用空间时;
  • Master Thread。

第一种情况为当辅助索引页被读取到缓冲池中时,例如这在执行正常的 SELECT 查询操作,这时需要检查 Insert Buffer Bitmap 页,然后确认该辅助索引页是否有记录存放于 Insert Buffer B+ 树中。若有,则将 Insert Buffer B+ 树中该页的记录插入到该辅助索引页中。可以看到对该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,因此性能会有大幅提高。

Insert Buffer Bitmap 页用来追踪每个辅助索引页的可用空间,并至少有 1 /32 页的空间。若插入辅助索引记录时检测到插入记录后可用空间会小于 1 /32 页,则会强制进行一个合并操作,即强制读取辅助索引页,将 Insert Buffer B+ 树中该页的记录及待插入的记录插入到辅助索引页中。这就是上述所说的第二种情况。

还有一种情况,之前在分析 Master Thread 时曾讲到,在 Master Thread 线程中每秒或每 10 秒会进行一次 Merge Insert Buffer 的操作,不同之处在于每次进行 merge 操作的页的数量不同。

两次写

doublewrite 应用场景

我们知道,innodb 的数据页一般大小是 16KB,MySQL 存取数据的最小单位也是页,而操作系统并不能保障一个数据页的原子性,也就是说当写入数据时,有可能在一个页中写入一半时(比如 8K)数据库宕机,这种情况称为部分写失效(partial page write),从而导致数据丢失。

大家也许会问,难道我不可以根据 redo log 进行数据恢复吗?答案是肯定的也是否定的,要分为两种情况:1、数据库宕机,物理文件完好无损,是可以通过 redo log 进行崩溃恢复。2、数据库宕机,正在刷新到磁盘的页发生 partial page write,而正好在磁盘上的这个数据页由于宕机发生损坏,这时就无法通过 redo log 进行数据恢复了,为什么?我们必须要清楚的认识到,redo log 里记录的是对页的物理操作!比如一条 redo 记录 ”page number xx,偏移量 800 写记录“this is abc””,那当页损坏时,这条 redo 记录还有意义吗?于是在这种特殊情况下,doublewrite 就派上用场啦!

doublewrite 体系结构及工作流程

doublewrite 由两部分组成,一部分为内存中的 doublewrite buffer,其大小为 2MB,另一部分是磁盘上共享表空间(ibdata x)中连续的 128 个页,即 2 个区(extent),大小也是 2M。doublewrite 工作流程如下:

  1. 当一系列机制(main 函数触发、checkpoint 等)触发数据缓冲池中的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 doublewrite buffer, 之后通过 doublewrite buffer 再分两次、每次 1MB 顺序写入共享表空间的物理磁盘上。
  2. 马上调用 fsync 函数,同步脏页进磁盘。由于在这个过程中,doublewrite 页的存储时连续的,因此写入磁盘为顺序写,性能很高;完成 doublewrite 后,再将脏页写入实际的各个表空间文件,这时写入就是离散的了。各模块协作情况如下图(第一步应为脏页产生的 redo 记录 logbuffer,然后 logbuffer 写入 redo log file,为简化次要步骤直接连线表示):

InnoDB 关键特性学习笔记

查看 doublewrite 工作情况,可以执行命令:

mysql> show global status like 'innodb_dblwr%'\G
*************************** 1. row ***************************
Variable_name: Innodb_dblwr_pages_written
        Value: 61932183
*************************** 2. row ***************************
Variable_name: Innodb_dblwr_writes
        Value: 15237891
2 rows in set (0.00 sec)

以上数据显示,doublewrite 一共写了 61932183 个页,一共写了 15237891 次,从这组数据我们可以分析,之前讲过在开启 doublewrite 后,每次脏页刷新必须要先写 doublewrite,而 doublewrite 存在于磁盘上的是两个连续的区,每个区由连续的页组成,一般情况下一个区最多有 64 个页,所以一次 IO 写入应该可以最多写 64 个页。而根据以上我这个系统 Innodb_dblwr_pages_written 与 Innodb_dblwr_writes 的比例来看,大概在 4 左右,远远还没到 64,所以从这个角度也可以看出,系统写入压力并不高。

崩溃恢复

如果操作系统在将页写入磁盘的过程中发生崩溃,如上图,在恢复过程中,innodb 存储引擎可以从共享表空间的 doublewrite 中找到该页的一个最近的副本,将其复制到表空间文件,再应用 redo log,就完成了恢复过程。因为有副本所以也不担心表空间中数据页是否损坏。

自适应哈希索引

哈希是一��非常快的查找方法,在一般情况时间复杂度为 O(1)。而 B + 树的查找次数,取决于 B + 树的高度,在生成环境中,B+ 树的高度一般为 3 - 4 层,不需要查询 3 - 4 次。

InnoDB 存储引擎会监控对表上各索引页的查询。如果观察到简历哈希索引可以提升速度,这简历哈希索引,称之为自适应哈希索引(Adaptive Hash Index, AHI)。AHI 是通过缓冲池的 B + 树页构造而来的。因此建立的速度非常快,且不要对整张表构建哈希索引。InnoDB 存储哟 inquiry 会自动根据房屋的频率和陌生来自动的为某些热点页建立哈希索引。

AHI 有一个要求,对这个页的连续访问模式 (查询条件) 必须一样的。例如联合索引 (a,b) 其访问模式可以有以下情况:

  • WHERE a=XXX;
  • 2.WHERE a=xxx AND b=xxx。

若交替进行上述两张查询,InnoDB 存储引擎不会对该页构造 AHI。此外 AHI 还有如下要求:

  • 以该模式访问了 100 次;
  • b. 页通过该模式访问了 N 次,其中 N = 页中记录 /16。

根据官方文档显示,启用 AHI 后,读取和写入的速度可以提高 2 倍,负责索引的链接操作性能可以提高 5 倍。其设计思想是数据库自由化的,无需 DBA 对数据库进行人为调整。

异步 IO

为了提高磁盘操作性能,当前的数据库系统都采用异步 IO 的方式来处理磁盘操作。InnoDB 也是如此。

与 AIO 对应的是 Sync IO,即每进行一次 IO 操作,需要等待此次操作结束才能继续接下来的操作。但是如果用户发出的是一条索引扫描的查询,那么这条 SQL 语句可能需要扫描多个索引页,也就是需要进行多次 IO 操作。在每扫描一个页并等待其完成再进行下一次扫描,这是没有必要的。用户可以在发出一个 IO 请求后立即再发出另外一个 IO 请求,当全部 IO 请求发送完毕后,等待所有 IO 操作完成,这就是 AIO。

AIO 的另外一个优势是进行 IO Merge 操作,也就是将多个 IO 合并为一个 IO 操作,这样可以提高 IOPS 的性能。

在 InnoDB 1.1.x 之前,AIO 的实现是通过 InnoDB 存储引擎中的代码来模拟的。但是从这之后,提供了内核级别的 AIO 的支持,称为 Native AIO。Native AIO 需要操作系统提供支持。Windows 和 Linux 都支持,而 Mac 则未提供。在选择 MySQL 数据库服务器的操作系统时,需要考虑这方面的因素。

MySQL 可以通过参数 innodb_use_native_aio 来决定是否启用 Native AIO。在 InnoDB 存储引擎中,read ahead 方式的读取都是通过 AIO 完成,脏页的刷新,也是通过 AIO 完成。

刷新邻接页

InnoDB 存储引擎在刷新一个脏页时,会检测该页所在区 (extent) 的所有页,如果是脏页,那么一起刷新。这样做的好处是通过 AIO 可以将多个 IO 写操作合并为一个 IO 操作。该工作机制在传统机械磁盘下有显著优势。但是需要考虑下吧两个问题:

  • 是不是将不怎么脏的页进行写入,而该页之后又会很快变成脏页?
  • 固态硬盘有很高 IOPS,是否还需要这个特性?

为此 InnoDB 存储引擎 1.2.x 版本开始提供参数 innodb_flush_neighbors 来决定是否启用。对于传统机械硬盘建议使用,而对于固态硬盘可以关闭。

本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-01/139358.htm

正文完
星哥说事-微信公众号
post-qrcode
 
星锅
版权声明:本站原创文章,由 星锅 2022-01-22发表,共计5740字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中