mysql如何保证数据一致性

1.MySQL数据库层丢数据场景

   本节咱们主要介绍一下在存储引擎层上是如何会丢数据的。mysql

1.1.InnoDB丢数据

    InnoDB支持事务,同Oracle相似,事务提交须要写redo、undo。采用日志先行的策略,将数据的变动在内存中完成,而且将事务记录成redo,顺序的写入redo日志中,即表示该事务已经完成,就能够返回给客户已提交的信息。可是实际上被更改的数据还在内存中,并无刷新到磁盘,即尚未落地,当达到必定的条件,会触发checkpoint,将内存中的数据(page)合并写入到磁盘,这样就减小了离散写、IOPS,提升性能。sql

    在这个过程当中,若是服务器宕机了,内存中的数据丢失,当重启后,会经过redo日志进行recovery重作。确保不会丢失数据。所以只要redo可以实时的写入到磁盘,InnoDB就不会丢数据。数据库

先来看一下innodb_flush_log_at_trx_commit这个参数:缓存

    = 0 :每秒 write cache & flush disk安全

    = 1 :每次commit都 write cache & flush disk服务器

    = 2 :每次commit都 write cache,而后根据innodb_flush_log_at_timeout(默认为1s)时间 flush disk网络

    从这三个配置来看,显然innodb_flush_log_at_trx_commit=1最为安全,由于每次commit都保证redo写入了disk。可是这种方式性能对DML性能来讲比较低,在咱们的测试中发现,若是设置为2,DML性能要比设置为1高10倍左右。架构

    为何oracle的实时写要比innodb的实时写性能更好?线程与进程?后面还须要研究oracle

你们能够考虑一下0与2的区别?异步

   在某些DML操做频繁的场景下,库的innodb_flush_log_at_trx_commit须要设置为2,这样就存在丢数据的风险:当服务器出现宕机,重启后进行crash recovery则会丢失innodb_flush_log_at_timeout秒内的数据。

   

PS:当开启了内部XA事务(默认开启),且开启binlog,状况稍有不同,后面会进行介绍。

1.2.MyISAM丢数据

    MyISAM存储引擎在咱们的生产中用的并很少,可是系统的数据字典表元数据等都是存储在MyISAM引擎下。

    MyISAM不支持事务,且没有data cache,全部DML操做只写到OS cache中,flush disk操做均由OS来完成,所以若是服务器宕机,则这部分数据确定会丢失。

2.主从复制不一致

   主从复制原理:MySQL主库在事务提交时写binlog,并经过sync_binlog参数来控制binlog刷新到磁盘“落地”,而备库经过IO线程从主库读取binlog,并记录到本地的relay log中,由本地的SQL线程再将relay log的数据应用到本地数据库,以下图所示:

        25704976_14149949148R0f.jpg

   从上图咱们能够看到,在主从环境中,增长了binlog,这就增长了环境的复杂性,所以也增长了丢数据以及数据不一致可能。

在分析这些丢数据的可能性以前,咱们先了解一下binlog的刷新机制以及MySQL的内部XA事务是如何保证binlog与redo的一致性的。

2.1.binlog刷新机制

    master写binlog与innodb引擎写redo相似,也有参数控制:sync_binlog

          = 0 :表示MySQL不控制binlog的刷新,由文件系统本身控制它的缓存的刷新

          > 0 :表示每sync_binlog次事务提交,MySQL调用文件系统的刷新操做将缓存刷下去

    其中最安全的就是=1,表示每次事务提交,MySQL都会把binlog缓存刷下去,这样在掉电等状况下,系统才有可能丢失1个事务的数据。当sync_binlog设置为1,对系统的IO消耗也是很是大的。

2.2.内部XA事务原理

   MySQL的存储引擎与MySQL服务层之间,或者存储引擎与存储引擎之间的分布式事务,称之为内部XA事务。最为常见的内部XA事务存在与binlog与InnoDB存储引擎之间。在事务提交时,先写二进制日志,再写InnoDB存储引发的redo日志。对于这个操做要求必须是原子的,即须要保证二者同时写入,内部XA事务机制就是保证二者的同时写入。

   

   XA事务的大体流程:

  1. 事务提交后,InnoDB存储引擎会先作一个PREPARE操做,将事务的XID写入到redo日志中。

  2. 写binlog日志

  3. 再将该事务的commit信息写到redo log中

            25704976_14149949127kIU.png

     若是在步骤1和步骤2失败的状况下,整个事务会回滚,若是在步骤3失败的状况下,MySQL数据库在重启后会先检查准备的UXID事务是否已经提交,若没有,则在存储引擎层再进行一次提交操做。这样就保证了redo与binlog的一致性,防止丢数据。

2.3.master库写redo、binlog不实时丢数据的场景

     上面咱们介绍了MySQL的内部XA事务流程,可是这个流程并非完美无缺的,redo的ib_logfile与binlog日志若是被设置非实时flush,就有可能存在丢数据的状况。

         1.redo的trx_prepare未写入,但binlog写入,形成从库数据量比主库多。

         2.redo的trx_prepare与commit都写入了,可是binlog未写入,形成从库数据量比主库少。


从目前来看,只能牺牲性能去换取数据的安全性,必需要设置redo和binlog为实时刷盘,若是对性能要求很高,则考虑使用SSD

2.4.slave库写redo、binlog不实时丢数据的场景

    master正常,可是slave出现异常的状况下宕机,这个时候会出现什么样的状况呢?若是数据丢失,slave的SQL线程还会从新应用吗?这个咱们须要先了解SQL线程的机制。

    slave读取master的binlog日志后,须要落地3个文件:relay log、relay log info、master info:

        relay log:即读取过来的master的binlog,内容与格式与master的binlog一致

        relay log info:记录SQL Thread应用的relay log的位置、文件号等信息

        master info:记录IO Thread读取master的binlog的位置、文件号、延迟等信息

    所以若是当这3个文件若是不及时落地,则主机crash后会致使数据的不一致。

在MySQL 5.6.2以前,slave记录的master信息以及slave应用binlog的信息存放在文件中,即master.info与relay-log.info。在5.6.2版本以后,容许记录到table中,参数设置以下:

		
			
			
				master-info-repository  = TABLE 
			

			
				relay-log-info-repository = TABLE 
			

		

对应的表分别为mysql.slave_master_info与mysql.slave_relay_log_info,且这两个表均为innodb引擎表。

master info与relay info还有3个参数控制刷新:

  • sync_relay_log:默认为10000,即每10000次sync_relay_log事件会刷新到磁盘。为0则表示不刷新,交由OS的cache控制。

  • sync_master_info:若master-info-repository为FILE,当设置为0,则每次sync_master_info事件都会刷新到磁盘,默认为10000次刷新到磁盘;若master-info-repository为TABLE,当设置为0,则表不作任何更新,设置为1,则每次事件会更新表 #默认为10000

  • sync_relay_log_info:若relay_log_info_repository为FILE,当设置为0,交由OS刷新磁盘,默认为10000次刷新到磁盘;若relay_log_info_repository为TABLE,且为INNODB存储,则不管为任何值,则都每次evnet都会更新表。

建议参数设置以下:

		
			
			
				sync_relay_log = 1 
			

			
				sync_master_info = 1 
			

			
				sync_relay_log_info = 1 
			

			
				master-info-repository  = TABLE 
			

			
				relay-log-info-repository = TABLE 
			

		

当这样设置,致使调用fsync()/fdatasync()随着master的事务的增长而增长,且若slave的binlog和redo也实时刷新的话,会带来很严重的IO性能瓶颈。

2.5.master宕机后没法及时恢复形成的数据丢失

   当master出现故障后,binlog未及时传到slave,或者各个slave收到的binlog不一致。且master没法在第一时间恢复,这个时候怎么办?

   若是master不切换,则整个数据库只能只读,影响应用的运行。

   若是将别的slave提高为新的master,那么原master将来得及传到slave的binlog的数据则会丢失,而且还涉及到下面2个问题。

      1.各个slave之间接收到的binlog不一致,若是强制拉起一个slave,则slave之间数据会不一致。

      2.原master恢复正常后,因为新的master日志丢弃了部分原master的binlog日志,这些多出来的binlog日志怎么处理,从新搭建环境?

对于上面出现的问题,一种方法是确保binlog传到从库,或者说保证主库的binlog有多个拷贝。第二种方法就是容许数据丢失,制定必定的策略,保证最小化丢失数据。

1.确保binlog所有传到从库

   方案一:使用semi sync(半同步)方式,事务提交后,必需要传到slave,事务才能算结束。对性能影响很大,依赖网络适合小tps系统。

   方案二:双写binlog,经过DBDR OS层的文件系统复制到备机,或者使用共享盘保存binlog日志。

   方案三:在数据层作文章,好比保证数据库写成功后,再异步队列的方式写一份,部分业务能够借助设计和数据流解决。

2.保证数据最小化丢失

   上面的方案设计及架构比较复杂,若是能容忍数据的丢失,能够考虑使用淘宝的TMHA复制管理工具。

       当master宕机后,TMHA会选择一个binlog接收最大的slave做为master。当原master宕机恢复后,经过binlog的逆向应用,把原master上多执行的事务回退掉。

3.总结

     经过上面的总结分析,MySQL丢数据的场景是五花八门,涉及到单库的丢数据场景、主从的丢数据场景以及MySQL内部XA事务原理等,相对还比较复杂,有点难以理解。

     只有当咱们了解了这些丢数据的场景,才能更好的去学习, 并解决这些问题。

     

     根据分布式领域的CAP理论(Consistency、Availability、Partition tolerance),任何的分布式系统只能同时知足2点,没办法三者兼顾。MySQL的主从环境知足Availability,且主从互不干扰,所以知足Partition tolerance,可是不知足Consistency,若是须要知足Consistency,则确定会失去Partition tolerance,所以实现100%高可用性的MySQL主从架构仍是很是困难的。只能经过一些设计去牺牲部分特性去知足另外的特性。

相关文章
相关标签/搜索