Mysql架构<五> innoDB内存引擎架构 之磁盘文件层面的存储结构

储存结构-表的结构

在这里插入图片描述

表空间由各个段(Segment)组成,创建的段类型分为数据段、索引段、回滚段RollBack segment等。由于 InnoDB 采用聚簇索引B+ 树的结构存储数据,所以事实上数据页二级索引页仅仅只是 B+ 树的叶子节点,因此数据段称为 Leaf node segment,索引段其实指的是 B+ 树的非叶子节点,称为 Non-Leaf nodesegment。一个段会包含多个区,至少会有一个区,段扩展的最小单位是区。

数据段称为 Leaf node segment
索引段称为 Non-Leaf node segment

区【Extent】

区(Extend)是由连续的页组成的空间,大小固定为 1MB,由于默认页大小为 16K,因此一个区默认存储 64 个连续的页。如果页大小调整为 4K,则 256 个连续页组成一个区。为了保证页的连续性,InnoDB 存储引擎会一次从磁盘申请 4 ~ 5 个区。

对于新创建的独立表空间,其大小默认是 96K 而不是 1MB,
这是因为在每个段开始都会使用 32个页大小的碎片页Fragement page来存放数据,
当碎片页写满了在进行 Extend 的申请,以节省磁盘容量的开销。
页【Page】

页(Page)是 InnoDB 的基本存储单位,每个页大小默认为 16K,从 InnoDB1.2.x 版本开始,可通过设置innodb_page_size 修改为 4K、8K、16K。InnoDB 首次加载后便无法更改。

# 查看MySQL页大小
show global variables like 'innodb_page_size';

在这里插入图片描述

索引树上一个节点就是一个页,MySQL规定一个页上最少存储2个数据项。如果向一个页插入数据时,
这个页已将满了,就会从区中分配一个新页。如果向索引树叶子节点中间的一个页中插入数据,如果这个页是满的,就会发生页分裂。
操作系统管理磁盘的最小单位也是页,是操作系统读写磁盘最小单位,Linux中页一般是4K,可以通过命令查看。

# 默认 4096 4K
getconf PAGE_SIZE

所以InnoDB从磁盘中读取一个数据页时,操作系统会分4次从磁盘文件中读取数据到内存。写入也是一样的,需要分4次从内存写入到磁盘中。

行【Row】

InnoDB的数据是以行为单位存储的,1个页中包含多个行。在MySQL5.7中,InnoDB提供了4种行格式:CompactRedundantDynamicCompressed行格式,Dynamic为MySQL5.7默认的行格式。
InnoDB行格式官网:

行与事务有关!

创建表时可以指定行格式:

CREATE TABLE t1 (c1 INT) ROW_FORMAT=DYNAMIC;
#修改表的行格式
ALTER TABLE tablename ROW_FORMAT=行格式名称;
#修改默认行格式
SET GLOBAL innodb_default_row_format=DYNAMIC;
#查看表行格式
SHOW TABLE STATUS LIKE 't1';

内存数据落盘

在这里插入图片描述
在数据库中进行读取操作,将从磁盘中读到的页放在缓冲池中,下次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时都触发,而是通过一种称为CheckPoint的机制刷新回磁盘。

内存数据落盘要考虑的核心问题:高性能写入数据,同时要保证数据的绝对安全性!

数据库灵魂三问:

写入性能如何保证
分散写入操作放在内存中,通过定期 批量写入磁盘的方式提高写入效率减少磁盘 IO。

理解:能在内存处理交互的就在内存进行。

如何持久化?(怎么写入硬盘)
也就是修改后的数据如何到磁盘中去。内存里缓冲池中的数据页要完成持久化通过两个流程来完成。

  • 脏页落盘
    检查点机制CheckPoint
    双写机制Double Write
  • 预写redo log落盘

数据安全性怎么保证

  • 记录操作日志:
    Force Log at Commit机制:将日志写到磁盘上去
    Write Ahead Log(WAL)策略:写日志
  • 脏页落盘
    检查点机制CheckPoint
    双写机制Double Write
脏页落盘

在这里插入图片描述

什么是脏页

对于数据库中页的修改操作,则首先修改在缓冲池中的页,缓冲池中的页与磁盘中的页数据不一致,所以称缓冲池中的页为脏页。然后再以一定的频率将脏页刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称CheckPoint的机制刷新回磁盘。

为什么不是每次更新直接写入磁盘呢
  • 如果每次一个页发生变化就进行落盘,每次落盘一个页,必然伴随着4次IO操作,那么性能开销会非常大。而且这个开销是随着写入操作的增加指数级增长的!
  • 如果数据长期在内存中保存,那么数据就存在安全性风险!
  • InnoDB采用了Write Ahead Log(WAL)策略和Force Log at Commit机制实现事务级别下数据的持久性。
    Force Log at Commit机制:要求当一个事务提交时,所有产生的日志都必须刷新到磁盘上。如果日志刷新成功后,缓冲池中的数据刷新到磁盘前数据库发生了宕机,那么重启时,数据库可以从日志中恢复数据。这样可以保证数据的安全性。
    Write Ahead Log(WAL)策略:要求数据的变更写入到磁盘前,首先必须将内存中的日志写入到磁盘;InnoDB 的 WAL(Write Ahead Log)技术的产物就是 redo log,对于写操作,永远都是日志先行,先写入 redo log 确保一致性之后,再对修改数据进行落盘。
  • 说白了,保证数据的持久性与安全性我们采用记录日志的方式,那么也就是说,日志安全了,数据就安全了。
怎么确保日志就能安全的写入系统呢

为了确保每次日志都写入到redo日志文件,在每次将redo日志缓冲写入redo日志后,调用一次fsync操作,将缓冲文件从文件系统缓存中真正写入磁盘。

这样做不就等同于数据直接写入磁盘吗

日志是顺序写入,而数据是随机写入。顺序写入效率更高
日志也不是改一条写一条,而是采用redo 日志落盘策略来兼顾安全性与性能!
可以通过 innodb_flush_log_at_trx_commit 来控制redo日志刷新到磁盘的策略。接下来我们看redo日志如何落盘

redo日志落盘

在这里插入图片描述
Log Buffer写入磁盘的时机由参数 innodb_flush_log_at_trx_commit 控制,默认1表示事务提交后立即落盘。 InnoDB的该属性可以控制每次事务提交时InnoDB的行为。是InnoDB性能调优的一个基础参
数,涉及InnoDB的写入性能和数据安全性。

# 查看写入时机参数配置
show VARIABLES like 'innodb_flush_log_at_trx_commit';
innodb_flush_log_at_trx_commit 配置详解:
`为0时`:事务提交时,不会立即把 log buffer里的数据写入到redo log日志文件的。
而是等待主线程每秒写入一次。
如果MySQL崩溃或者服务器宕机,此时内存里的数据会全部丢失,最多会丢失1秒的事务。
写入效率最高,但是数据安全最低;

`为1时`:每次事务提交时,会将数据将从log buffer写入redo日志文件与文件系统缓存,并同时fsync刷新到磁盘中。
系统默认配置为1,MySQL崩溃已经提交的事务不会丢失,要完全符合ACID必须使用默认设置1。
写入效率最低,但是数据安全最高;

`为2时`:事务提交时,也会将数据写入redo日志文件与文件系统缓存,但是不会调用fsync,而是让操作系统自己去判断何时将缓存写入磁盘。
事务提交都会将数据刷新到操作系统缓冲区,可以认为是已经持久化到磁盘,但没有真正意义上持久化到磁盘。
如果MySQL崩溃已经提交的事务不会丢失。但是如果服务器宕机或者意外断电,操作系统缓存内的数据会丢失,所以最多丢失1秒的事务。

#Tips. 用户程序写入数据到磁盘文件时,需要调用操作系统的接口,操作系统本身是有缓冲区的,之后依赖操作系统机制不时的将缓存中刷新到磁盘文件中。用户程序可以执行fsync操作将操作系统缓冲区的数据刷入到磁盘文件中。

只有设置为1是最安全但是性能消耗的方式,可以真正地保证事务的持久性,但是由于MySQL执行刷新操作 fsync() 是阻塞的,直到完成后才会返回,我们知道写磁盘的速度是很慢的,因此 MySQL 的性能会明显地下降。0的性能最好的模式,但安全性却很差。2是一个折中的选择。在配置时,需综合安全性和性能等因素。

CheckPoint检查点机制
什么是CheckPoint?

讲到目前为止,数据还没有进入到磁盘!
思考一下这个场景:
如果redo日志可以无限地增大,同时缓冲池也足够大,那么是不需要将缓冲池中页的新版本数据页刷新回磁盘。当发生宕机时,完全可以通过redo日志来恢复整个数据库系统中的数据到宕机发生的时刻。

这么做的前提条件:

  • 缓冲池可以缓存数据库中所有数据;
  • redo日志可以无限增大

显然这是不可能的,因此Checkpoint(检查点)技术就诞生了,目的是解决以下几个问题:

  1. 缩短数据库的恢复时间:当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。数据库只需对Checkpoint后的redo日志进行恢复,这样就大大缩短了恢复的时间。
  2. 缓冲池不够用时,将脏页刷新到磁盘:当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。
  3. redo日志不可用时,刷新脏页:当redo日志出现不可用时,Checkpoint将缓冲池中的页至少刷新到当前redo日志的位置。
    3.1. 数据库系统对redo日志的设计都是循环使用的,并不是让其无限增大的。如果当数据库宕机恢复操作时,不需要redo日志中的部分redo日志,这部分就可以被覆盖重用。
    3.2. InnoDB通过LSN(Log Sequence Number)来标记日志刷新的版本。LSN是8字节的数字,每个页有LSN,redo日志中也有LSN,Checkpoint也有LSN。
    可以通过命令 SHOW ENGINE INNODB STATUS 来观察:
1 mysql> show engine innodb status;

Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘,不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。

Checkpoint分类

在InnoDB存储引擎内部,有两种Checkpoint,分别为:Sharp CheckpointFuzzy Checkpoint

  • sharp checkpoint:在关闭数据库的时候,将buffer pool中的脏页全部刷新到磁盘中。
  • fuzzy checkpoint:数据库正常运行时,在不同的时机,将部分脏页写入磁盘。仅刷新部分脏页到磁盘,也是为了避免一次刷新全部的脏页造成的性能问题。

Fuzzy Checkpoint:默认方式,只刷新一部分脏页,不是刷新所有脏页;主要有以下几种情况:

  1. Master Thread Checkpoint;
  2. FLUSH_LRU_LIST Checkpoint;
  3. Async/Sync Flush Checkpoint;
  4. Dirty Page too much Checkpoint

1、Master Thread Checkpoint :在Master Thread中,会以每秒或者每10秒一次的频率,将部分脏页从内存中刷新到磁盘,这个过程是异步的。正常的用户线程对数据的操作不会被阻塞。
2、FLUSH_LRU_LIST Checkpoint:缓冲池不够用时,根据LRU算法会淘汰掉最近最少使用的页,如果该页是脏页的话,会强制执行CheckPoint,将该脏页刷回磁盘(由Page Cleaner Thread完成);
3、Async/Sync Flush Checkpoint:重做日志不可用的情况,需要强制从脏页列表中选取一些脏页刷新磁盘到缓存(由Page Cleaner Thread完成)。由于磁盘是一种相对较慢的存储设备,内存与磁盘的
交互是一个相对较慢的过程。innodb_log_file_size定义的是一个相对较大的值,正常情况下,由前面两种checkpoint刷新脏页到磁盘,在前面两种checkpoint刷新脏页到磁盘之后,脏页对应的redo log空间随即释放,一般不会发生Async/Sync Flush checkpoint。
4、Dirty Page too much:即脏页数量太多,导致强制进行Checkpoint。由参数innodb_max_dirty_pages_pt 来控制,默认75(即75%)。当脏页数量占据75%缓冲池时,刷新一部
分脏页到磁盘。(由Page Cleaner Thread完成)

show variables like 'innodb_max_dirty_pages_pct';
Double Write双写
脏页落盘出现的问题

我们知道脏页会在某些场景下进行刷盘,将缓冲池内的脏页数据落地到磁盘。因为存储引擎缓冲池内的数据页大小默认为16KB,而文件系统一页大小为4KB,所以在进行刷盘操作时,就有可能发生如下场景
在这里插入图片描述
如图所示,数据库准备刷新脏页时,需要四次IO才能将16KB的数据页刷入磁盘。但当执行完第二次IO时,数据库发生意外宕机,导致此时才刷了2个文件系统里的页,这种情况被称为写失效(partial pagewrite)。此时重启后,磁盘上就是不完整的数据页,就算使用redo log也是无法进行恢复的。
注意:redo log无法恢复数据页损坏的问题,恢复必须是数据页正常并且redo log正常。这里要知道一点,redo log中记录的是对页的物理操作,如偏移量600,写’xxxx’记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的

该怎么解决这个问题?

在数据库进行脏页刷新时,如果此时宕机,有可能会导致磁盘数据页损坏,丢失我们重要的数据。此时就算重做日志也是无法进行恢复的,因为重做日志记录的是对页的物理修改。
其实就是在重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本来还原该页,再进行重做,这就是double write
在这里插入图片描述
如图,其实Double Write 分为了两个组成部分:

  • 内存中的double write buffer
  • 物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB

可以看出,有了Double write后的脏页刷新流程就是多了几步操作:

  1. 在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的Double write buffer
  2. 通过Double write buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘。这样做可以避免缓冲写带来的问题
Double write崩溃恢复

在这里插入图片描述
如图,如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的Double write中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。
下面显示了一个由Double write进行恢复的情况:

090923 12:36:32 mysqld restarted
090923 12:26:33 InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Crash recovery may have faild for some .ibd files!
InnoDB: Restoring possible half-written data pages from the doublewrite.
InnoDB: buffer...
小结
  1. 当commit 一个修改语句时,如果redo log有空闲区域,直接写redo log,如果redo log没有空闲区域,那么需要把被覆盖的redo log对应的数据页刷新到data file 中,最后改pool buffer中的记录
  2. innodb的redo log 不会记录完整的一页数据,因为这样日志太大,它只会记录那次(sequence)如何操作了(update,insert)哪页(page)的哪行(row)
  3. 因为数据库使用的页(page,默认16KB)大小和操作系统对磁盘的操作页(page,默认4KB)不一样,当提交了一个页需要刷新到磁盘,会有多次IO, 此时刷了前面的8k时异常发生宕机。在系统恢复正常后,如果没有double write机制,此时数据库磁盘内的数据页已损坏,无法使用redolog进行恢复。
  4. 如果有double write buffer,会检查double writer的数据的完整性,如果不完整直接丢弃doublewrite buffer内容,重新执行那条redo log,如果double write buffer的数据是完整的,用doublewriter buffer的数据更新该数据页,跳过该redo log。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四库全书的酷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值