数据+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)
- IO性能
Undo + Redo的设计主要考虑的是提升IO性能。虽说通过缓存数据,减少了写数据的IO.
但是却引入了新的IO,即写Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。
为了保证Redo Log能够有比较好的IO性能,InnoDB 的 Redo Log的设计有以下几个特点:
A. 尽量保持Redo Log存储在一段连续的空间上。因此在系统第一次启动时就会将日志文件的空间完全分配。
以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。
B. 批量写入日志。日志并不是直接写入文件,而是先写入redo log buffer.当需要将日志刷新到磁盘时
(如事务提交),将许多日志一起写入磁盘.
C. 并发的事务共享Redo Log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起,
以减少日志占用的空间。例如,Redo Log中的记录内容可能是这样的:
记录1: <trx1, insert …>
记录2: <trx2, update …>
记录3: <trx1, delete …>
记录4: <trx3, update …>
记录5: <trx2, insert …>
D. 因为C的原因,当一个事务将Redo Log写入磁盘时,也会将其他未提交的事务的日志写入磁盘。
E. Redo Log上只进行顺序追加的操作,当一个事务需要回滚时,它的Redo Log记录也不会从
Redo Log中删除掉。
Redo为啥采用物理page+逻辑
https://zhuanlan.zhihu.com/p/109417488
» MySQL数据库InnoDB存储引擎Log漫游(2)
这也是为啥需要double write buffer
保证日志的刷盘
上文提到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仍然需要过操作系统缓冲。
![](https://img.haomeiwen.com/i5675121/cb3a2f10293fcd16.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到磁盘。
![](https://img.haomeiwen.com/i5675121/a8eaa3473dd1eba9.png)
数据完整性
上文说Redo日志与Undo日志保证了数据的持久性和原子性,这个是Innodb层面的。因为MySQL是Server-engine架构,server层面需要使用binlog。
https://www.infoq.cn/article/M6g1yjZqK6HiTIl_9bex
在同时考虑Redo日志与binlog日志时就需要保证三个方面:
- 数据内容一致性
- 数据顺序一致性
- 效率
数据内容一致性
为什么?
如何做的?
- 自动为每个事务分配一个唯一的ID(XID)。
- COMMIT会被自动的分成Prepare和Commit两个阶段。
- Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。
- 当实例从崩溃中恢复时,需要将活跃的事务从undo中提取出来,对于ACTIVE状态的事务直接回滚,对于Prepare状态的事务,如果该事务对应的binlog已经记录,则提交,否则回滚事务。
具体实现
![](https://img.haomeiwen.com/i5675121/d7fd349ed24f7677.png)
以上的图片中可以看到,事务的提交主要分为两个主要步骤:
- 准备阶段(Storage Engine(InnoDB) Transaction Prepare Phase)
此时SQL已经成功执行,并生成xid信息及redo和undo的内存日志。然后调用prepare方法完成第一阶段,papare方法实际上什么也没做,将事务状态设为TRX_PREPARED,并将redo log刷磁盘。 - 提交阶段(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顺序不一致为例。
![](https://img.haomeiwen.com/i5675121/eb824ad1c7ec8fc0.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的过程如下:
- 获取 log_mutex
- 若flushed_to_disk_lsn>=lsn,表示日志已经被刷盘,跳转5
- 若 current_flush_lsn>=lsn,表示日志正在刷盘中,跳转5后进入等待状态
- 将小于LSN的日志刷盘(flush and sync)
- 退出log_mutex
这个过程是根据LSN的顺序进行合并的,也就是说一次redo group commit的过程可能会讲别的未提交事务中的lsn也一并刷盘
https://segmentfault.com/a/1190000014810628
组提交提升效率
优化
网友评论