MySQL数据一致性-单机

数据+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仍然需要过操作系统缓冲。

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日志时就需要保证三个方面:

  • 数据内容一致性
  • 数据顺序一致性
  • 效率

数据内容一致性

为什么?

如何做的?

  • 自动为每个事务分配一个唯一的ID(XID)。
  • COMMIT会被自动的分成Prepare和Commit两个阶段。
  • Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。
  • 当实例从崩溃中恢复时,需要将活跃的事务从undo中提取出来,对于ACTIVE状态的事务直接回滚,对于Prepare状态的事务,如果该事务对应的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.1中的操作完成,就确保了事务的提交,即使在执行步骤2.2时数据库发送了宕机。此外需要注意的是,每个步骤都需要进行一次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的数据。

如何做的

旧版本(性能差)

prepare_commit_mutex
在每次进行xa事务时,在prepare阶段事务先拿到一个全局的prepare_commit_mutex, 然后执行前面说的持久化(fsync)redo log与binlog,然后等fsync完了之后再释放prepare_commit_mutex,这样相当于串行化的效果虽然保证了binlog与redo log之间顺序一致性,但是却导致每个事务都需要一个fsync操作。

组提交

Binary Log Group Commit
组提交的目的是减少fsync的次数。
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
Binary Log组提交的实现
组提交整体过程
  1. binlog prepare
  2. InnoDB prepare
  3. binlog commit(ordered commit)
    --3.1 Stage #1: flushing transactions to binary log
    --3.2 Stage #2: Syncing binary log file to disk
    --3.3 Stage #3: Commit all transactions in order.
  4. InnoDB commit
5.6

为了提高并发性能,肯定要细化锁粒度。MySQL 5.6 引入了 binlog 的组提交(group commit)功能,prepare 阶段不变,只针对 commit 阶段,将 commit 阶段拆分为三个过程:

flush stage:多个线程按进入的顺序将 binlog 从 cache 写入文件(不刷盘);
sync stage:对 binlog 文件做 fsync 操作(多个线程的 binlog 合并一次刷盘);
commit stage:各个线程按顺序做 InnoDB commit 操作。
其中,每个阶段有 lock 进行保护,因此保证了事务写入的顺序。

实现方法是,在每个 stage 设置一个队列,第一个进入该队列的线程会成为 leader,后续进入的线程会阻塞直至完成提交。leader 线程会领导队列中的所有线程执行该 stage 的任务,并带领所有 follower 进入到下一个 stage 去执行,当遇到下一个 stage 为非空队列时,leader 会变成 follower 注册到此队列中。

这种组提交的优势在于锁的粒度减小,三个阶段可以并发执行,从而提升效率。

5.7优化

延迟写 redo 到 group commit 阶段

MySQL 5.6 的组提交逻辑中,每个事务各自做 prepare 并写 redo log,只有到了 commit 阶段才进入组提交,因此每个事务的 redolog sync 操作成为性能瓶颈。

在 5.7 版本中,修改了组提交的 flush 阶段,在 prepare 阶段不再让线程各自执行 flush redolog 操作,而是推迟到组提交的 flush 阶段,flush stage 修改成如下逻辑:

收集组提交队列,得到 leader 线程,其余 follower 线程进入阻塞;
leader 调用 ha_flush_logs 做一次 redo write/sync,即,一次将所有线程的 redolog 刷盘;
将队列中 thd 的所有 binlog cache 写到 binlog 文件中。
这个优化是将 redolog 的刷盘延迟到了 binlog group commit 的 flush stage 之中,sync binlog 之前。通过延迟写 redolog 的方式,为 redolog 做了一次组写入,这样 binlog 和 redolog 都进行了优化。

Innobackupex为啥不需要备份Binlog?

首先看看什么时候需要binlog=>当实例从崩溃中恢复时,需要将活跃的事务从undo中提取出来,对于ACTIVE状态的事务直接回滚,对于Prepare状态的事务,如果该事务对应的binlog已经完整记录,则提交,否则回滚事务。
因此,XA Recover只对Prepare状态事务有影响。

如何做的

FLUSH TABLE WITH READ LOCK
Innobackupex中有一步是FLUSH TABLE WITH READ LOCK。
FTWRL主要包括3个步骤:
1.上全局读锁(lock_global_read_lock)
2.清理表缓存(close_cached_tables)
3.上全局COMMIT锁(make_global_read_lock_block_commit)。
FTWRL在备份中的作用=>阻塞新事务开启以及活跃事务提交,为获取一致性位点做准备。这个时候Redo日志与Binlog日志已经是一致的了。
FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS
把Log buffer中的Redo刷到硬盘上,然后Innobackupex拷走。

https://segmentfault.com/a/1190000014810628
https://blog.csdn.net/zbszhangbosen/article/details/9132833
http://mysql.taobao.org/monthly/2020/05/07/
http://mysql.taobao.org/monthly/2015/01/01/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,270评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,489评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,630评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,906评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,928评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,718评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,442评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,345评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,802评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,984评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,117评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,810评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,462评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,011评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,139评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,377评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,060评论 2 355