💡 有理想,但不妄想, 💭 有希望,但不奢望, 🐬 有作为,但不妄为。|

navyum

园龄:4个月粉丝:0关注:0

02.日志系统

bin log 备份日志

  • 作用:Server层生成的日志,主要用于数据备份和主从复制

bin log刷盘时机:

  • 事务执行过程中,先把日志写到 binlog cache(Server 层的 cache),事务提交的时候,再把 binlog cache 写到操作系统的内核缓冲区page cache,最后通过系统调用fsync刷盘。 Img

bin log 刷盘频率控制:

  • sync_binlog = 0 ,表示每次提交事务都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;
  • sync_binlog = 1 ,表示每次提交事务都会 write,然后马上执行 fsync;
  • sync_binlog = N(N>1) ,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

bin log 格式 binlog_format

  • STATEMENT:(记录动作)
    • 每一条修改数据的 SQL 都会被记录到 binlog 中(相当于记录了逻辑操作,所以针对这种格式,binlog 可以称为逻辑日志),主从复制根据 SQL 语句重放
    • 问题:但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
  • ROW(默认):(记录结果)
    • 记录行数据最终被修改成什么样(这种格式的日志,就不能称为逻辑日志了)
    • 问题:但 ROW 的缺点是每行数据的变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录,使 binlog 文件过大
  • MIXED:
    • 包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;
    • 折中方案

redo log 重做日志

  • 作用:Innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复
  • 常识:
    • WAL技术 ( Write-Ahead Logging):先写日志,再写磁盘
    • crash-safe:即使数据库发生异常重启,之前提交的记录都不会丢失
  • redo log 记录内容:
    • redo log 是物理日志,记录了某个数据页做了什么修改(对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了 AAA 更新)
  • redo log 如何确保crash-safe:
    • 当系统崩溃时,即使Buffer Pool脏页数据没有持久化,通过已经持久化的轻量级redo log,将所有数据恢复到最新的状态

redo log 刷盘时机:

  • log buffer空间不足时(innodb_log_buffer_size)
  • 事务提交时
  • 后台线程不停的刷(其实是兜底逻辑)
  • 正常关闭服务器时(异常时)
  • 发生checkpoint
  • 其他的一些情况

redo log的生成和写入:

  • 综述:
    • 事务开始执行时,可能会有多个mtr,每个mtr执行时,生成多个redo log为一组
    • redo log记录会按组为单位,先被写入到Buffer Pool中的redo log buffer中,这个时候更新就算完成了
    • 当事务提交时,再将 redo log buffer 持久化到磁盘中
    • 这样就不需要等待 Buffer Pool 里的脏页数据持久化到磁盘
    • 后台进程再将Buffer Pool中的用户数据刷盘处理
  • 既然redo log 也要刷盘,为什么不直接将用户数据刷盘?
    • redo log 占用的空间非常小,用户数据可能会很大
    • redo log 日志是顺序IO刷盘,用户数据刷盘可能是随机IO刷盘
  • Mini-Transaction(mtr)
    • 对底层页面中的一次原子访问的过程称之为一个Mini-Transaction(mtr)
    • 一个事务可以包含若干条语句,每一条语句其实是由若干个mtr组成,每一个mtr又可以包含若干条redo日志。
    • 为什么要基于mtr做redo log记录分组:
      • 在恢复时,一次mtr对应的一组redo log需要保证事务的原子性。要么全部成功,要么都失败。
    • 多条redo log记录如何分组:
      • 分隔:组和组之间通过 type=MLOG_MULTI_REC_END的redo log进行分隔,组内只有一个redo log除外
      • 通过type高位1bit,type最高位=1,表示是生产一条redo log记录
    • redo log 记录结构Img
    • redo log 页结构
      • 用来存储redo日志的称为block,大小为512字节 Img
      • log block body:存储redo log 记录
      • log block header、trailer:存储管理信息
    • redo log buffer 结构Img
      • 如何确定buffer的写入位置:buf_free的全局变量,该变量是偏移位置,指明后续写入的redo日志应该写入到log buffer中的哪个位置
      • 一个事务可以有多个mtr,每当一个mtr执行完成时,伴随该mtr生成的一组redo日志就需要被复制到redo log buffer;多个事务的多个mtr可以交叉写入buffer;当事务提交时,需要将所有mtr对应的redo log刷盘。 Img

redo log在磁盘中的结构:

  • redo日志文件组:
    • 文件名称:ib_logfile_x
    • 大小:每个redo log文件大小:innodb_log_file_size 默认48MB,每组文件数量 innodb_log_files_in_group 默认2 Img
  • redo log 刷盘:
    • 采用循环使用的方式将redo log buffer中的数据,写到redo日志文件组里
    • 使用checkpoint做偏移标记
  • lsn
    • log sequeue number 日志序列号
    • 作用:记录当前系统产生的redo log的日志量的总和
    • 初始值:8704,就这么规定的
    • 和buf_free全局变量的关系,lsn是结构里面的字段,buf_free是程序中的全局变量
    • 当buf_free为12字节时,lsn = 8704 + 12
    • 一个mtr可能会有多条redo log记录,所以对应的会有lsn起始值和结束值
  • flushed_to_disk_lsn
    • 作用:表示redo log buffer中已经刷新到磁盘的序列号,用于记录下次写入buffer的偏移位置
    • lsn和flushed_to_disk_lsn之间的差距值就是还未写入磁盘的redo log 数据,如果值相等表示已经全部写入磁盘
    • 和buf_next_to_write全局变量的关系,flushed_to_disk_lsn是结构里面的字段,buf_next_to_write是程序中的全局变量
  • flush链表中的lsn
    • oldest_modification:某个页被加载到Buffer Pool后进行第一次修改,那么就将修改该页的mtr开始时对应的lsn值记为该脏页的oldest_modification
    • newest_modification:某个页每次被修改,都会将修改该页的mtr结束时对应的lsn更新到newest_modification Img
    • 上图解释:一次事务中,发生三次mtr
      • 第一次mtr起始lsn为8716,结束为8916,过程中更新了页a
      • 第二次mtr起始lsn为8916,结束为8848,过程中更新了页c、页b
      • 第三次mtr起始lsn9948,结束为10000,过程中更新了页b、页d
      • 第二次mtr时,在当前事务中因为页b已经在buffer中,所以页b不用移动,页b的o_m不变,将该mtr结束时lsn更新给页b的n_m,页d为新页,所以会插入flush链表头部
    • flush链表中的脏页顺序:
      • 修改发生的时间倒序,即oldest_modification
      • 一个事务中被多次更新的页面不会重复插入到flush链表中,但是会多次更新newest_modification
  • checkpoint_lsn
    • redo log 如何做到循环使用:
      • 通过checkpoint_lsn值,判断对应的脏页是否已经刷新到磁盘里,如果已经完成,则可以覆盖
    • checkpoint过程:
      • 步骤一:计算一下当前系统中可以被覆盖的redo日志对应的lsn值最大是多少
        • 从flush链表中找出当前系统中被最早修改的脏页对应的oldest_modification(因为有序性,所以一定是flush链表末尾页对应的o_m)
      • 步骤二:更新checkpoint_no(每次checkpoint加1)、checkpoint_lsn(最早的脏页o_m)、checkpoint_offset(lsn在日志文件中的offset)到redo日志文件组的管理信息中(当checkpoint_no的值是偶数时写到checkpoint1中,是奇数时写到checkpoint2中) Img 此时的checkpoint_lsn为页c的o_m,即8916
  • 各种lsn和redo log file的关系:
    • lsn:全局redo log总量
    • flushed_to_disk_lsn: redo log buffer中待写入的lsn
    • checkpoint_lsn: flush 链表中未刷入磁盘中最早的脏页对应的lsn Img
  • 查看系统的各种lsn:
  • mysql> SHOW ENGINE INNODB STATUS\G (...省略前面的许多状态) LOG --- Log sequence number 124476971 Log flushed up to 124099769 Pages flushed up to 124052503 Last checkpoint at 124052494
  • 崩溃后如何恢复:
    • 恢复的起始点:checkpoint_lsn
    • 恢复的终点:redo log日志文件中,数据未写满512字节的那个block
    • 加速恢复:
      • 使用hash表
        • 通过space ID + page number计算散列值(详细看redo log 日志格式),散列值相同,按照生成的先后顺序通过链表法连接。这些相同的散列值,表示对同一个page的修改的redo log记录,这样可以一次性将一个页面修复好
        • 之后就可以遍历哈希表,将页面逐个修复
      • 跳过已经刷新到磁盘的页
        • 判断方式File Header中的FIL_PAGE_LSN(值等于newest_modification)与checkpoint_lsn比较。如果小于等于checkpoint_lsn,则表示页已经被刷到盘里,可以跳过

binlogredo log区别:

  1. 日志格式
  2. 空间大小和写入方式
  3. 适用对象不同
  4. 用途不同
两阶段提交:
  • 含义:将redo log 的写入步骤拆成了两个:preparecommit
  • 作用:确保redo log 和binlog 两份日志之间的逻辑一致
  • redo log 影响主库的数据,binlog 影响从库的数据,所以 redo log 和 binlog 必须保持一致才能保证主从数据一致
  • 两阶段提交其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态

undo log 回滚日志

  • 作用:Innodb存储引擎层生成的日志,实现了事务中原子性,主要用于事务回滚和 MVCC

undo log 生成过程:

  • 事务没提交前,MySQL会先记录更新前的数据到 undo log 日志文件里面。操作之后就可以销毁。
  • 操作不同,需要记录到undo log的内容也不同

原子性场景:

  • 当事务需要回滚时(异常回滚、主动回滚),可以利用undo log来进行回滚

undo log + 一致性视图实现MVCC:

  • 通过undo log 的 roll_pointer 指针 和trx_id事务id构成一条记录的版本链
  • 通过一致性视图,可以获得当前活跃事务IDs

undo log记录的结构:

  • 不同的sql生成的undo log格式不一样
    • insert 操作
      • 当我们向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。不过记录undo日志时,我们只需要考虑向聚簇索引的情况。(因为聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的undo操作)
    • delete 操作
      • 过程:
        • 阶段一:仅仅将记录的delete_mask标识位设置为1,称为delete_mask
        • 阶段二:当该删除语句所在的事务提交之后,会有专门的线程做purge操作,真正的把记录做删除掉。(把该记录从正常记录链表中移除,并且加入到垃圾链表 PAGE_FREE)
    • update 操作
      • 不更新主键:
        • 就地更新(字段占用的存储空间大小不发生变化):
          • 直接在原记录的基础上修改对应列的值
        • 先删除,再新增(字段占用的存储空间大小发生变化)
          • 先把这条旧的记录从聚簇索引页面中真正删除,然后再根据更新后列的值创建一条新的记录插入到页面中
          • 优化点:空间不超过旧记录占用的空间,可以直接重用被加入到垃圾链表中的旧记录所占用的存储空间,否则需要在页面中新申请
      • 更新主键:
        • 更新了某条记录的主键值,意味着这条记录在聚簇索引中的位置将会发生改变
        • 过程:
          • 将旧记录进行delete mark操作
          • 根据更新后各列的值创建一条新记录,重新定位插入的位置,并将其插入到聚簇索引
  • 查询时
    • 不详细展开

undo log和数据页的关系:

  • 通过roll_pointer关联 Img

undo log 和事务的关系:

  • 单个事务中的Undo页面链表:
    • 一个事务可能包含多个语句,一个语句可能对若干条记录进行改动,而对每条记录进行改动前,都需要记录1条或2条的undo日志
    • 所以在一个事务执行过程中可能产生很多undo日志这些日志可能一个页面放不下,需要放到多个页面中,这些页面就通过TRX_UNDO_PAGE_NODE属性连成了链表
    • 每个undo 页链表的首页是特殊的first undo page,存储了一些额外的管理信息Undo Log Segment Header,记录页链表的段信息
  • 单个事务中会生成哪些链表:
    • undo log类型:
      • 同一个Undo页面只能存储TRX_UNDO_INSERT大类的undo日志TRX_UNDO_UPDATE大类的undo日志,这样就有了insert undo 链表和update undo 链表
    • 普通表和临时表:
      • 普通表和临时表的记录改动时产生的undo日志要分别记录,这样就有了普通表 undo 链表临时表 undo 链表
  • 多个事务中的Undo页面链表:
    • 为了尽可能提高undo日志的写入效率,不同事务执行过程中产生的undo日志需要被写入到不同的Undo页面链表中,这样每个事务的链表完全独立 Img
  • 事务写undo log的过程:
    • 一个事务在向Undo页面中写入undo日志时的方式是十分简单暴力的,就是直接往里怼,写完一条紧接着写另一条,各条undo日志之间是亲密无间的。写完一个Undo页面后,再从段里申请一个新页面,然后把这个页面插入到Undo页面链表中,继续往这个新申请的页面中写。 Img

重用Undo page:

  • 重复使用已提交的事务的undo page
  • 条件:
    • 该链表中只包含一个Undo page
    • Undo page已经使用的空间小于整个页面空间的3/4

回滚段:

  • 含义:在同一时刻系统里有多个事务,此时会有多个Undo页面链表存在。为了更好的管理这些链表,通过Rollback Segment Header的页面,来管理这些Undo页面链表。在这个页面中存放了各个Undo页面链表的first undo page页号,这些Undo页面链表的首个页面(first undo page)在回滚段内被称为undo slot
  • 一个回滚段可以存放1024个undo 页面 链表
  • 回滚段存储在系统表的5号页面:
  • 该页面可以存128个回滚段 Img

本文作者:navyum

本文链接:https://www.cnblogs.com/navyum/p/18509403

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   navyum  阅读(9)  评论(0编辑  收藏  举报
//自己上传到博客园的js
点击右上角即可分享
微信分享提示