关于innodb:MySQL之InnoDB存储结构-京东物流技术团队

25次阅读

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

1 InnoDB 存储引擎

InnoDB 存储引擎最早由 Innobase Oy 公司开发(属第三方存储引擎)。从 MySQL 5.5 版本开始作为表的默认存储引擎。该存储引擎是第一个残缺反对 ACID 事务的 MySQL 存储引擎,特点是行锁设计、反对 MVCC、反对外键、提供一致性非锁定读,非常适合 OLTP 场景的利用应用。目前也是利用最宽泛的存储引擎。

InnoDB 存储引擎架构蕴含内存构造和磁盘构造两大部分,总体架构图如下:

8.0 版本:

[]()

5.5 版本:

[]()

2 InnoDB 存储构造

2.1 磁盘构造

2.1.1 表空间 Tablespaces

InnoDB 存储引擎的逻辑存储构造是将所有的数据都被逻辑地放在了一个空间中,这个空间中的文件就是理论存在的物理文件(.ibd 文件),即表空间。默认状况下,一个数据库表占用一个表空间,表空间能够看做是 InnoDB 存储引擎逻辑构造的最高层,所以的数据都寄存在表空间中,例如:表对应的数据、索引、insert buffer bitmap undo 信息、insert buffer 索引页、double write buffer files 等都是放在共享表空间中的。

表空间分为零碎表空间(ibdata1 文件)(共享表空间)、长期表空间、惯例表空间、Undo 表空间和 file-per-table 表空间(独立表空间)。零碎表空间又包含双写缓冲区(Doublewrite buffer)、Change Buffer 等

1. 零碎表空间 System Tablespace

零碎表空间能够对应文件系统上一个或多个理论的文件,默认状况下,InnoDB 会在数据目录下创立一个名为.ibdata1,大小为 12M 的文件,这个文件就是对应的零碎表空间在文件系统上的示意。这个文件是能够自扩大的,当不够用的时候它会本人减少文件大小。须要留神的一点是,在一个 MySQL 服务器中,零碎表空间只有一份。从 MySQL5.5.7 到 MySQL5.6.6 之间的各个版本中,咱们表中的数据都会被默认存储到这个零碎表空间。

show variables like '%innodb_data_file_path%'

[]()

2. 独立表空间

在 MySQL5.6.6 以及之后的版本中,InnoDB 并不会默认的把各个表的数据存储到零碎表空间中,而是为每一个表建设一个独立表空间,也就是说咱们创立了多少个表,就有多少个独立表空间。应用独立表空间来存储表数据的话,会在该表所属数据库对应的子目录下创立一个示意该独立表空间的文件,文件名和表名雷同,只不过增加了一个.ibd 的扩展名而已。

show variables like '%innodb_file_per_table%'

[]()

独立表空间只是存放数据、索引和插入缓冲 Bitmap 页,其余类的数据如回滚(undo)信息、插入缓冲索引页、零碎事务信息、二次写缓冲等还是寄存在原来的零碎表空间。

3. 其余类型的表空间

随着 MySQL 的倒退,除了上述两种表空间之外,当初还新提出了一些不同类型的表空间,比方通用表空间 (general tablespace)、undo 表空间 (undo tablespace)、长期表空间(temporary tablespace) 等

4. 表空间结构

表空间又由段 (segment)、区(extent)、页(page) 组成,页是 InnoDB 磁盘治理的最小单位。在咱们执行 sql 时,不论是查问还是批改,mysql 总会把数据从磁盘读取内内存中,而且在读取数据时,不会独自加在一条数据,而是间接加载数据所在的数据页到内存中。表空间实质上就是一个寄存各种页的页面池。

[]()

「页」是 InnoDB 治理存储空间的根本单位,也是内存和磁盘交互的根本单位。也就是说,哪怕你须要 1 字节的数据,InnoDB 也会读取整个页的数据,InnoDB 有很多类型的页,它们的用途也各不相同。比方:有寄存 undo 日志的页、有寄存 INODE 信息的页、有寄存 Change Buffer 信息的页、寄存用户记录数据的页(索引页)等等。

InnoDB 默认的页大小是 16KB,在初始化表空间之前能够在配置文件中进行配置,一旦数据库初始化实现就不可再变更了。

SHOW VARIABLES LIKE 'innodb_page_size'

[]()

2.1.2 重写日志 redo log 文件

redo log 记录数据库的变更,数据库解体后,会从 redo log 获取事务信息,进行零碎复原。redo log 在磁盘上体现为 ib_logfile0 和 ib_logfile1 两个文件。MySQL 会在事务的提交前将 redo 日志刷新回磁盘。

在同一时间提交的事务,会采纳组提交(group commit)的形式一次性刷新回磁盘。从而防止一个事务刷新一次磁盘,进步性能。

2.1.3 Double Write Files 双写缓冲文件

double write 是保障 InnoDB 存储引擎操作数据页的可靠性。double write 分为两局部组成,一部分在内存中的 double write buffer, 大小为 2MB,另一部分是物理磁盘上共享表空间中间断的 128 个数据页,即 2 个区大小(同样是 2MB)。

[]()

2.2 内存构造

InnoDB 存储引擎是基于磁盘存储的,并将其中的记录依照页的形式进行治理,因而可将其视为基于磁盘的数据库系统(Disk-base Database)。在数据库中 CPU 速度与磁盘速度是有很大差距的,基于磁盘的数据库系统通常应用缓冲池技术来进步数据库的整体性能。构造如图所示:

[]()

2.1.1 缓存池 Buffer Pool

Buffer Pool 是 InnoDB 内存中的一块占比拟大的区域,通过内存的速度来补救磁盘速度慢对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读到的页放在缓冲池中,这个过程称为将页”FIX”在缓冲池中,下次再读到雷同的页时,首先判断该页是否在缓冲池中,若在缓冲池中,间接读取该页,否则读取磁盘上的页。

对于数据库中的页的批改操作,首先批改在缓冲池中的页,而后再以肯定频率刷新到磁盘上,这里须要留神的是,页从缓冲池刷新回磁盘的操作并不是在每次页产生更新时触发,而是通过一种称为 Checkpoint 的机制刷新回磁盘。

缓存区缓存的数据页类型有:索引页,数据页,undo 页,插入缓冲(change buffer),自适应哈希索引(adaptive hash index),InnoDB 存储锁信息(lock info),数据字典信息(data dictionary)。数据页和索引页占据了缓冲池很大部分。

InnoDB1.0.x 版本开始,容许有多个缓冲池实例,每个页依据哈希值平均分配到不同缓冲池的实例中,这样能够缩小数据库外部资源竞争,减少数据库的并发解决能力。

show variables like 'innodb_buffer_pool_instances'

[]()

整个 Buffer Pool 的阐明用一张图来概括如下:

[]()

1.LRU List,Free List 和 Flush List——治理 InnoDB 内存区域

为了缓存治理的效率,缓冲池被实现为页链表,采纳三个链表保护内存页,而内存页也因而对应 3 种状态:Free 尚未应用;Clean 已应用但未修改;Dirty(脏页)已批改;Free 页只位于 Free List,而 Clean 和 Dirty 页同时位于 LRU List,Dirty 页只存在于 Flush List;

[]()

1)LRU List:

数据库中的缓冲池是通过 LRU(Latest Recent Used,最近起码应用)算法来进行治理的。即最频繁应用的页在 LRU 列表的前端,而起码应用的页在 LRU 列表的尾端。当缓冲池不能寄存新读取到的页时,将首先开释 LRU 列表中尾端的页。

在 InnoDB 存储引擎中,缓冲池中页的大小默认为 16KB,同样应用 LRU 算法对缓冲池进行治理。稍有不同的是 InnoDB 存储引擎对传统的 LRU 算法做了一些优化。在 InnoDB 的存储引擎中,LRU 列表中还退出了 midpoint 地位。新读取到的页,尽管是最新拜访的页,但并不是间接放入到 LRU 列表的首部,而是放入到 LRU 列表的 midpoint 地位。这个算法在 InnoDB 存储引擎下称为 midpoint insertion strategy。在默认配置下,该地位在 LRU 列表长度的 5 / 8 处。

[]()

SHOW VARIABLES LIKE'innodb_old_blocks_pct'

[]()

参数 innodb_old_blocks_pct 默认值为 37,示意新读取的页插入到 LRU 列表尾端的 37% 的地位(差不多 3 / 8 的地位)。在 InnoDB 存储引擎中,把 midpoint 之后的列表称为 old 列表,之前的列表称为 new 列表。能够简略地了解为 new 列表中的页都是最为沉闷的热点数据

  • 那为什么不采纳奢侈的 LRU 算法,间接将读取的页放入到 LRU 列表的首部呢?

这是因为若间接将读取到的页放入到 LRU 的首部,那么某些 SQL 操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作须要拜访表中的许多页,甚至是全副的页,而这些页通常来说又仅在这次查问操作中须要,并不是沉闷的热点数据。如果页被放入 LRU 列表的首部,那么十分可能将所须要的热点数据页从 LRU 列表中移除,而在下一次须要读取该页时,InnoDB 存储引擎须要再次拜访磁盘。

  • 解决热点数据被移除 LRU 列表

InnoDB 存储引擎引入了另一个参数来进一步治理 LRU 列表,这个参数是 innodb_old_blocks_time,用于示意页读取到 mid 地位后须要期待多久才会被退出到 LRU 列表的热端,通过这个办法尽可能使 LRU 列表中热点数据不被刷出。

SHOW VARIABLES LIKE'innodb_old_blocks_time'

[]()

当有新的数据从磁盘查问到内存时,会写入到 old sub list 的头部,当此数据再次被查问的时候,即在 old sublist 中命中之后,才会放入 new sublist 的头部。当页从 LRU 列表的 old 局部退出到 new 局部时,称此时产生的操作为 page made young;如果因为 innodb_old_blocks_time 的设置导致页没有从 old 局部挪动到 new 局部的操作,称为 page not made young。
通过命令 SHOW ENGINE INNODB STATUS 能够察看到如下内容:

SHOW ENGINE INNODB STATUS

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 137428992
Dictionary memory allocated 10620037
Buffer pool size   8191  // 示意以后缓冲池中内存页的数量,内存池的大小 =Buffer pool size*16KB
Free buffers       1025  // 示意以后 FREE 列表中页的数量;Database pages     6985  //LRU 列表中页的数量;Old database pages 2558  //
Modified db pages  0     // 显示了脏页的数量;Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4656751, not young 61021911  // 示意是否产生了页在 LRU 队列上的挪动;0.00 youngs/s, 0.00 non-youngs/s   // 示意每秒两类操作产生的次数;Pages read 1036977, created 686192, written 21243071
0.00 reads/s, 0.00 creates/s, 0.28 writes/s
// 示意缓冲池的命中率,失常状况下命中率如果低于 95%,则须要察看是否因为全表扫描引起了 LRU 队列被净化的问题
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not 0 / 1000 
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 6985, unzip_LRU len: 0
I/O sum[17]:cur[0], unzip sum[0]:cur[0]

[]()

  • 页压缩性能

InnoDB 存储引擎从 1.0.x 版本开始反对压缩页的性能,行将本来 16KB 的页压缩为 1KB、2KB、4KB 和 8KB。而因为页的大小产生了变动,LRU 列表也有了些许的扭转。对于非 16KB 的页,是通过 unzip_LRU 列表进行治理的,LRU 中的页蕴含了 unzip_LRU 列表中的页。

对于压缩页的表,每个表的压缩比率可能各不相同。可能存在有的表页大小为 8KB,有的表页大小为 2KB 的状况。unzip_LRU 是怎么从缓冲池中分配内存的呢?

首先,在 unzip_LRU 列表中对不同压缩页大小的页进行别离治理。其次,通过搭档算法进行内存的调配。例如对须要从缓冲池中申请页为 4KB 的大小,其过程如下:

  • 查看 4KB 的 unzip_LRU 列表,查看是否有可用的闲暇页;
  • 若有,则间接应用;
  • 否则,查看 8KB 的 unzip_LRU 列表;
  • 若可能失去闲暇页,将页分成 2 个 4KB 页,寄存到 4KB 的 unzip_LRU 列表;
  • 若不能失去闲暇页,从 LRU 列表中申请一个 16KB 的页,将页分为 1 个 8KB 的页、2 个 4KB 的页,别离寄存到对应的 unzip_LRU 列表中。

2)Free List:

free list 定义是以后没有被应用的内存页,也就是闲暇的内存页,当执行查问操作时,如果页曾经在 buffer pool 中了,则查问到间接返回,如果没有在 buffer pool,并且 free list 不为空,则会从磁盘中查问对应的数据,放入 free list 的某一页中,并且把这页从 free list 中移除,放入 LRU 队列中。Flush List 中的脏页在执行了刷盘操作后会将空间还给 Free List,通过这种形式能够解决空间碎片化

LRU 列表用来治理曾经读取的页,但当数据库刚启动时,LRU 列表是空的,即没有任何的页。这时页都寄存在 Free 列表中。当须要从缓冲池中分页时,首先从 Free 列表中查找是否有可用的闲暇页,若有则将该页从 Free 列表中删除,放入到 LRU 列表中。否则,依据 LRU 算法,淘汰 LRU 列表开端的页,将该内存空间调配给新的页。

从下面能够看出【SHOW ENGINE INNODB STATUS】:

  • Free buffers 示意以后 Free 列表中页的数量,Database pages 示意 LRU 列表中页的数量。可能的状况是 Free buffers 与 Database pages 的数量之和不等于 Buffer pool size。因为缓冲池中的页还可能会被调配给自适应哈希索引、Lock 信息、Change Buffer 等页,而这部分页不须要 LRU 算法进行保护,因而不存在于 LRU 列表中。
  • pages made young 显示了 LRU 列表中页挪动到前端的次数,youngs/s、non-youngs/ s 示意每秒这两类操作的次数。
  • 这里还有一个重要的察看变量——Buffer pool hit rate,示意缓冲池的命中率,通常该值不应该小于 95%。若产生 Buffer pool hit rate 的值小于 95% 这种状况,用户须要察看是否是因为全表扫描引起的 LRU 列表被净化的问题。

3)Flush List:

在 LRU 列表中的页被批改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不统一。这时数据库会通过 CHECKPOINT 机制将脏页刷新回磁盘,而 Flush 列表中的页即为脏页列表。须要留神的是,脏页既存在于 LRU 列表中,也存在于 Flush 列表中。LRU 列表用来治理缓冲池中页的可用性,Flush 列表用来治理将页刷新回磁盘,二者互不影响。

Flush List 中的脏页在执行了刷盘操作后会将空间还给 Free List。

同 LRU 列表一样,Flush 列表也能够通过命令 SHOW ENGINE INNODB STATUS 来查看,后面例子中 Modified db pages 就显示了脏页的数量。

2.Checkpoint 技术

数据库在产生增删查改操作的时候,都是先在 buffer pool 中实现的,为了进步事物操作的效率,buffer pool 中批改之后的数据,并没有立刻写入到磁盘,这有可能会导致内存中数据与磁盘中的数据产生不统一的状况。

假使每次一个页的变动,就将新页的版本刷新到磁盘,那么这个开销是十分大的,若热点数据集中在某几个页中,那么数据库的性能就会变得十分差。同时,如果在从缓冲池将页的的新版本刷新到磁盘时产生了宕机,那么数据就不能复原了,为了防止这种状况,以后事务数据库系统广泛都采纳了 Write Ahead Log 策略,即当事务提交时,先写重做日志,再批改页,当因为产生宕机而导致数据失落时,能够通过重做日志来实现数据的复原。这也是事务 ACID 中 D(Durability 持久性)的要求。

checkpoint 的作用:

  • 缩短数据库的复原工夫
  • 缓冲池不够用时,将脏页刷新到磁盘
  • 重做日志不可用时,刷新脏页
    checkpoint 的分类
  • sharp checkpoint:在敞开数据库的时候,将 buffer pool 中的脏页全副刷新到磁盘中。
  • fuzzy checkpoint:数据库失常运行时,在不同的机会,将局部脏页写入磁盘,进刷新局部脏页到磁盘,也是为了防止一次刷新全副的脏页造成的性能问题。

2.2.2 写缓冲 Change Buffer

在 MySQL5.5 之前,叫插入缓冲(Insert Buffer),只针对 INSERT 做了优化;当初对 DELETE 和 UPDATE 也无效,叫做写缓冲(Change Buffer)。它是一种利用在非惟一一般索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立即将磁盘页加载到缓冲池,而仅仅记录缓冲变更(Buffer Changes),等将来数据被读取时,再将数据合并(Merge)复原到缓冲池中的技术。写缓冲的目标是升高写操作的磁盘 IO,晋升数据库性能。

数据的批改分为两个状况:

1. 当批改的数据页在缓冲池时

上文讲过,通过 LRU、Flush List 的治理,数据库不是间接写入磁盘中,是先将 redo log 写入到磁盘,再通过 checkpoint 机制,将这些“脏数据页”同步地写入磁盘,等于是将这期间产生的 n 次的落盘合并成了一次落盘。因为有 redo log 是落盘的,所以即便数据库解体,缓存中的数据页全副失落,也能够通过 redo log 将这些数据页找回来。

redo log 是数据库用来在解体的时候进行数据恢复的日志,redo log 的写入策略能够通过参数管制,并不一定是每一次写操作之后立刻落盘 redo log,在局部参数下,redo log 可能是每秒集中写入一次,也有可能采取其余落盘策略,然而无论采纳什么形式,redo log 的量都是不会缩小的,与数据写入的覆盖性不同,后一条 redo log 是不会笼罩前一条的,而是增量模式的,因而写 redo log 的操作,等同于是对磁盘某一小块区域的程序 I /O,而不像数据落盘一样的随机 IO 在磁盘里写入,须要磁盘在多个中央挪动磁头。所以 redo log 的落盘是 IO 操作当中耗费较少的一种,比数据间接刷回磁盘要优很多。

2. 当批改的数据页不在缓冲池时,不必写缓冲至多须要上面的三步:

  • 先把须要的索引页,从磁盘加载到缓冲池,一次磁盘随机读操作;
  • 批改缓冲池中的页,一次内存操作;
  • 写入 redo log,一次磁盘程序写操作;

在没有命中缓冲池的时候,至多多产生一次磁盘 IO,对于写多读少的业务场景,性能损耗是很高的

退出写缓冲优化后,流程优化为:

  • 在写缓冲中记录这个操作,一次内存操作;
  • 写入 redo log,一次磁盘程序写操作;

其性能与这个索引页在缓冲池中,相近。

3. 如何保证数据的一致性?

  • 数据库异样奔溃,可能从 redo log 中复原数据;
  • 写缓冲不只是一个内存构造,它也会被定期刷盘到写缓冲零碎表空间;
  • 数据读取时,有另外的流程,将数据合并到缓冲池;

下一次读到该索引页:

  • 载入索引页,缓冲池未命中,这次磁盘 IO 不可避免;
  • 从写缓冲读取相干信息;
  • 复原索引页,放到缓冲池 LRU 和 Flush 里;(在真正被读取时,才会被加载到缓冲池中)

4. 为什么写缓冲优化,仅实用于非惟一一般索引页呢?

InnoDB 里有汇集索引(Clustered Index))和一般索引 (Secondary Index) 两种。如果索引设置了惟一(Unique)属性,在 进行批改操作 时,InnoDB 必须进行唯一性查看。也就是说,索引页即便不在缓冲池,磁盘上的页读取无奈防止(否则怎么校验是否惟一!?)

此时就应该间接把相应的页放入缓冲池再进行批改。

5. 除了数据页被拜访,还有哪些场景会触发刷写缓冲中的数据呢?

  • 有一个后盾线程,会认为数据库闲暇时;
  • 数据库缓冲池不够用时;
  • 数据库失常敞开时;
  • redo log 写满时;(简直不会呈现 redo log 写满,此时整个数据库处于无奈写入的不可用状态)

6. 什么业务场景,适宜开启 InnoDB 的写缓冲机制?

  • 数据库大部分是非惟一索引;
  • 业务是写多读少,或者不是写后立即读取;

[]()

SHOW VARIABLES LIKE 'innodb_change_buffer_max_size'

[]()

2.2.3 自适应散列索引 Adaptive Hash Index

自适应哈希索引用于优化对 BP 数据的查问。InnoDB 存储引擎会监控对二级索引数据的查找,如果察看到建设哈希索引能够带来速度的晋升(最近间断被拜访三次的数据),则建设哈希索引,自适应哈希索引通过缓冲池的 B + 树结构而来,因而建设的速度很快。InnoDB 存储引擎会主动依据拜访的频率和模式来为某些页建设哈希索引。(在高负载零碎下 AHI 容易产生资源的争用,进而引起一些 bug 导致系统受影响甚至解体,故倡议敞开该性能)

2.2.4 重做日志缓冲区 rodo Log Buffer

重做日志缓冲区,当在 MySQL 中对 InnoDB 表进行数据更改时,这些更改首先存储在 InnoDB 日志缓冲区的内存中,而后再写入重做日志(redo logs)的 InnoDB 日志磁盘文件中。他让 MySQL 在解体的时候具备了复原数据的能力,即在数据库发生意外的时候,能够进行数据恢复;

日志缓冲区 log buffer 是内存存储区域,用于保留要写入磁盘上的日志文件的数据。日志缓冲区大小由 innodb_log_buffer_size 变量定义,默认大小为 16MB。

日志缓冲区的内容定期刷新到磁盘。较大的日志缓冲区能够运行大型事务,而无需在事务提交之前将重做日志数据写入磁盘。因而,如果有更新,插入或删除许多行的事务,则减少日志缓冲区的大小能够节俭磁盘 I /O。

这里还波及到一个参数 innodb_flush_log_at_trx_commit:管制如何将日志缓冲区的内容写入并刷新到磁盘, 默认为 1,不倡议批改

  1. 参数为 0 时,示意事务 commit 不立刻把 redo log buffer 里的数据刷入磁盘文件的,而是依附 InnoDB 的主线程每秒(此工夫由参数 innodb_flush_log_at_timeout 管制,默认 1s)执行一次刷新到磁盘。此时可能你提交事务了,后果 mysql 宕机了,而后此时内存里的数据全副失落。
  2. 参数为 1 时,示意事务 commit 后立刻把 redo log buffer 里的数据写入到 os buffer 中,并立刻执行 fsync()操作
  3. 参数为 2 时,示意事务 commit 后立刻把 redo log buffer 里的数据写入到 os buffer 中,但不立刻 fsync()SQL 执行过程

[]()

什么是 binlog

binlog 是一个二进制格局的文件,用于记录用户对数据库更新的 SQL 语句信息,默认状况下,binlog 是二进制格局的,不能应用文本工具的命令进行查看,而是应用 mysqlbinlog 解析查看。

binlog 的性能

当数据写入到数据库的时候,会同时把更新的 SQL 语句写入到相应的 binlog 文件外面,同时在应用 mysqldump 进行备份的时候,只是对一段时间的数据进行了全局备份,然而如果备份后发现数据库服务器产生故障,这个时候就要用到 binlog 日志了。

binlog 和 redolog 的区别:

  1. redo log 是在 InnoDB 存储引擎层产生,而 binlog 是 mysql 数据库的下层产生,而且 binlog 是二进制格局的日志,不仅仅针对 InnoDB 存储引擎。
  2. 两种日志记录的内容模式不同,MySQL 的 binlog 是逻辑日志,而 InnoDB 存储引擎层面的重做日志是物理日志。
  3. 两种日志与记录写入磁盘的工夫点不同,二进制日志只在事物提交实现后进行一次写入,而 redo log 的重做日志在事物的进行过程中一直地被写入。
  4. binlog 不是循环应用,在写满或者重启之后,会生成新的 binlog 文件,然而 redo log 是循环应用的。

3 InnoDB 存储个性

  1. 写缓冲 Change Buffer
  2. 两次写 Double Write

InnoDB 在把 Dirty 脏页写回到表空间之前,在内存中会线拷贝到间断的内存空间 double write buffer 缓冲区,而后再把它们写到一个叫 doublewrite buffer file 的间断磁盘存储区域内,在写 doublewrite buffer file 实现后,InnoDB 才会把 Dirty pages 写到 data file 的适当的地位。如果在写 page 的过程中发生意外解体,InnoDB 在稍后的复原过程中在 doublewrite buffer file 中找到完整的 page 正本用于复原。

[]()

为什么须要双写?

InnoDB 的 Page Size 个别是 16KB,其数据校验也是针对这 16KB 来计算的,将数据写入到磁盘是以 Page 为单位进行操作的。而计算机硬件和操作系统,写文件是以 4KB(512 字节)作为单位的,不能保障 MySQL 数据页面 16KB 的一次性原子写。试想,在某个 Dirty Page flush 的过程中,产生了零碎断电(或者 OS 解体),16K 的数据只有局部被写到磁盘上,只有一部分写是胜利的,这种景象被称为 partial page writes。在呈现磁盘解体的时候,InnoDB 引擎会从共享表空间中的 doublewrite 找到该页的一个正本,将其复制到表空间文件,再利用重做日志,保障 InnoDB 存储引擎操作数据页的可靠性。

为什么不能应用 redo log 解决 partial page writes?

一旦 partial page writes 产生,那么在 InnoDB 复原时就很难堪:redo log 的页大小个别设计为 512 个字节,因而 redo log page 自身不会产生 break page。用 redo log 来解决 partial write 实践上是可行的,不过 innodb 的 redo log 是物理逻辑日志,并不是纯物理日志,因而产生 partial write 后解体复原过程中不能间接利用 redo log,innodb 发现 break page 后实际上会报错。物理逻辑日志不是齐全幂等的,这取决于重做日志类型,对于 INSERT 产生的日志其不是幂等的。

两次写的工作流程

double write 由两局部组成,一部分是 InnoDB 内存中的 double write buffer,大小为 2MB,另一部分是物理磁盘上的 ibdata,零碎表空间中大小为 2MB,共 128 个间断的 Page(2*1024/16KB=128),即两个分区(extend)一个段(segment)。其中 120 个页用于批量刷新脏页(如 LRU LIST 刷新与 FLUSH LIST 刷新这两种刷新策略),另外 8 个页用于单页刷新(Single Page Flush)。做辨别的起因是批量刷脏是后盾线程做的,不影响前台线程。而单页刷新是用户线程发动的,须要尽快的刷脏页并替换出一个闲暇页进去。

InnoDB 刷新(写出)缓冲区中的数据页时采纳的是一次写多个页的形式:

  • 多个页就能够先程序写入到 double write buffer,并调用 fsync()保障这些数据被刷新到 double write 磁盘(ibdata)。
  • 而后数据页调用 fsync()被刷新到理论存储地位;
  • 故障复原时 InnoDB 查看 double write Buffer 与数据页原存储地位的内容,若 double write 页处于页断裂状态,则简略的抛弃;若数据页不统一,则从 double write 页还原。

因为 double write 页落盘与数据页落盘在不同的工夫点,不会呈现 double write 页和数据页同时产生断裂的状况,因而 doublewrite 技术能够解决页断裂问题,进而保障了重做日志能顺利进行,数据库能复原到统一的状态。

3. 自适应哈希索引 Adaptive Hash Index

4 参考资料

掘金小册《MySQL 是怎么运行的:从根儿上了解 MySQL》学习笔记 https://www.jianshu.com/p/3394321c11bf

作者:京东物流 邓钧蔚

起源:京东云开发者社区 自猿其说 Tech

正文完
 0