MySQL的wal机制,得到的结论是:只要redo log和binlog 持久化到磁盘,就能确保mysql异常重新启动后,数据是可以恢复的。

binlog的写入机制

其实,binlog的写入逻辑比较简单:事务执行过程中,先把日志写到binlog cache,事务提交的时候,再把binlog cache的内容写到binlog文件中

一个事务的binlog是不能被拆开的,因此不论事务多大,也要确保一次性写入,这就涉及到binlog cache的保存问题。

系统给binlog cache分配了一片内存,每个线程一个,参数binlog_cache用于控制单个线程内binlog cache所占内存的大小,如果超过这个值,就要暂存磁盘

事务提交的时候,执行器把binlog cache的完整事务写入到binlog 文件中,并清空binlog cache,状态如图

可以看到每个线程都自己的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是要先写到redo log buffer,那redo log buffer的内容是不是每次生成后都要直接持久化到磁盘呢?答案是不需要,

如果事务运行期间,mysql发生异常重启,那么这部分日志就丢失了,由于事务还没有提交,丢失的就不影响。

那么在事务还没有提交的时候,redo log buffer中的部分日志会不会持久化到磁盘呢?答案是会有的,这个要从redo log的三种状态说起

三种状态分别是:

1 存在redo log buffer中,物理上就是mysql的进程内存中,红色部分。

2 写到磁盘(write),但是没有持久化(fsync)到磁盘物理上就是操作系统的page cache,黄色的部分。

3 持久化到磁盘,对应的是hard disk,图中绿色的部分。

日志写到redo log buffer是很快的,write写到page cache也很快,但是持久化的速度就相对要慢很多。为了控制redo log的写入策略,innodb提供了参数innodb_fush_log_at_trx_commit,它有三种取值:

1 设置为0的时候,表示每次事务提交都会只把redo log留在redo log buffer中

2 设置为1的时候,事务的每次提交都会持久化到磁盘

3 设置为2的时候,事务的每次提交都会把redo log 写入到page cache中

Innodb有一个后台线程,每1秒就会把redo log buffer中的写入到redo log,调用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 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来恢复。

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

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

这时候,如果看到mysql的tps是2w的话,每秒就会写4w次磁盘,但是,用工具测试,磁盘能力也就2w左右,怎么实现tps为2w呢?

要解释这个问题,就要用到组提交(group commit)机制了

日志的逻辑序列号(LSN)是单调递增的,用来对应redo log的一个个写入点,每次写入的长度为length的redo log,LSN就会加上length。

LSN也会写到innodb的数据页中,来确保数据不会被多次执行重复的redo log,关于lsn,redo log和checkpoint后面会提到。

如图所示,是三个并发事务(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也可以组提交了,在执行上图的第四步把binlog fsync到磁盘时,如果有多个事务的binlog已经写完了,也是一起持久化的,这样也可以减少iops的消耗。

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

如果想提升binlog的组提交效果,可以通过设置(5.7)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的时候,另外的参数也就无效了。

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只保存在内存中,这样的话,mysql本身异常重启也会丢失数据,风险太大,

而redo log写到文件系统的page cache的速度也是很快,所以这个参数设置为2和0的性能差不多,但是,这样2就不会在mysql异常重启时丢失数据。

小结

如果保证binlog和redo log是完整的,就可以保证mysql是crash-safe的。

问题1,执行一个update语句以后,再去执行hexdump命令直接查看idb文件,为什么没有看到数据改变?

回答:这可能是因为WAL机制的原因,update语句执行完成,innodb只保证redolog,内存,可能还没来得及将数据写到磁盘。

问题2,为什么binlog cache每个线程自己维护,而redo log buffer是全局公用?

回答:mysql这么设计,binlog是不能”被打断”,一个事务的binlog必须连续写,因此要整个事务完成后,再一起写入到文件里。

而redo log没有这个要求,中间生成的日志可以写到redo log buffer中,还可以被写到磁盘中。

问题3,事务执行期间,还没到提交阶段,如果发生crash,redo log肯定丢失,这会不会导致主备不一致。

回答:不会,这时候binlog还在binlog cache里,没发给备库,carsh后,redo log和binlog都没有,从业务角度看事务还没有提交,所以数据是一致的。

问题4,如果binlog写完后发生crash,这时候还没有给客户端答复就重启了,等客户端重新连接进来,发现事务已经提交成功,这是不是bug

回答:不是,

假设整个事务提交成功了,redo log commit完成了,备库也收到binlog并执行了,但是主库和客户端网络断开了,导致事务成功的包返回不了,这时候客户端收到”网络断开”异常,这种也是算事务成功的,不能认为是bug

实际上数据库的crash-safe保证的是:

1 如果客户端收到事务成功的消息,事务就一定持久化了

2 如果客户端收到失败(比如pk冲突,rollback等)的消息,事务就一定失败了

3 如果客户端收到”执行异常”的消息,应用需要重连通过当前查询来继续后续的逻辑,此时数据库只需要保证内部(数据和日志之间,主库和备库之间)一致就可以了。

23 mysql怎么保证数据不丢失?的更多相关文章

  1. Spark Streaming使用Kafka保证数据零丢失

    来自: https://community.qingcloud.com/topic/344/spark-streaming使用kafka保证数据零丢失 spark streaming从1.2开始提供了 ...

  2. Kafka如何保证数据不丢失

    Kafka如何保证数据不丢失 1.生产者数据的不丢失 kafka的ack机制:在kafka发送数据的时候,每次发送消息都会有一个确认反馈机制,确保消息正常的能够被收到,其中状态有0,1,-1. 如果是 ...

  3. [转帖]kafka 如何保证数据不丢失

    kafka 如何保证数据不丢失 https://www.cnblogs.com/MrRightZhao/p/11498952.html   一般我们在用到这种消息中件的时候,肯定会考虑要怎样才能保证数 ...

  4. kafka 如何保证数据不丢失

    一般我们在用到这种消息中件的时候,肯定会考虑要怎样才能保证数据不丢失,在面试中也会问到相关的问题.但凡遇到这种问题,是指3个方面的数据不丢失,即:producer consumer 端数据不丢失  b ...

  5. Elasticsearch如何保证数据不丢失?

    目录 如何保证数据写入过程中不丢 直接落盘的 translog 为什么不怕降低写入吞吐量? 如何保证已写数据在集群中不丢 in-memory buffer 总结 LSM Tree的详细介绍 参考资料 ...

  6. Spark Streaming和Kafka整合保证数据零丢失

    当我们正确地部署好Spark Streaming,我们就可以使用Spark Streaming提供的零数据丢失机制.为了体验这个关键的特性,你需要满足以下几个先决条件: 1.输入的数据来自可靠的数据源 ...

  7. rabbitmq保证数据不丢失方案

    rabbitmq如何保证消息的可靠性 1.保证消息不丢失 1.1.开启事务(不推荐) 1.2.开启confirm(推荐) 1.3.开启RabbitMQ的持久化(交换机.队列.消息) 1.4.关闭Rab ...

  8. kafka保证数据不丢失机制

    kafka如何保证数据的不丢失 1.生产者如何保证数据的不丢失:消息的确认机制,使用ack机制我们可以配置我们的消息不丢失机制为-1,保证我们的partition的leader与follower都保存 ...

  9. Spark Streaming和Kafka整合是如何保证数据零丢失

    转载:https://www.iteblog.com/archives/1591.html 当我们正确地部署好Spark Streaming,我们就可以使用Spark Streaming提供的零数据丢 ...

随机推荐

  1. Java class、Object、Class 的区别

    Java的对象模型中: 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例. 所有的类都最终继承自Object类,Class是类,那么Class也继承自Obj ...

  2. python运行httpserver

    $ python -m SimpleHTTPServer $ python3 -m http.server

  3. C# 运行时替换方法(需要unsafe编译)

    /* https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method ...

  4. MongoDB GridFS——本质上是将一个文件分割为大小为256KB的chunks 每个chunk里会放md5标识 取文件的时候会将这些chunks合并为一个整体返回

    MongoDB GridFS GridFS 用于存储和恢复那些超过16M(BSON文件限制)的文件(如:图片.音频.视频等). GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中 ...

  5. 【sparkSQL】创建DataFrame及保存

    首先我们要创建SparkSession val spark = SparkSession.builder() .appName("test") .master("loca ...

  6. CABAC与CAVLC有什么区别?

    待完善 7.3.12 用 CAVLC 方式编码的残差数据的语义 coeff_token   指明了非零系数的个数,拖尾系数的个数. trailing_ones_sign_flag 拖尾系数的符号 - ...

  7. Win7系统64位环境下使用Apache——Apache2.4版本安装及卸载

    转载请注明出处:http://blog.csdn.net/dongdong9223/article/details/70255992 本文出自[我是干勾鱼的博客] 之前在Win7系统64位环境下使用A ...

  8. wordpress 使用固定链接

    官方文档 无插件移除url中category 目录前缀 设置 >> 固定链接,设置固定链接为自定义为: /%category%/%postname%/或者/%category%/%post ...

  9. 在CodeBlocks上配置OpenGL问题

    问题:出现No such file or directory.之后重建了C++project 仍然出现这个error.嘿 奇了怪了! 原因:前几日写密码学作业,用到NTL库,将编译器路径设置为NTL库 ...

  10. PTA L3-020 至多删三个字符 (序列dp/序列自动机)

    给定一个全部由小写英文字母组成的字符串,允许你至多删掉其中 3 个字符,结果可能有多少种不同的字符串? 输入格式: 输入在一行中给出全部由小写英文字母组成的.长度在区间 [4, 1] 内的字符串. 输 ...