MySQL数据一致性-单机

2020-09-21  本文已影响0人  多血

数据+LOG

数据库的数据由两部分组成,一部分是数据,一部分是LOG。Innodb的数据包括内存(Innodb buffer pool)中和硬盘中的数据。数据的更改首先会作用到内存中的缓存数据,然后Innodb会根据flush策略将最新的数据flush到硬盘中。因此数据并不是实时落盘的,此时如果进程或者系统崩溃的话,没flush到硬盘的数据会丢失。Innodb采用了WAL技术,通过Redo日志与Undo日志保证了数据的持久性与原子性。

WAL技术

计算机科学中,预写式日志(Write-ahead logging,缩写 WAL)是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个)的一系列技术。在使用WAL的系统中,所有的修改在提交之前都要先写入log文件中。
Redo与Undo
为啥使用Redo与Undo。
» MySQL数据库InnoDB存储引擎Log漫游(1)

保证日志的刷盘

上文提到Innodb通过Redo日志与Undo日志保证了数据的持久性与原子性。所以日志的刷盘策略很关键,只有日志及时刷盘,持久性和原子性才得以实现。
这里面就涉及到几个参数,包括innodb_flush_method、innodb_flush_log_at_commit等。

innodb_flush_method

Defines the method used to flush data to InnoDB data files and log files, which can affect I/O throughput.
innodb_flush_method与open文件的模式不是一一对应,因为innodb_flush_method同时指定了data files和log files的刷盘方式,这两者的刷盘方式可能不一致。
举个例子,innodb_flush_method为O_DIRECT,以O_DIRECT模式open data files,数据绕过缓存直接写入硬盘,log files仍然需要过操作系统缓冲。

image.png
注意:
innodb_flush_method为O_DIRECT时
用O_DIRECT打开数据文件,那为什么还要fsync,因为需要刷新为了把directory cache和inode cache元数据也刷新到存储设备上。
日志文件还是要过文件系统缓存,所以也需要fsync。

innodb_flush_log_at_commit

控制着redo日志在commit时的刷盘行为,参考上文,因为日志仍然需要过操作系统缓冲,所以数据安全性要求高的需要设置为1,每次事务commit都把redo日志fsync到硬盘上。
图。
innodb_flush_log_at_commit的可选值为0,1,2。
当值为0时,LOG buffer中的数据每1s写入os缓存并fsync,当值为2时,每次事务commit都把redo日志写入到os缓存,每1s再fsync到磁盘。


image.png

数据完整性

上文说Redo日志与Undo日志保证了数据的持久性和原子性,这个是Innodb层面的。因为MySQL是Server-engine架构,server层面需要使用binlog。
https://www.infoq.cn/article/M6g1yjZqK6HiTIl_9bex
在同时考虑Redo日志与binlog日志时就需要保证三个方面:

数据内容一致性

为什么?

如何做的?

具体实现

image.png

以上的图片中可以看到,事务的提交主要分为两个主要步骤:

  1. 准备阶段(Storage Engine(InnoDB) Transaction Prepare Phase)
    此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成第一阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。
  2. 提交阶段(Storage Engine(InnoDB)Commit Phase)
    2.1 记录协调者日志,即Binlog日志。
    如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid方法将SQL语句写到binlog(write()将binary log内存日志数据写入文件系统缓存,fsync()将binary log文件系统缓存日志数据永久写入磁盘)。此时,事务已经铁定要提交了。否则,调用ha_rollback_trans方法回滚事务,而SQL语句实际上也不会写到binlog。
    2.2 告诉引擎做commit。
    最后,调用引擎的commit完成事务的提交。会清除undo信息,刷redo日志,将事务设为TRX_NOT_STARTED状态。
    PS:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。
    由上面的二阶段提交流程可以看出,一旦步骤2中的操作完成,就确保了事务的提交,即使在执行步骤3时数据库发送了宕机。此外需要注意的是,每个步骤都需要进行一次fsync操作才能保证上下两层数据的一致性。步骤2的fsync参数由sync_binlog=1控制,步骤3的fsync由参数innodb_flush_log_at_trx_commit=1控制,俗称“双1”,是保证CrashSafe的根本。

数据顺序一致性

为什么?

因为mysql是多线程的,如果没有机制保证的话,在多个事务并发执行的情况下先写binlog不一定代表先在innodb中commit。
以下图Binlog与Innodb commit顺序不一致为例。


image.png

如上图,事务按照T1、T2、T3顺序开始执行,将二进制日志(按照T1、T2、T3顺序)写入日志文件系统缓冲,调用fsync()进行一次group commit将日志文件永久写入磁盘,但是存储引擎提交的顺序为T2、T3、T1。当T2、T3提交事务之后,若通过在线物理备份进行数据库备份(xtrabackup只备份Redo,不备份binlog),所以虽然在线物理备份记录的Binlog点位是T1、T2、T3都已经提交的点位,但是在恢复时因为T1在Innodb层未进行Commit,所以最终会丢失T1的数据。

如何做的

Binary Log Group Commit
Binlog组提交的基本思想是,引入队列机制保证Innodb commit顺序与binlog落盘顺序一致。
RedoLog本身就是组提交的。
2PC中的prepare阶段,会对redo进行一次刷盘操作(innodb_flush_log_at_trx_commit=1),这时候redo group commit的过程如下:

  1. 获取 log_mutex
  2. 若flushed_to_disk_lsn>=lsn,表示日志已经被刷盘,跳转5
  3. 若 current_flush_lsn>=lsn,表示日志正在刷盘中,跳转5后进入等待状态
  4. 将小于LSN的日志刷盘(flush and sync)
  5. 退出log_mutex
    这个过程是根据LSN的顺序进行合并的,也就是说一次redo group commit的过程可能会讲别的未提交事务中的lsn也一并刷盘
    https://segmentfault.com/a/1190000014810628
    组提交提升效率
    优化
上一篇 下一篇

猜你喜欢

热点阅读