zoukankan      html  css  js  c++  java
  • 23 mysql怎么保证数据不丢失?

    MySQLwal机制,得到的结论是:只要redo logbinlog 持久化到磁盘,就能确保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

    writefsync由参数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里的日志全部持久化到磁盘,这时候,就会带上事务Aredo log buffer的日子一起持久化到磁盘。

    两阶段提交的时候,时序上redo logprepare,在写binlog,最后再把redo log commit

    如果把参数innodb_flush_log_at_trx_commit设置为1,那么redo logprepare阶段就要持久化一次,因为有一个崩溃恢复逻辑是要依赖于prepareredo log,再加上binlog来恢复。

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

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

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

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

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

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

    如图所示,是三个并发事务(trx1,trx2,trx3)prepare阶段,都写完redo log buffer,持久化到磁盘的过程,对应的LSN分别是50,120,160

    从图中看到

    1 trx1是第一个到达,会被选为这个组的leader

    2 trx1要开始写盘的时候,这个组里面已经有了三个事务,这时候LSN变成了160

    3 trx1去写盘的时候,带的就是lsn=160,因此等trx1返回时,所有lsn小于160redo log都已经持久化到磁盘

    4 这时候trx2trx3就可以直接返回了。

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

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

    为了让一次fsync带的组员更多,mysql有一个优化,托时间。

    图中,把写binlog当成一个动作,但实际上写binlog分为两步

    1 先把binlogbinlog cache中写道磁盘上的binlog文件

    2 调用fsync持久化

    Mysql为了让组提交的效果更好,把redo logfsync的时间拖到了步骤1之后,上图变成如下

     

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

    不过通常情况下第3步执行得会很快,所以binlogwritefsync间的间隔很短,导致能合到一起持久化的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_delay0的时候,另外的参数也就无效了。

    WAL机制主要得益于两个方面

    1 redo logbinlog都是顺序写,磁盘的顺序写比随机写速度快。

    2 组提交机制,可以大幅度降低磁盘的iops的消耗。

    到这里,如果mysql出现了性能瓶颈,而且瓶颈在io上,可以考虑呢些方法来提升

    1 设置binlog_group_commit_sync_delaybinlog_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的速度也是很快,所以这个参数设置为20的性能差不多,但是,这样2就不会在mysql异常重启时丢失数据。

    小结

    如果保证binlogredo log是完整的,就可以保证mysqlcrash-safe的。

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

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

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

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

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

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

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

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

    回答:不是,

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

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

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

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

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

  • 相关阅读:
    184. Department Highest Salary【leetcode】sql,join on
    181. Employees Earning More Than Their Managers【leetcode】,sql,inner join ,where
    178. Rank Scores【leetcode】,sql
    177. Nth Highest Salary【leetcode】,第n高数值,sql,limit,offset
    176. Second Highest Salary【取表中第二高的值】,sql,limit,offset
    118. Pascal's Triangle【LeetCode】,java,算法,杨辉三角
    204. Count Primes【leetcode】java,算法,质数
    202. Happy Number【leetcode】java,hashSet,算法
    41. First Missing Positive【leetcode】寻找第一个丢失的整数,java,算法
    删除
  • 原文地址:https://www.cnblogs.com/yhq1314/p/10239908.html
Copyright © 2011-2022 走看看