MySQL是怎么保证数据不丢的
发布时间:2022-09-17 12:55:46 所属栏目:MySql教程 来源:
导读: 主要靠redo log和binlog保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复。
binlog 的写入机制
事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写
binlog 的写入机制
事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写
|
主要靠redo log和binlog保证持久化到磁盘,就能确保 MySQL 异常重启后,数据可以恢复。 binlog 的写入机制 事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。如图, 可以看到,每个线程有自己 binlog cache,但是共用同一份 binlog 文件。 图中的 write,指的就是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。图中的 fsync,才是将数据持久化到磁盘的操作。一般情况下,我们认为 fsync 才占磁盘的 IOPS。 write 和 fsync 的时机,是由参数 sync_binlog 控制的: sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。 因此,在出现 IO 瓶颈的场景里,将 sync_binlog 设置成一个比较大的值,可以提升性能。在实际的业务场景中,考虑到丢失日志量的可控性,一般不建议将这个参数设成 0,比较常见的是将其设置为 100~1000 中的某个数值。 redo log 的写入机制 和binlog相似,不过redo log buffer是线程共享的(为何binlog buffer不能共享?MySQL 这么设计的主要原因是,binlog 是不能“被打断的”。一个事务的 binlog 必须连续写,因此要整个事务完成后,再一起写到文件里。而 redo log 并没有这个要求,中间有生成的日志可以写到 redo log buffer 中。redo log buffer 中的内容还能“搭便车”,其他事务提交的时候可以被一起写到磁盘中。)。 redo log中的数据可能存在的三种状态: 存在 redo log buffer 中,物理上是在 MySQL 进程内存中,就是图中的红色部分;写到磁盘 (write),但是没有持久化(fsync),物理上是在文件系统的 page cache 里面,也就是图中的黄色部分;持久化到磁盘,对应的是 hard disk,也就是图中的绿色部分。 日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。 为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值: 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ;设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。 如果innodb_flush_log_at_trx_commit 这个参数设置0或2的时候,InnoDB也会有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 写到文件系统的 page cache,然后调用 fsync 持久化到磁盘。(无论这个参数设置的是0、1还是2,最终都会通过后台线程刷进page cache,再由操作系统写进磁盘。) innodb_flush_log_at_trx_commit参数详解 两阶段提交的时候说过,时序上 redo log 先 prepare(所谓的 redo log prepare,是“当前事务提交”的一个阶段,也就是说,在事务A提交的时候,我们才会走到事务A的redo log prepare这个阶段。事务A在提交前,有一部分redo log被事务B提前持久化,但是事务A还没有进入提交阶段,是无所谓“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 中就够了(这里说明了,redo log 状态改为commit的时候不会进行fsync,因为只要binlog 写磁盘成功,就算redo log 的状态还是prepare也没有关系会被认为事务已经执行成功,所以只需要write 到page cache就ok了,没必要再浪费io主动去进行一次fsync。这个write动作交给后台线程去执行。)。通常我们说 MySQL 的“双 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都设置成 1。也就是说,一个事务完整提交前,需要等待两次刷盘,一次是 redo log(prepare 阶段),一次是 binlog。 组提交(group commit)机制 你可能有一个疑问,这意味着我从 MySQL 看到的 TPS 是每秒两万的话,每秒就会写四万次磁盘。但是用工具测试出来,磁盘能力也就两万左右,怎么能实现两万的 TPS? 这里,我需要先和你介绍日志逻辑序列号(log sequence number,LSN)的概念。LSN 是单调递增的,用来对应 redo log 的一个个写入点。每次写入长度为 length 的 redo log, LSN 的值就会加上 length。如图,是三个并发事务 (trx1, trx2, trx3) 在 prepare 阶段,都写完 redo log buffer,持久化到磁盘的过程,对应的 LSN 分别是 50、120 和 160。 从图中可以看到, trx1 是第一个到达的,会被选为这组的 leader;等 trx1 要开始写盘的时候mysql持久化,这个组里面已经有了三个事务,这时候 LSN 也变成了 160;trx1 去写盘的时候,带的就是 LSN=160,因此等 trx1 返回时,所有 LSN 小于等于 160 的 redo log,都已经被持久化到磁盘。这时候 trx2 和 trx3 就可以直接返回了。 所以,一次组提交里面,组员越多,节约磁盘 IOPS 的效果越好。但如果只有单线程压测,那就只能老老实实地一个事务对应一次持久化操作了。在并发更新场景下,第一个事务写完 redo log buffer 以后,接下来这个 fsync 越晚调用,组员可能越多,节约 IOPS 的效果就越好。 为了让一次 fsync 带的组员更多,MySQL 有一个很有趣的优化:拖时间。如在两阶段提交的时候,如图, 图中,实际上,写 binlog 是分成两步的。 先把 binlog 从 binlog cache 中写到磁盘上的 binlog 文件。调用 fsync 持久化。 MySQL 为了让组提交的效果更好,把 redo log 做 fsync 的时间拖到了步骤 1 之后。也就是说,上面的图变成了这样: 这么一来,binlog 也可以组提交了。在执行图中第 4 步把 binlog fsync 到磁盘时,如果有多个事务的 binlog 已经写完了,也是一起持久化的,这样也可以减少 IOPS 的消耗。 事务执行期间,还没到提交阶段,如果发生 crash 的话,redo log 肯定丢了,这会不会导致主备不一致呢? 不会。因为这时候 binlog 也还在 binlog cache 里,没发给备库。crash 以后 redo log 和 binlog 都没有了,从业务角度看这个事务也没有提交,所以数据是一致的。 (编辑:均轻资讯网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |

