最近遇到过生产上的数据丢失问题,看到大家各种分析代码,然后我发现很多人认为事物提交了就认为数据不会丢失!!!。。。我只能说这是公司没有DBA,或者说大家不能深层次理解mySQL原理的悲哀!
针对此,特意写了这篇博客!(理解不深刻不准确之处,敬请指出)
现象
正常情况下,我们会将一系列增,删,改操作写在一个事物中,也就是一个原子操作,那么理论上这一系列的操作,要么会同时成功,要么会同时失败(回滚)!
但实际上呢,数据库返回失败,那么这一系列数据库操作肯定都不会成功,但如果数据库返回成功,那么绝大部分情况都会会全部成功,但也有可能会部分成功,或者全部不成功。这也就是数据丢失。
mysql的数据丢失大致可以分为:
存储引擎层面丢失数据
在实际项目中,我们一般接触到的是支持事物的InnoDB存储引擎,此处只讲innoDB存储引擎的数据丢失。
InnoDB默认开启内部的XA事物(实现方式是基于redo log和undo log),也就是采用日志先行的策略。在未开启binlog的情况下,数据变更(数据的增,删,改操作)会在内存中操作,并将事物顺序写入到redo log中,这时就会认为事物已经完成,响应事物提交成功。然后在一定条件下,才将内存中的数据合并到磁盘中。
所以,在持久化到磁盘的过程中,如果服务器宕机等导致内存中的数据丢失,数据也就会丢失。不过这种数据丢失是可以通过 recovery 重做日志,找回数据的。
这就是前面描述的数据丢失,根本原因就是内存中的数据没有及时刷新到磁盘中。 所以InnoDB有一个特别的参数用于设置这两个缓存的刷新: innodb_flush_log_at_trx_commit.
默认,innodb_flush_log_at_trx_commit=1,表示在每次事务提交的时候,都把log buffer刷到文件系统中去,并且调用文件系统的“flush”操作将缓存刷新到磁盘上去。这样的话,数据库对IO的要求就非常高了,如果底层的硬件提供的IOPS比较差,那么MySQL数据库的并发很快就会由于硬件IO的问题而无法提升。
为了提高效率,保证并发,牺牲一定的数据一致性。innodb_flush_log_at_trx_commit 还可以设置为0和2。
innodb_flush_log_at_trx_commit=0时,每隔一秒把log buffer刷到文件系统中去,并且调用文件系统的“flush”操作将缓存刷新到磁盘上去。这样的话,可能丢失1秒的事务数据。
innodb_flush_log_at_trx_commit=2时,在每次事务提交的时候会把log buffer刷到文件系统中去,但是每隔一秒调用文件系统的“flush”操作将缓存刷新到磁盘上去。如果只是MySQL数据库挂掉了,由于文件系统没有问题,那么对应的事务数据并没有丢失。只有在数据库所在的主机操作系统损坏或者突然掉电的情况下,数据库的事务数据可能丢失1秒之类的事务数据。这样的好处就是,减少了事务数据丢失的概率,而对底层硬件的IO要求也没有那么高(log buffer写到文件系统中,一般只是从log buffer的内存转移的文件系统的内存缓存中,对底层IO没有压力)。MySQL 5.6.6以后,这个“1秒”的刷新还可以用innodb_flush_log_at_timeout 来控制刷新间隔。
在大部分应用环境中,应用对数据的一致性要求并没有那么高,所以很多MySQL DBA会设置innodb_flush_log_at_trx_commit=2,这样的话,数据库就存在丢失最多1秒的事务数据的风险。
mysql还存在另外一种数据丢失,数据库的复制丢失数据。
主从复制层面丢失数据
mysql的复制并不能很好地从服务器宕机,磁盘损坏、内存或网络错误中恢复,这时一般得从某个点开启重启复制。
这些情况包括:主库意外关闭、备库意外关闭、主库上的二进制日志损坏、备库上的中继日志损坏、二进制日志与InnoDb事物日志不同步等。