MySQL原理探索——23 MySQL是怎么保证数据不丢的

news2024/11/23 16:56:35

今天这篇文章,我会继续介绍在业务高峰期临时提升性能的方法。从文章标题“MySQL 是怎么保证数据不丢的”,你就可以看出来,今天我介绍的方法,跟数据的可靠性有关。
在前面文章,我都着重介绍了 WAL 机制(你可以再回顾下第 2 篇、第 9 篇、第 12 篇和第 15 篇文章中的相关内容),得到的结论是:只要 redo log 和 binlog保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复。
有同学问,redo log 的写入流程是怎么样的,如何保证 redo log 真实地写入了磁盘。那么今天,我们就再一起看看 MySQL 写入 binlog 和 redo log 的流程。

binlog 的写入机制

其实,binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
一个事务的 binlog 是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。这就涉及到了 binlog cache 的保存问题。
系统给 binlog cache 分配了一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。
事务提交的时候,执行器把 binlog cache 里的完整事务写入到 binlog 中,并清空binlog cache。状态如图 1 所示。

可以看到,每个线程有自己 binlog cache,但是共用同一份 binlog 文件。
图中的 write,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。

图中的 fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的 IOPS。
write 和 fsync 的时机,是由参数 sync_binlog 控制的:
1. sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
2. sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
3. sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才fsync。
因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。
但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。

redo log 的写入机制

 接下来,我们再说说 redo log 的写入机制。
在第 15 篇文章中,介绍了 redo log buffer。事务在执行过程中,生成的 redo log 是要先写到 redo log buffer 的。
然后就有人问了,redo log buffer 里面的内容,是不是每次生成后都要直接持久化到磁盘呢?
答案是,不需要。
如果事务执行期间 MySQL 发生异常重启,那这部分日志就丢了。由于事务并没有提交,所以这时日志丢了也不会有损失。

那么,另外一个问题是,事务还没提交的时候,redo log buffer 中的部分日志有没有可能被持久化到磁盘呢?
答案是,确实会有。

这个问题,要从 redo log 可能存在的三种状态说起。这三种状态,对应的就是图 2 中的三个颜色块。

这三种状态分别是:
1. 存在 redo log buffer 中,物理上是在 MySQL 进程内存中,就是图中的红色部分;
2. 写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面,也就是图中的黄色部分;
3. 持久化到磁盘,对应的是 hard disk,也就是图中的绿色部分。
日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。
为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值:
1. 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
2. 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
3. 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。

InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。
注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经持久化到磁盘的。
实际上,除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的redo log 写入到磁盘中。
1. 一种是,redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。注意,由于这个事务并没有提交,所以这个写盘动作只是write,而没有调用 fsync,也就是只留在了文件系统的 page cache。
2. 另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘。假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。
这里需要说明的是,我们介绍两阶段提交的时候说过,时序上 redo log 先 prepare, 再写 binlog,最后再把 redo log commit。
如果把 innodb_flush_log_at_trx_commit 设置成 1,那么 redo log 在 prepare 阶段就要持久化一次,因为有一个崩溃恢复逻辑是要依赖于 prepare 的 redo log,再加上binlog 来恢复的。(如果你印象有点儿模糊了,可以再回顾下第 15 篇文章中的相关内
容)。
每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB 就认为 redo log 在 commit的时候就不需要 fsync 了,只会 write 到文件系统的 page cache 中就够了。
通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。 

这时候,你可能有一个疑问,这意味着我从 MySQL 看到的 TPS 是每秒两万的话,每秒就会写四万次磁盘。但是,我用工具测试出来,磁盘能力也就两万左右,怎么能实现两万的TPS?
解释这个问题,就要用到组提交(group commit)机制了。
这里,我需要先介绍日志逻辑序列号(log sequence number,LSN)的概念。LSN是单调递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。
LSN 也会写到 InnoDB 的数据页中,来确保数据页不会被多次执行重复的 redo log。关于 LSN 和 redo log、checkpoint 的关系,我会在后面的文章中详细展开。
如图 3 所示,是三个并发事务 (trx1, trx2, trx3) 在 prepare 阶段,都写完 redo log buffer,持久化到磁盘的过程,对应的 LSN 分别是 50、120 和 160。

从图中可以看到,
1. trx1 是第一个到达的,会被选为这组的 leader;
2. 等 trx1 要开始写盘的时候,这个组里面已经有了三个事务,这时候 LSN 也变成了160;
3. trx1 去写盘的时候,带的就是 LSN=160,因此等 trx1 返回时,所有 LSN 小于等于160 的 redo log,都已经被持久化到磁盘;
4. 这时候 trx2 和 trx3 就可以直接返回了。
所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。
在并发更新场景下,第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的效果就越好。
为了让一次 fsync 带的组员更多,MySQL 有一个很有趣的优化:拖时间。在介绍两阶段提交的时候,我曾经给你画了一个图,现在我把它截过来。

 图中,我把“写 binlog”当成一个动作。但实际上,写 binlog 是分成两步的:
1. 先把 binlog 从 binlog cache 中写到磁盘上的 binlog 文件;
2. 调用 fsync 持久化。
MySQL 为了让组提交的效果更好,把 redo log 做 fsync 的时间拖到了步骤 1 之后。也就是说,上面的图变成了这样:

 这么一来,binlog 也可以组提交了。在执行图 5 中第 4 步把 binlog fsync 到磁盘时,如果有多个事务的 binlog 已经写完了,也是一起持久化的,这样也可以减少 IOPS 的消耗。

 不过通常情况下第 3 步执行得会很快,所以 binlog 的 write 和 fsync 间的间隔时间短,导致能集合到一起持久化的 binlog 比较少,因此 binlog 的组提交的效果通常不如 redo log 的效果那么好。
如果你想提升 binlog 组提交的效果,可以通过设置 binlog_group_commit_sync_delay和 binlog_group_commit_sync_no_delay_count 来实现。
1. binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync;
2. binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用fsync。
这两个条件是或的关系,也就是说只要有一个满足条件就会调用 fsync。
所以,当 binlog_group_commit_sync_delay 设置为 0 的时候,binlog_group_commit_sync_no_delay_count 也无效了。
之前有同学在评论区问到,WAL 机制是减少磁盘写,可是每次提交事务都要写 redo log和 binlog,这磁盘读写次数也没变少呀?
现在你就能理解了,WAL 机制主要得益于两个方面:
1. redo log 和 binlog 都是顺序写,磁盘的顺序写比随机写速度要快;
2. 组提交机制,可以大幅度降低磁盘的 IOPS 消耗。
分析到这里,我们再来回答这个问题:如果你的 MySQL 现在出现了性能瓶颈,而且瓶颈在 IO 上,可以通过哪些方法来提升性能呢?
针对这个问题,可以考虑以下三种方法:
1. 设置 binlog_group_commit_sync_delay 和binlog_group_commit_sync_no_delay_count 参数,减少 binlog 的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
2. 将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000)。这样做的风险是,主机掉电时会丢 binlog 日志。

 3. 将 innodb_flush_log_at_trx_commit 设置为 2。这样做的风险是,主机掉电的时候会丢数据。
我不建议你把 innodb_flush_log_at_trx_commit 设置成 0。因为把这个参数设置成 0,表示 redo log 只保存在内存中,这样的话 MySQL 本身异常重启也会丢数据,风险太大。而 redo log 写到文件系统的 page cache 的速度也是很快的,所以将这个参数设置成 2 跟设置成 0 其实性能差不多,但这样做 MySQL 异常重启时就不会丢数据了,相比之下风险会更小。

小结

在专栏的第 2 篇和第 15 篇文章中,我和你分析了,如果 redo log 和 binlog 是完整的,MySQL 是如何保证 crash-safe 的。今天这篇文章,我着重和你介绍的是 MySQL 是“怎么保证 redo log 和 binlog 是完整的”。
希望这三篇文章串起来的内容,能够让你对 crash-safe 这个概念有更清晰的理解。

问题 1:执行一个 update 语句以后,我再去执行 hexdump 命令直接查看 ibd 文件内容,为什么没有看到数据有改变呢?
回答:这可能是因为 WAL 机制的原因。update 语句执行完成后,InnoDB 只保证写完了redo log、内存,可能还没来得及将数据写到磁盘。
问题 2:为什么 binlog cache 是每个线程自己维护的,而 redo log buffer 是全局共用的?
回答:MySQL 这么设计的主要原因是,binlog 是不能“被打断的”。一个事务的 binlog必须连续写,因此要整个事务完成后,再一起写到文件里。
而 redo log 并没有这个要求,中间有生成的日志可以写到 redo log buffer 中。redolog buffer 中的内容还能“搭便车”,其他事务提交的时候可以被一起写到磁盘中。

问题 3:事务执行期间,还没到提交阶段,如果发生 crash 的话,redo log 肯定丢了,这会不会导致主备不一致呢?
回答:不会。因为这时候 binlog 也还在 binlog cache 里,没发给备库。crash 以后redo log 和 binlog 都没有了,从业务角度看这个事务也没有提交,所以数据是一致的。
问题 4:如果 binlog 写完盘以后发生 crash,这时候还没给客户端答复就重启了。等客户端再重连进来,发现事务已经提交成功了,这是不是 bug?
回答:不是。
你可以设想一下更极端的情况,整个事务都提交成功了,redo log commit 完成了,备库也收到 binlog 并执行了。但是主库和客户端网络断开了,导致事务成功的包返回不回去,这时候客户端也会收到“网络断开”的异常。这种也只能算是事务成功的,不能认为是 bug。
实际上数据库的 crash-safe 保证的是:
1. 如果客户端收到事务成功的消息,事务就一定持久化了;
2. 如果客户端收到事务失败(比如主键冲突、回滚等)的消息,事务就一定失败了;
3. 如果客户端收到“执行异常”的消息,应用需要重连后通过查询当前状态来继续后续的逻辑。此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElementUI plus框架Table表格cell-style属性的使用

官方文档说明: 例:设置单元格文字居中 Object方式: function方式:

安全 --- http报文包详解及burp简单使用

HTTP HTTP(超文本传输协议)是今天所有web应用程序使用的通信协议。最初HTTP只是一个为了获取基本文本的静态资源而开发的简单协议,后来对其进行扩展和利用,使其发展为能够支持如今常见的复杂分布式应用程序。 (1&…

PADS-LAYOUT菜单及工具使用

目录 1菜单栏 1.1文件菜单 1.2编辑菜单 1.3查看菜单 1.4设置菜单 1.5工具菜单 1.6帮助菜单 2工具栏 2.1标准工具栏 2.2绘图工具栏 2.3设计工具栏 2.4尺寸标注工具栏 2.5ECO工具栏 3系统配置 3.1全局选项 3.2设计选项 3.3栅格和捕获选项 3.4显示选项 3.5布线选…

【UnityDOTS 六】预制实例化成Entity

预制实例化成Entity 前言 从Authoring模式中,如何通过预制件来实例化一个对应的Entity对象到DOTS系统中。 一、Authoring模式与Runtime模式 Authoring创作模式:即我们熟悉的方便操作的创建预制的模式 Runtime模式:运行模式,即在…

Three.js教程:网格模型

推荐:将 NSDT场景编辑器 加到你的3D工具链 工具集: NSDT简石数字孪生 网格模型(三角形概念) 本节课给大家演示网格模型Mesh渲染自定义几何体BufferGeometry的顶点坐标,通过这样一个例子帮助大家建立**三角形(面)**的概念 三角形(面) 网格模型Mesh其实…

Spring Boot 中的 XA 事务

Spring Boot 中的 XA 事务 在现代化的应用程序开发中,事务管理是一个重要的话题。事务管理可以确保数据的一致性和完整性,同时也可以避免数据丢失和冲突等问题。在分布式环境中,XA 事务是一种常用的事务管理方式。在本文中,我们将…

基于麦克风阵列模块I2s6路slot数字音频信号的ADSP/STM32F4处理

hezkz17进数字音频系统研究开发交流答疑 1麦克风阵列 2 ADAU1452 DSP 输入接口 3 PCM数据算法处理

uni-app获取节点的相关信息

获取单个节点: selectorQuery.select(selector) 在当前页面下选择第一个匹配选择器 selector 的节点,返回一个 NodesRef 对象实例,可以用于获取节点信息。 selector 说明: selector 类似于 CSS 的选择器,但仅支持下列…

Jenkins 参数化构建

目录 前言: 以下是Jenkins官方的介绍: 首先新建一个自由风格的job,然后勾选“This project is parameterized”,如下图: 勾选参数构建 choice parameterized: 选项: 使用参数:…

linux驱动struct file_operations`中设置`owner`成员变量。

在Linux内核中,struct file_operations结构体是用来定义一个文件操作的集合。该结构体中包含了一组函数指针,这些函数指针指向了与文件操作相关的函数,例如文件的打开、关闭、读写、定位等操作。同时,该结构体中还包含了一些元数据…

Opencv项目实战:00 专栏内容介绍

目录 Opencv项目实战专栏介绍: 01 文字检测OCR 02 角度探测器 03 扫描二维码&条形码 04 全景图片拼接 05 物体检测 06 文档扫描仪 07 人脸识别和考勤系统 08 Yolov3更高精度的检测物体 09 物体尺寸测量 10 面部特征提取及添加滤镜 11 使用Opencv高亮…

接近完美的监控系统—普罗米修斯

内容 隐藏 1.怎么采集监控数据? 2.采集的数据结构与指标类型 2.1 数据结构 2.2 指标类型 3.实例概念 4.数据可视化 5.应用前景 普罗米修斯(Prometheus)是一个SoundCloud公司开源的监控和告警系统。当年,由于SoundCloud公司生产了太多的服务&#…

ant vue 自定义首页菜单实现

如图,我把 views/dataScreening/DataScreening 页面,设置成首页,登录以后自动跳转到这个页面 实现代码: 文件:/src/config/router.config.js import { UserLayout, TabLayout, RouteView, BlankLayout, PageView } from @/components/layouts/*** 走菜单,走权限控制* @t…

【深度学习】AIGC ,ControlNet 论文,原理,训练,部署,实战,教程

论文:https://arxiv.53yu.com/pdf/2302.05543 代码:https://github.com/lllyasviel/ControlNet 得分几个博客完成这个事情的记录了,此篇是第一篇,摘录了一些论文内容。ControlNet 的原理极为朴实无华(对每个block添加…

c++ connect函数连接失败

bool Connect() {//初始化网络WSADATA wsadata;WSAStartup(MAKEWORD(2, 2), &wsadata);if (0 ! WSAStartup(MAKEWORD(2, 2), &wsadata))return false;if (LOBYTE(wsadata.wVersion) ! 2 ||HIBYTE(wsadata.wVersion) ! 2) {printf("请求协议版本失败!\n");ret…

Ghatgpt正式登录苹果手机应用商城,并支持Siri和快捷指令

根据最新信息,OpenAI 发布的 ChatGPT 官方 iOS 应用程序迎来了重大更新。该应用程序已经在上个月登陆了美国、英国、法国、德国和韩国等 App Store,并且成为该市场上最受欢迎的免费应用程序。 作为生产力类应用的领导者,该应用程序完全免费&a…

MYSQL执行一条SELECT语句的具体流程

昨天CSDN突然抽风 我一个ctrlz把整篇文章给撤掉了还不能复原 直接心态崩了不想写了 不过这部分果然还是很重要,还是写出来吧 流程图 这里面总共有两层结构Server层 储存引擎 Server 层负责建立连接、分析和执行 SQL。MySQL 大多数的核心功能模块都在这实现,主要包…

PF-Net点云补全解读

论文链接:https://arxiv.org/pdf/2003.00410.pdfhttps://arxiv.org/pdf/2003.00410.pdf PF-Net是一个点云补全模型,整体网络为生成对抗网络(Gan)构成,G网络负责生成缺失的数据,D网络负责鉴别G网络生…

为什么Unity使用AssetBundle热更的时候要剔除掉.mainfest文件

1)为什么Unity使用AssetBundle热更的时候要剔除掉.mainfest文件 ​2)Addressable热更,断点续传问题 3)在线性空间,使用后处理解决UI混合问题 这是第341篇UWA技术知识分享的推送,精选了UWA社区的热门话题&am…

计算机毕业论文内容参考|基于Python的手机订餐系统的设计与实现

文章目录 导文摘要前言绪论1课题背景2国内外现状与趋势3课题内容相关技术与方法介绍系统分析系统设计系统实现系统测试总结与展望1本文总结2后续工作展望导文 计算机毕业论文内容参考|基于Python的手机订餐系统的设计与实现 摘要 本文基于Python语言,设计并实现了一个手机订餐…