MySQL是如何保证不丢失数据的呢?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
日志服务 SLS,月写入数据量 50GB 1个月
简介: 大家好,我是Leo,从事Java后端开发。之前的文章大概介绍了WAL机制,如果不太清楚的小伙伴下面第一部分我们可以再回顾一下。今天这里主要介绍一下WAL的安全性这一块。

介绍


大家好,我是Leo,从事Java后端开发。之前的文章大概介绍了WAL机制,如果不太清楚的小伙伴下面第一部分我们可以再回顾一下。今天这里主要介绍一下WAL的安全性这一块。


写作思路


根据读者与朋友的反馈,所以从这篇文章开始我会加一个写作的思路。可以先让读者了解到学完这一篇下来之后能收获到哪些知识,以防看了半个小时最后啥也没学到,这样的确挺气人的。

image.png


步入正题


binlog  写入机制

binlog写入日志这个是比较简单的。提到binlog,必然提到binlog cache。那么binlog cache是什么?

我们有必要了解一些前提知识,再学习binlog。会的人可以跳过这段,照顾一下不懂的朋友。

binlog cache是一个二进制日志文件的缓冲区,他是由一个参数 binlog_cache_size 控制大小的缓冲区。

一个事务在执行是时候是不允许被拆开的,因此无论事务多大,都是要一次性保存执行的。那么这个就涉及到了binlog cache 的保存问题。如果所占的内存大小超过了这个binlog_cache_size 参数的设定。就会采用暂存到磁盘。事务在提交的时候,会先把binlog cache里的数据写入到binlog中,并清空binlog cache数据。

如下图,我们可以从图中了解一下。

image.png

每个binlog_cache是由单独的一个线程享有的。也就是说多个线程带着多个binlog_cache去进入write操作的时候是写入到一个binlog 文件的。效率是非常快的,因为并没有涉及到磁盘IO的开销。

当进行到了fsync的时候,才是将数据持久化到磁盘操作。这个时候才会占用磁盘IO,也就是我们常说的IOPS。

我们我们可以深入讨论一个问题,什么时候进行write,什么时候需要fsync操作呢?


下面我们介绍一下write与fsync的时机。

int sync_binlog=0;
if(sync_binlog==0){
    每次提交事务都只 write,不 fsync
}
if(sync_binlog==1){
    每次提交事务都会执行 fsync
}
if(sync_binlog>1){
    每次提交事务都 write,但累积 N 个事务后才 fsync。
}


因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。

但是,将 sync_binlog 设置为 N,对应的风险是:如果主机发生异常重启,会丢失最近 N 个事务的 binlog 日志。

扩展:上面我们介绍了通过binlog_cahe_size 控制一个参数的大小。进行磁盘与内存缓存区的抉择。那么还有哪些参数是控制这一类问题的。你能想到几个?

下面我们延伸介绍一下sort_buffer_size ,从前几篇文章中引用的一段。详细的可以去order by那篇文章回顾一下。

image.png

binlog 写入机制大概就是这样了,以后技术的进步优化会再完善的。有不对的地方也可以加以指出!互相探讨学习!

redo log  写入机制

还是老样子吧。我感觉MySQL的设计很多地方都很相像。比如change buffer,binlog cache都是一些缓存区,内存临时存放的地方。那么redolog的缓冲区是什么呢? redo log buffer

我们可以先介绍一下 redo log buffer

redo log buffer 要做的是一个事务在插入一条数据的时候,需要先写入日志。但是又不能在还没有提交事务的时候直接写到redo log文件中。这个日志的临时存放处就是redo log buffer。真正在写入redo log文件的过程是在commit这一步完成的。

这里是开启一个事务下执行的。如果我们只执行一条insert 语句它又是如何实现的呢?

单独执行一个更新语句的时候,InnoDB 会自己启动一个事务,在语句执行完成的时候提交。过程跟上面是一样的,只不过是“压缩”到了一个语句里面完成。


上面我们说了 buffer是他的一个临时缓存区,那么 是不是所有buffe都要持久化到磁盘呢?

不需要 如果事务执行期间 MySQL 发生异常重启,那这部分日志就丢了。由于事务并没有提交,所以这时日志丢了也不会有损失。

那么事务还没提交的时候,redo log buffer 中的部分日志有没有可能被持久化到磁盘呢?

确实会有。这个问题,要从 redo log 可能存在的三种状态说起 如下图

image.png

  • redo log buffer:物理上这是MySQL的进程内存
  • FS page cache:写入到磁盘,但是还没有进行持久化。物理上是page cache文件系统。
  • hard disk,这个就是持久化到磁盘了。

图中的红色区域是内存操作,不涉及到磁盘IO。所以性能的非常快的。write也是非常快的,也就是图中的黄色部分。fsync的速度就慢了很多。因为持久化到磁盘。


MySQL没那么简单,这里的redo log是有一个写入策略的。我们下面介绍一下策略与案例。

写入策略这个就涉及到了一个参数innodb_flush_log_at_trx_commit 这个参数控制写入redo log,写入到磁盘的走向。为了提供更好的性能保障!

  • 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;
  • 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;
  • 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。

InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。


下面我们可以细想一下,redo log buffer帮redo log解决了那么大 的一个难题。那么redo log buffer又是绝对的安全或者说绝对的性能吗?

如果我事务正在执行,还没有提交。那么MySQL肯定会把数据从redo log写入到redo log buffer!上面我们介绍了每隔一秒会把redo log buffer里的数据做一边写入。那么有没有可能事务没执行完,可能已经写盘了呢?

答案是肯定的。下面我们介绍一下redo log buffer的刷新策略

控制这个策略的参数是innodb_log_buffer_size

  • redo log buffer 占用的空间即将达到 innodb_log_buffer_size 一半的时候,后台线程会主动写盘。

(注意,由于这个事务并没有提交,所以这个写盘动作只是 write,而没有调用 fsync,也就是只留在了文件系统的 page cache。)

  • 另一种是,并行的事务提交的时候,顺带将这个事务的 redo log buffer 持久化到磁盘

(假设一个事务 A 执行到一半,已经写了一些 redo log 到 buffer 中,这时候有另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么按照这个参数的逻辑,事务 B 要把 redo log buffer 里的日志全部持久化到磁盘。这时候,就会带上事务 A 在 redo log buffer 里的日志一起持久化到磁盘。)

image.png

说明: 如上图所示, 在做两阶段提交的时候会有一个prepare。先写入redolog处于prepare阶段。再写binlog。最后再commit。

假设: 如果innodb_log_buffer_size 设置成1,那么redo log在prepare就要持久化一次,因为有一个崩溃恢复的逻辑还要依赖prepare的redo log,再加上binlog来恢复。

每秒一次后台轮询刷盘,再加上崩溃恢复这个逻辑,InnoDB 就认为 redo log 在 commit 的时候就不需要 fsync 了,只会 write 到文件系统的 page cache 中就够了。

通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。


还远远不止这些,有些时候我们听说大厂的TPS每秒两万。也就是说每秒就会写四万次磁盘。但是,我用工具测试出来,磁盘能力也就两万左右,怎么能实现两万的 TPS? 组提交机制


虽然是最后一个模块,不过还是少不了概念

这里,先介绍日志逻辑序列号(log sequence number,LSN)的概念。LSN 是单调递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。LSN 也会写到 InnoDB 的数据页中,来确保数据页不会被多次执行重复的 redo log。

后续会介绍一下lsn,redo log,checkpoint他们三者的区别。checkpoint这个还没忘记吧。就是前面介绍的刷内存的时候利用的就是这个checkpoint。redo log和LSN就是正在介绍的。

如下图所示,trx1,trx2,trx3三个并发事务在prepare阶段,都写完了redo log buffer持久化到磁盘的过程。

image.png

由图中可以得知

  • trx1是最先到达的,会被选为这组的leader。
  • 等 trx1 要开始写盘的时候,这个组里面已经有了三个事务,这时候 LSN 也变成了 160;
  • trx1 去写盘的时候,带的就是 LSN=160,因此等 trx1 返回时,所有 LSN 小于等于 160 的 redo log,都已经被持久化到磁盘;
  • 这时候 trx2 和 trx3 就可以直接返回了。

所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。

在并发更新场景下,第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的效果就越好。

优化:

为了提升MySQL的性能,一般会选择在这个地方进行延迟,因为这样可以用节省更多的IOPS。

我们借助上文的两阶段提交的图!这里把写binlog日志这一过程分成了两步。

  • 先把 binlog 从 binlog cache 中写到磁盘上的 binlog 文件;
  • 调用 fsync 持久化。

根据这里的优化改一下。MySQL为了让组提交效果更好如下图。

image.png

这么一来,binlog也可以组提交了。为什么这么说呢。可以看上图的第二步。如果多个事务都已经write了(也就是说写入到redo log buffer了),再到第四步的时候就可以一起持久化到磁盘了。不是提升IOPS嘛的这个优化过程嘛!

不过通常情况下第 3 步执行得会很快,所以 binlog 的 write 和 fsync 间的间隔时间短,导致能集合到一起持久化的 binlog 比较少,因此 binlog 的组提交的效果通常不如 redo log 的效果那么好。

如果你想提升 binlog 组提交的效果,可以通过设置 binlog_group_commit_sync_delaybinlog_group_commit_sync_no_delay_count 来实现。这两个只要有一个满足条件就会调用 fsync。

  • binlog_group_commit_sync_delay 参数,表示延迟多少微秒后才调用 fsync;
  • binlog_group_commit_sync_no_delay_count 参数,表示累积多少次以后才调用 fsync。

所以,当 binlog_group_commit_sync_delay 设置为 0 的时候,binlog_group_commit_sync_no_delay_count 也无效了。


回到上文的WAL机制那里我们继续讨论一下。WAL机制主要得以于两方面

  • redo log 和 binlog 都是顺序写,磁盘的顺序写比随机写速度要快;
  • 组提交机制,可以大幅度降低磁盘的 IOPS 消耗。


实战案例


如果你的 MySQL 现在出现了IO性能瓶颈,可以通过哪些方法来提升性能呢?


  • 设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 参数,减少 binlog 的写盘次数。这个方法是基于“额外的故意等待”来实现的,因此可能会增加语句的响应时间,但没有丢失数据的风险。
  • 将 sync_binlog 设置为大于 1 的值(比较常见是 100~1000)。这样做的风险是,主机掉电时会丢 binlog 日志。
  • 将 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 异常重启时就不会丢数据了,相比之下风险会更小。


总结


今天我们已经介绍完了两大日志的写入机制。以及日志的两阶段提交的优缺点。组提交机制的流程与性能


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
安全 关系型数据库 MySQL
如何将数据从MySQL同步到其他系统
【10月更文挑战第17天】如何将数据从MySQL同步到其他系统
308 0
|
13天前
|
存储 关系型数据库 MySQL
mysql怎么查询longblob类型数据的大小
通过本文的介绍,希望您能深入理解如何查询MySQL中 `LONG BLOB`类型数据的大小,并结合优化技术提升查询性能,以满足实际业务需求。
48 6
|
1月前
|
存储 Oracle 关系型数据库
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
149 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
|
24天前
|
SQL 关系型数据库 MySQL
mysql分页读取数据重复问题
在服务端开发中,与MySQL数据库进行数据交互时,常因数据量大、网络延迟等因素需分页读取数据。文章介绍了使用`limit`和`offset`参数实现分页的方法,并针对分页过程中可能出现的数据重复问题进行了详细分析,提出了利用时间戳或确保排序规则绝对性等解决方案。
|
1月前
|
关系型数据库 MySQL 数据库
GBase 数据库如何像MYSQL一样存放多行数据
GBase 数据库如何像MYSQL一样存放多行数据
|
1月前
|
缓存 NoSQL 关系型数据库
Redis和Mysql如何保证数据⼀致?
在项目中,为了解决Redis与Mysql的数据一致性问题,我们采用了多种策略:对于低一致性要求的数据,不做特别处理;时效性数据通过设置缓存过期时间来减少不一致风险;高一致性但时效性要求不高的数据,利用MQ异步同步确保最终一致性;而对一致性和时效性都有高要求的数据,则采用分布式事务(如Seata TCC模式)来保障。
68 14
|
1月前
|
SQL 前端开发 关系型数据库
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
SpringBoot使用mysql查询昨天、今天、过去一周、过去半年、过去一年数据
65 9
|
2月前
|
SQL Java 关系型数据库
java连接mysql查询数据(基础版,无框架)
【10月更文挑战第12天】该示例展示了如何使用Java通过JDBC连接MySQL数据库并查询数据。首先在项目中引入`mysql-connector-java`依赖,然后通过`JdbcUtil`类中的`main`方法实现数据库连接、执行SQL查询及结果处理,最后关闭相关资源。
188 6
|
1月前
|
SQL 关系型数据库 MySQL
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
定时任务频繁插入数据导致锁表问题 -> 查询mysql进程
53 1
|
1月前
|
SQL 关系型数据库 MySQL
mysql数据误删后的数据回滚
【11月更文挑战第1天】本文介绍了四种恢复误删数据的方法:1. 使用事务回滚,通过 `pymysql` 库在 Python 中实现;2. 使用备份恢复,通过 `mysqldump` 命令备份和恢复数据;3. 使用二进制日志恢复,通过 `mysqlbinlog` 工具恢复特定位置的事件;4. 使用延迟复制从副本恢复,通过停止和重启从库复制来恢复数据。每种方法都有详细的步骤和示例代码。
340 2