MySQL 表数据多久刷一次盘?

MySQL 表数据多久刷一次盘?,第1张

前言

事情是这样的,在某乎的邀请回答中看到了这个问题:

然后当时我没多想就啪一下写下来这样的答案:

这个其实要通过 MySQL 后台线程来刷的,在 Buffer Pool 中被修改的过的 Page(页)都会被标记成脏页,放到一个链表(Flush 链表)里。

然后 MySQL 通过启动后台线程,在满足条件时将 Flush 链表中的脏页刷入磁盘

满足的条件是:  脏页的数量  达到了 Buffer Pool 中页数量的 **10% ,当然 10% 这个值是可变的,通过配置项 innodb_max_dirty_pages_pct_lwm 来配置的,其默认值为 10%,并且这个值也必须小于另一个配置 innodb_max_dirty_pages_pct 的值( 90%**)。

至于启多少个线程,则是由另一个变量 innodb_page_cleaners 来控制的,默认是 4.一般都不会去改这个。

大概就是这样。

但是,后面有兄弟在下面说:”我唔知你喺讲乜“。

后面我回过头去看,当时写的确实有点过于跳跃了,过一段时间再去看有些不是那么连贯,打算重新把这个事情讲清楚。

1. 表数据

我们这篇「短文」讨论的是【MySQL 表数据多久刷一次盘】,从这个标题中我们可以分裂成两个问题:

刷什么到磁盘

什么时候刷到磁盘

我们分开来讨论。

2. 刷什么到磁盘

看上去有点废话,肯定是将数据刷入磁盘。所以我们更多需要讨论的是【数据是以什么样的形式被刷入磁盘】。

答案是页

对页不太了解的可以去看看之前写的文章:  MySQL 页完全指南——浅入深出页的原理

在 Inn

oDB 中,  页  是数据被管理的最小的单位。当使用 InnoDB 作为存储引擎的 MySQL 运行时,表中一行一行的数据会被组织在一页一页当中,放在 Buffer Pool 中。

Buffer Pool 可以看另一篇:  详细了解 InnoDB 内存结构及其原理

这一页一页的数据,就存放在 Buffer Pool 中。当 DML 语句(也就是 CRUD)语句对表数据进行了变更之后,数据所在的那一页就会被标记为  脏页  。

InnoDB 会用一个叫【Flush 链表】的结构来存放这些脏页,凡是被放进该链表的页都代表需要  刷入磁盘  ,但不是立即刷入。

和 InnoDB 的其他日志例如 Redo Log 一样,这些日志都是有自己的  刷盘策略  。例如 Redo Log,其刷盘策略可以用下图来表示:

参数为0,Redo Log 会每隔一秒,写入并且刷入磁盘。

参数为1,Redo Log 会在每次事务提交之后刷入磁盘

参数为2,每次事务提交,都会写到 OS 缓存中去,然后每隔一秒将 OS 缓存中的数据刷入磁盘

而 Flush 链表也有自己的策略。

3. 什么时候刷到磁盘

接上节,策略就是:  脏页的数量  达到了 Buffer Pool 中页数量的 **10%**,就会触发将 Flush 链表中的脏页刷入磁盘。举个例子,Buffer Pool 中总共有 100 张页,脏页如果达到了 10 页就会启动后台线程,触发刷盘。

当然,【10%】这个数值是可配置的,通过 MySQL 配置项 innodb_max_dirty_pages_pct_lwm 可以进行调整,只是默认值是 10%。但是我们调整的值不能超过某个最大值,这个最大值由 innodb_max_dirty_pages_pct 来指定,默认值为 90%。

换句话说,默认情况,刷盘阈值是 10%,如果需要自定义,则最大值不能超过 90%。

4. 谁来负责刷盘

上个小节已经说过了,会启动线程来专门做这个事情,这个没有什么疑问。我们需要关注的是会启动多少个线程来做这个事。

答案是 4 个,我们也可以通过配置项 innodb_page_cleaners 来更改,但一般都不会去改这个值。

关于这个点就聊到这。

InnoDB在处理更新语句时,先写内存再写redo log,并不会立即将数据页的更新落地到磁盘(WAL机制),这就会产生升内存数据页和磁盘数据页的数据不一致的情况,这种数据不一致的数据页称为 脏页 ,当脏页写入到磁盘(这个 *** 作称为flush)后,数据一致后称为干净页。

第3种是系统空闲不会有性能问题,第4种是要关闭了不考虑性能问题。第1和2的情况flush脏页会产生系统性能问题。

此时整个系统不能再更新了,更新数会降为0,所以这种情况要尽量避免。

InnoDB缓冲池(buffer pool)中的内存页有三种状态:

当一个SQL语句要淘汰的脏页数量太多,会导致语句执行的响应时间显著边长。

InnoDB为了避免出现上述两种情况,需要有控制脏页比例的策略,控制的主要参考因素就是:脏页比例和redo log写盘速度。

需要告诉InnoDB的磁盘读写能力(IOPS)让引擎全力flush脏页,磁盘的IOPS可以通过fio工具测试。

如果 innodb_io_capacity 参数设置的不合理,比如远远低于磁盘实际的IOPS,InnoDB会认为IO性能低,刷脏页速度会很慢,甚至低于脏页的生成速度,导致脏页累计影响查询和更新性能。

为了兼顾正常的业务请求,InnoDB引擎控制按照磁盘IOPS的百分比来刷脏页,具体流程如下:

脏页比例计算:

Innodb_buffer_pool_pages_dirty/Innodb_buffer_pool_pages_total

SQL语句如下:

在准备flush一个脏页时,如果相邻的数据页也是脏页,会把这个脏页一起flush,而且对这个新的脏页还可能有相邻的脏页导致连锁flush。

InnoDB使用 innodb_flush_neighbors 参数控制这个行为,值为1会产生上述连锁flush的情况,值为0则不会找相邻页。

找相邻页flush的机制虽然可以减少很多随机IO,但会增加一次flush时间,导致flush时的SQL语句执行时间变慢。

现在基本都使用的SSD这种IOPS比较高的硬盘,建议将 innodb_flush_neighbors 参数设为0,提高flush的速度。

flush会占用IO资源影响了正在执行的SQL语句,本来正常情况下执行很快的一条语句,突然耗时大大增加,造成业务抖动。要尽量避免这种情况,需要合理的设置 innodb_io_capacity 的值,并且多关注脏页比例,不要让脏页比例经常接近75%。

【极客时间】 MySQL实战45讲:第12节

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

不论是脏页还是干净页,都在内存中。

平时很快的更新 *** 作,都是在写内存和日志。

一条 SQL 语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢。

那这时候可能就是在刷脏页到磁盘中了~ flush

(1) InnoDB的redo log写满了。这时候系统会停止所有的更新 *** 作,然后让日志可以继续写。

把这部分数据日志都flush到磁盘上面。

(2) 也可能是系统内存不足,需要新的内存页,那么就淘汰一些内存页,空出来的给别的数据页使用。

先把脏页写到磁盘。

PS:使用内存是为了效率更好,

因为如果内存存在数据页,那么数据就一定正确,直接返回;

如果内存没有数据,才需要去磁盘中取,读入到内存,返回;

(3) MySQL 认为系统“空闲”的时候,反正闲着也是闲着hh

反正有机会就刷点数据

(4)MySQL 正常关闭。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。

3.1 如果是redo log写满了

尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。更新数为 0。

3.2 内存不够用了

常态,很正常。

3.3 buffer pool

因为innodb用的是buffer pool 管理内存,缓冲池中的内存页有三种状态:第一种是还没有使用的;第二种是使用了并且是干净页;第三种是使用了并且是脏页。

Innodb 的内存策略是尽量使用内存。

我觉得知道一下就好,这个脏页刷的快不快跟磁盘的能力有关。

可以通过innodb_io_capacity 这个参数设置磁盘能力。

InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。

平时要多关注脏页比例,不要让它经常接近 75%。

INNODB刷脏页,如果发现旁边也是脏页,那么会连带着一起刷掉。

所以可能会很慢,如果你的查询正好要先flush一个脏页的话。

在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。

找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百。

但是SSD 的IO很高,所以可以不用非要有刷写邻居的 *** 作,可以加快响应。

在 MySQL 8.0 中,innodb_flush_neighbors 参数的默认值已经是 0 了。

对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页

也就是如果内存中比redolog的头部小,那么就是干净页

每个数据页有LSN,重做日志有LSN,checkpoint有LSN。

占用8字节,LSN主要用于发生crash时对数据进行recovery,LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。

LSN不仅只存在于重做日志中,在每个数据页头部也会有对应的LSN号,该LSN记录当前页最后一次修改的LSN号,用于在recovery时对比重做日志LSN号决定是否对该页进行恢复数据。前面说的checkpoint也是有LSN号记录的,LSN号串联起一个事务开始到恢复的过程。

感谢: https://www.cnblogs.com/drizzle-xu/p/9713378.html

我感觉就是可以理解为是一个long类型的数字,可以根据这个来比较要不要刷写数据,以及是不是干净页面,在恢复数据要拿这个进行比较。

缓存区域,缓存数据和索引在内存中。

innodb使用了一些链表。

lru链表:用来存储内存中的缓存数据。

free链表:用来存放所有的空闲页,每次需要数据页存储数据时,就首先检测free中有没有空闲的页来分配。

flush链表:在内存中被修改但还没有刷新到磁盘的数据页列表,就是所谓的脏页列表,内存中的数据跟对应的磁盘上的数据不一致,属于该列表的页面同样存在于lru列表中,但反之未必。

将脏页flush到磁盘上是直接将脏页数据覆盖到对应磁盘上的数据


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/7486739.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-06
下一篇 2023-04-06

发表评论

登录后才能评论

评论列表(0条)

保存