zoukankan      html  css  js  c++  java
  • mysql的文件组成以及IO操作的流程

    文件组成:

    主要包括数据文件、索引文件、日志文件(undolog、redolog、binlog),其中undolog和redolog是innodb存储引擎所有。

     

    IO流程:

    1.InnoDB引擎使用的是 WAL 技术(write ahead log),执行事务的时候,写完内存和日志(undolog、redolog、binlog),事务就算完成了。  异步刷内存数据到磁盘。

    2.undolog、redolog、binlog比较

    日志种类   存储内容 作用
    undolog Innodb引擎 逻辑日志,记录的是数据内容。 提供回滚和多版本并发控制(MVCC)
    redolog Innodb引擎

    物理日志,记录的是数据页的物理修改。(某个磁盘位置,修改为...)

    机器崩溃恢复
    binlog mysql 逻辑日志,记录的是数据内容或者SQL语句。 主从复制

    3.innodb使用两阶段提交,保证数据一致性。 (保证redolog与binlog的逻辑一致性,进而保证主库与从库的数据一致性)

    4.具体流程详见:

    redolog-InnoDB 基本概念

    redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。

    在概念上,innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file中进行持久化。

    为了确保每次日志都能写入到事务日志文件中,在每次将log buffer中的日志写入日志文件的过程中都会调用一次操作系统的fsync操作(即fsync()系统调用)。因为MySQL是工作在用户空间的,MariaDB/MySQL的log buffer处于用户空间的内存中。要写入到磁盘上的log file中(redo:ib_logfileN文件),中间还要经过操作系统内核空间的os buffer,调用fsync()的作用就是将OS buffer中的日志刷到磁盘上的log file中。

    从redo log buffer写日志到磁盘的redo log file中,过程如下:

    MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。

    • 当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。

    • 当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。

    • 当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。

    尽管设置为0和2可以大幅度提升插入性能,但是在故障的时候可能会丢失1秒钟数据,更好的插入数据的做法是将值设置为1。

     
     

    redolog-InnoDB 记录方式

    1. 当有一条记录需要更新的时候,InnoDB会先把记录写到redolog,并更新内存(buffer pool)

      • InnoDB会在适当的时候(例如系统空闲),将这个操作记录到磁盘里面(刷脏页)

    2. InnoDB的redolog是固定大小的,如果每个日志文件大小为1GB,4个日志文件为一组

      • redolog的总大小为4GB,循环写

      • write pos是当前记录的位置,一边写一边后移,写到3号文件末尾后就回到0号文件开头

        • redolog是顺序写,数据文件是随机写

      • checkpoint是当前要擦除的位置,擦除记录前需要先把对应的数据落盘(更新内存页,等待刷脏页)

      • write pos到checkpoint之间的部分可以用来记录新的操作

        • 如果write pos赶上了checkpoint,说明redolog已满,不能再执行新的更新操作,需要先推进checkpoint

        • 只要write pos未赶上checkpoint,就可以执行新的更新操作

      • checkpoint到write pos之间的部分等待落盘(先更新内存页,然后等待刷脏页)

        • 如果checkpoint赶上了write pos,说明redolog已空

    3. 有了redolog之后,InnoDB能保证数据库即使发生异常重启,之前提交的记录都不会丢失,达到crash-safe

    4. 如果redolog太小,会导致很快被写满,然后就不得不强行刷redolog,这样WAL机制的能力就无法发挥出来

    Q1、更新内存是更新什么,所有数据在内存中有一份,更新了便于后来的查找?

    是内存页里的数据,更新的时候会先把数据页加载到内存里再更新

    Q2、redo log是在内存中还是磁盘上?在mysql的内存中?

    既然能保证数据库异常重启不丢失,redo log肯定是在磁盘上。

    binlog-Server

    1. redolog是InnoDB特有的日志,binlog属于Server层日志

    2. 有两份日志的历史原因

      • 一开始并没有InnoDB,采用的是MyISAM,但MyISAM没有crash-safe的能力,binlog日志只能用于归档

      • InnoDB是以插件的形式引入MySQL的,为了实现crash-safe,InnoDB采用了redolog的方案

    3. binlog一开始的设计就是不支持崩溃恢复(原库)的,如果不考虑搭建从库等操作,binlog是可以关闭的(sql_log_bin)

    4. redolog vs binlog

      • redolog是InnoDB特有的,binlog是MySQL的Server层实现的,所有层都可以使用

      • redolog是物理日志,记录某个数据页上做了什么修改

        • binlog是逻辑日志,记录某个语句的原始逻辑

        • 逻辑日志:提供给别的引擎用,是大家都能理解的逻辑,例如搭建从库

        • 物理日志:只能内部使用,其他引擎无法共享内部的物理格式

      • redolog是循环写,空间固定,不能持久保存,没有归档功能

        • binlog是追加写,空间不受限制,有归档功能

      • redolog主要用于crash-safe,原库恢复

        • binlog主要用于恢复成临时库(从库)

      • 崩溃恢复的过程不写binlog(可能需要读binlog,如果binlog有打开,一般都会打开)

        • 用binlog恢复实例(从库),需要写redolog

    5. redolog对应用开发来说是透明的

    6. binlog有两种模式

      • statement格式:SQL语句

      • row格式:行内容(记两条,更新前和更新后),推荐

        • 日志一样的可以用于重放

    update 内部流程

    浅色框在InnoDB内部执行,深色框在执行器中执行(SQL: update T set c=c+1 where ID=2;)

    1. 执行器先通过InnoDB获取id=2这一行,id是主键,InnoDB可以通过聚簇索引找到这一行

      • 如果id=2这一行所在的数据页本来就在内存(InnoDB Buffer Pool)中,直接返回给执行器

      • 否则先从磁盘读入内存,然后再返回

    2. 执行器拿到InnoDB返回的行数据,进行+1操作,得到新的一行数据,再调用InnoDB的引擎接口写入这行数据

    3. InnoDB首先将这行新数据更新到内存(InnoDB Buffer Pool)中,同时将这个更新操作记录到redolog(物理记录)

      • 更新到内存中,在事务提交后,后续的查询就可以直接在内存中读取该数据页,但此时的数据可能还没有真正落盘

        • 但在事务提交前,其他事务是无法看到这个内存修改的

        • 而在事务提交后,说明已经成功写入了redolog,可崩溃恢复,不会丢数据,因此可以直接读内存的数据

      • 刚更新的内存是不会删除的,除非内存不够用,在数据从内存删除之前,系统会保证它们已经落盘

      • 此时redolog处于prepare状态(prepare标签),然后告诉执行器执行完成,随时可以提交事务

        • 对其他事务来说,刚刚修改的内存是不可见的

    4. 执行器生成这个操作的binlog(逻辑记录)并写入磁盘

      • binlog写成功事务就算成功,可以提交事务

        • 哪怕崩溃恢复,也会恢复binlog写成功的事务(此时对应的redolog处于prepare状态)

      • binlog如果没写成功就回滚,回滚会写redolog,打上rollback标签,binlog则会直接丢弃

        • 如果binlog不丢弃,则会传播到从库

    5. 执行器调用InnoDB的提交事务接口,InnoDB把刚刚写入的redolog改成commit状态,更新完成

      • redolog打上了commit标签

      • commit表示两个日志都生效了

      • commit完成后才会返回客户端

     

    两阶段提交

    概述

    1. 目的:为了让redolog和binlog的逻辑一致

    2. 脑洞:假设不采用两阶段提交

      • 有两种顺序:redolog->binlog,或者binlog->redolog

      • 此时可以认为redolog和binlog完全独立

        • 崩溃恢复完全依赖于redolog(原库),恢复临时库完全依赖于binlog

      • 按照上面的两种顺序,都会导致redolog和binlog逻辑上的不一致

        • 假设原库crash后执行原库恢复+执行临时库恢复,恢复出来的数据是不一致的(主从不一致)

    3. 恢复清醒:两阶段提交(假设是双1的配置)

      • redolog prepare + binlog成功,提交事务,崩溃恢复后也会继续提交事务(redolog commit),逻辑一致

      • redolog prepare + binlog失败,回滚事务,崩溃恢复后也会继续回滚事务(redolog rollback),逻辑一致

        • 一个事务的binlog是有固定格式的

        • redolog与binlog是通过事务id(XID)进行关联的

        • 此时binglog中没有对应的记录,事务记录是不完整的

      • 崩溃恢复后是会从checkpoint开始往后主动刷数据

    4. 采用非双1的配置,在极端环境下会出现redolog与binlog不一致的情况

      • 优先保证redolog,先原库崩溃恢复,再处理从库(原库可以在崩溃恢复后,重新做一次全量备份,重建从库)

    在两阶段提交的不同瞬间,Mysql发生异常重启,如何保证数据完整性?

    崩溃恢复时的判断规则:

    1、如果redo log里的事务是完整的,也就是已经有了commit标识,直接提交

    2、如果redo log里的事务只有完整的prepare,则判断对应的事务binlog是否存在且完整,是则提交否则回滚事务。

    如果在A处出现crash:由于binlog还没写入,redo log还没提交,所以崩溃恢复时事务会回滚(删除redo log,内存回滚),可以保证数据一致性。

    如果在B处出现crash:对应于上面第二种情况,如果binlog已经写完则提交,否则回滚。

    F&Q

    1、redo log和binlog是如何关联起来的?

    它们有一个共同的数据字段:XID。崩溃恢复时会按顺序扫描redo log:

    如果碰到既有prepare又有commit标识的redo log就直接提交

    如果碰到只有prepare没有commit的redo log则拿着XID去binlog找对应的事务

    2、处于prepare阶段的redo log+完整的binlog重启就能恢复,MYSQL为什么这样设计?

    主要考虑数据一致性,在某一时刻,如果binlog写完后MYSQL发生崩溃,这时候binlog已经写入,之后会被从库(或用这个binlog恢复出的库)使用。

    所以,在主库上也要提交这个事务。采用这个策略,主库和备库的数据就保证了一致性。

    3、为什么是“两阶段提交”策略,先写完redo log,再写binlog,崩溃恢复的时候必须两个日志都完整才可以吗?

    两阶段提交是经典的分布式系统问题,这么做主要考虑事务的持久性问题

    对于InnoDB,如果redo log提交了那么事务就不能回滚了,如果redo log提交了binlog写入失败则数据又不一致了,

    只要当前事务不提交它就是活跃的,对别的事务就是不可见的,一旦提交就可能落到后面启动的事务的可见区域里,提交后回滚可能覆盖掉别的事务的更新

    两阶段提交是给所有人一个机会,当所有人都说“ok”的时候再一起提交。

    4、不引入两个日志,也就没有两阶段提交的必要了。只用binlog支持崩溃恢复和归档可以吗?

    历史层面:InnoDB不是MYSQL原生引擎,在加入MYSQL之前就已经有了redo log支持崩溃恢复和事务

    实现层面:下图是只用binlog的示意图

    这样的流程下,binlog 还是不能支持崩溃恢复的。

    说一个不支持的点吧:binlog 没有能力恢复“数据页”。

    如果在图中标的位置,也就是 binlog2 写完了,但是整个事务还没有 commit 的时候,MySQL 发生了 crash。

    重启后,引擎内部事务 2 会回滚,然后应用 binlog2 可以补回来;但是对于事务 1 来说,系统已经认为提交完成了,不会再应用一次 binlog1。

    但是,InnoDB 引擎使用的是 WAL 技术,执行事务的时候,写完内存和日志,事务就算完成了。如果之后崩溃,要依赖于日志来恢复数据页。

    也就是说在图中这个位置发生崩溃的话,事务 1 也是可能丢失了的,而且是数据页级的丢失。此时,binlog 里面并没有记录数据页的更新细节,是补不回来的。

    你如果要说,那我优化一下 binlog 的内容,让它来记录数据页的更改可以吗?但,这其实就是又做了一个 redo log 出来。

    5、那可以只要redo log 不要binlog吗?

    只从崩溃恢复的角度讲是可以的,关闭binlog依然是crash-safe的。

    实际生产环境binlog都会是开着的,一个是归档,redo log无法记录历史日志,另外MYSQL系统依赖于binlog,比如高可用的基础就是binlog复制。

    6、redo log一般设置多大?

    对于常见的几个TB的硬盘,设置4个文件,每个1GB

    7、正常运行的实例,数据写入后的最终落盘是从redo log更新过来的还是从buffer pool更新过来的?

    redo log并没有记录数据页的完整数据,所以它没有能力自己去更新磁盘数据页

    1、如果是正常运行的实例,数据页被修改后跟磁盘的数据页不一样称为脏页,最终落盘就是把内存中的数据页写入磁盘,也就是刷脏页(flush),这个过程跟redo log毫无关系

    2、在崩溃恢复场景中,InnoDB如果判断到一个数据页可能在崩溃恢复时丢失了更新,就会将它读到内存然后用redo log更新内存内容,更新完成后内存就变成了上述情况的脏页。

    8、redo log buffer是什么?是先修改内存还是先写redo log文件?

    在一个事务的更新过程中,日志是要写多次的,如:

    begin;

    insert into t1 ...

    insert into t2 ...

    commit;

    插入数据的过程中生成的日志都要先保存起来,但又不能在还没执行commit操作时直接写到redo log文件中

    所以redo log buffer是一块内存,用来先存redo 日志的,也就是在执行第一个insert时数据内存被修改了,redo log buffer也写入了日志

    真正把日志写到redo log文件(文件名是ib_logfile+数字)是在执行commit语句(不是commit小步骤)时做的。

  • 相关阅读:
    CF1435E Solo mid Oracle(推柿子)
    CF1435C Perform Easily(Set)
    NC7501I (反向建图)
    NC7501D Router Mesh (去掉割点算连通快的变化)
    超全的Python第三方库
    字符串常见题目
    IDEA的常用设置大全
    IDEA的介绍与安装
    涨姿势啦(1)
    Torch常用的函数总结
  • 原文地址:https://www.cnblogs.com/DengGao/p/12734775.html
Copyright © 2011-2022 走看看