当前位置: 代码迷 >> 综合 >> redolog,binlog,undolog与mvcc
  详细解决方案

redolog,binlog,undolog与mvcc

热度:97   发布时间:2023-11-14 22:39:32.0

redolog,binlog,undolog与mvcc

redoLog

redo log是固定大小的。比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,
有了redo log ,即使数据库异常重启,提交记录也不会丢失——crash-safe

redoLog何时写入

首先我们先明确一下InnoDB的修改数据的基本流程,当我们想要修改DB上某一行数据的时候,InnoDB是把数据从磁盘读取到内存的缓冲池上进行修改。这个时候数据在内存中被修改,与磁盘中相比就存在了差异,我们称这种有差异的数据为脏页。InnoDB对脏页的处理不是每次生成脏页就将脏页刷新回磁盘,这样会产生海量的IO操作,于是有了redoLog。

在事务提交时,分三种情况

1、要修改的数据页在buffer pool中,则直接修改buffer pool中的值,写入redoLog 一次Io

2、要修改的数据不在buffer pool中,则将数据读出放入 buffer pool。并且写入redoLog,两次Io

3、insertBuff,不在缓存时,将修改的内容写入buffer poll,同时写入redo log,一次io,在数据被读取时,会将修改的内容和insert buffer合并

redoLog原理

一、 日志类型
avatar
redo log在数据库重启恢复的时候被使用,因为其属于物理日志的特性,恢复速度远快于逻辑日志。而我们经常使用的binlog就属于典型的逻辑日志。
二、 checkpoint
坦白来讲checkpoint本身是比较复杂的,checkpoint所做的事就是把脏页给刷新回磁盘。所以,当DB重启恢复时,只需要恢复checkpoint之后的数据。这样就能大大缩短恢复时间。当然checkpoint还有其他的作用。
三、 LSN(Log Sequence Number)
LSN实际上就是InnoDB使用的一个版本标记的计数,它是一个单调递增的值。数据页和redo log都有各自的LSN。我们可以根据数据页中的LSN值和redo log中LSN的值判断需要恢复的redo log的位置和大小。
四、 工作原理
好的,现在我们来看看redo log的工作,说白了,redo log就是存储了数据被修改后的值。当我们提交一个事务时,InnoDB会先去把要修改的数据写入日志,然后再去修改缓冲池里面的真正数据页。
我们着重看看redo log是怎么一步步写入磁盘的。redo log本身也由两部分所构成即重做日志缓冲(redo log buffer)和重做日志文件(redo log file)。这样的设计同样也是为了调和内存与磁盘的速度差异。InnoDB写入磁盘的策略可以通过innodb_flush_log_at_trx_commit这个参数来控制。
avatar

当该值为1时,当然是最安全的,但是数据库性能会受一定影响。
为0时性能较好,但是会丢失掉master thread还没刷新进磁盘部分的数据。
这里我想简单介绍一下master thread,这是InnoDB一个在后台运行的主线程,从名字就能看出这个线程相当的重要。它做的主要工作包括但不限于:刷新日志缓冲,合并插入缓冲,刷新脏页等。master thread大致分为每秒运行一次的操作和每10秒运行一次的操作。master thread中刷新数据,属于checkpoint的一种。所以如果在master thread在刷新日志的间隙,DB出现故障那么将丢失掉这部分数据。
当该值为2时,当DB发生故障能恢复数据。但如果操作系统也出现宕机,那么就会丢失掉,文件系统没有及时写入磁盘的数据。
这里说明一下,innodb_flush_log_at_trx_commit设为非0的值,并不是说不会在master thread中刷新日志了。master thread刷新日志是在不断进行的,所以redo log写入磁盘是在持续的写入。

四、 宕机恢复
DB宕机后重启,InnoDB会首先去查看数据页中的LSN的数值。这个值代表数据页被刷新回磁盘的LSN的大小。然后再去查看redo log的LSN的大小。如果数据页中的LSN值大说明数据页领先于redo log刷新回磁盘,不需要进行恢复。反之需要从redo log中恢复数据。
Redo log的结构
其实这一部分内容日常工作中很少涉及到,稍微了解一下就足够了。
一、 log block
Redo log的存储都是以 块(block) 为单位进行存储的,每个块的大小为512字节。同磁盘扇区大小一致,可以保证块的写入是原子操作。
块由三部分所构成,分别是 日志块头(log block header),日志块尾(log block tailer),日志本身。
日志头占用12字节,日志尾占用8字节。故每个块实际存储日志的大小为492字节。
二、 log group
一个日志文件由多个块所构成,多个日志文件形成一个重做日志文件组(redo log group)。不过,log group是一个逻辑上的概念,真实的磁盘上不会这样存储。

redoLog为什么这么快

1、顺序写,顺序io的速度远快于随机io

binLog

binlog 归档日志:(以后备份恢复用到)
(1)binlog 是 MySQL 的Server层实现的,所有引擎都可以使用。
(2)binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”.redo log 是物理日志,记录的是“在某个数据页上做了什么修改”
(3)binlog 是可以追加写入的。“追加写”是指binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”。
3. 如何恢复到半个月任意一秒的状态
保存半个月的binlog,定期备份
依次提取当时的binlog,按照时间顺序重放的到需要的时刻

总结:binlog是做备份的,binlog 中记录的是sql语句,或行的内容。当想恢复到之前几小时,几天或者几个月的数据时,就需要依次提取当时的binlog,按照时间顺序重放的到需要的时刻。

redo log 和 binlog 都是顺序写,比磁盘的随机写要快;

通常我们说的 MySQL 的 “双1” 操作,指的是 sync_binlog = 1 AND innodb_flush_log_at_trx_commit = 1 。innodb_flush_log_at_trx_commit 设置成 1 表示 redo log 在 prepare 阶段就需要持久化一次,那么 “双1” 配置 每个事务提交的时候都会刷盘 2 次,一次是 binlog,一次是 redo log。

从上面可以看出,每个客户端线程都有自己独立的 binlog cache,但是会共享一份 binlog files。

上面的 write 是指把binlog cache 写到文件系统的 page cache,并没有写入到磁盘中,因此速度较快。

fsync 是实际的写盘操作,占用磁盘的 IOPS。

write 和 fsync 的写入时机,是由sync_binlog 控制的:

1、sync_binlog=0:每次事务提交都只 write,不 fsync;

2、sync_binlog=1:每次事务提交都会fsync;

3、sync_binlog=N(N>1):每次提交事务都会 write,累计N 个后再执行 fsync。

在出现 IO 瓶颈的情况下,可以考虑将 sync_binlog 设置成一个大的值。比较常见的是将 N设置为 100~1000。但是存在的风险是,当主机异常重启时会丢失 N 个最近提交的事务 binlog。

为了控制 redo log 的写入策略,innodb_flush_log_at_trx_commit 会有下面 3 中取值:1、0:每次提交事务只写在 redo log buffer 中;2、1:每次提交事务持久化到磁盘;3、2:每次提交事务写到 文件系统的 Page cache 中。

当内核发起一个写请求时(例如进程发起write()请求),同样是直接往cache中写入,后备存储中的内容不会直接更新。内核会将被写入的page标记为dirty,并将其加入dirty list中。内核会周期性地将dirty list中的page写回到磁盘上,从而使磁盘上的数据和内存中缓存的数据一致。

redo log 实际的触发 fsync 操作写盘包含以下几个场景:1、后台每隔 1 秒钟的线程轮询;2、innodb_flush_log_at_trx_commit 设置成 1 时,事务提交时触发;3、innodb_log_buffer_size 是设置 redo log 大小的参数,当 redo log buffer 达到 innodb_log_buffer_size / 2 时,也会触发一次 fsync。

如何查看bin_log

(一) binlog介绍

binlog,即二进制日志,它记录了数据库上的所有改变,并以二进制的形式保存在磁盘中;

它可以用来查看数据库的变更历史、数据库增量备份和恢复、Mysql的复制(主从数据库的复制)。

(二) binlog格式

binlog有三种格式:Statement、Row以及Mixed。

–基于SQL语句的复制(statement-based replication,SBR),
–基于行的复制(row-based replication,RBR),
–混合模式复制(mixed-based replication,MBR)。

2.1 Statement
每一条会修改数据的sql都会记录在binlog中。

优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。

缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同 的结果。另外mysql 的复制,像一些特定函数功能,slave可与master上要保持一致会有很多相关问题。

ps:相比row能节约多少性能与日志量,这个取决于应用的SQL情况,正常同一条记录修改或者插入row格式所产生的日志量还小于Statement产生的日志量,但是考虑到如果带条件的update操作,以及整表删除,alter表等操作,ROW格式会产生大量日志,因此在考虑是否使用ROW格式日志时应该跟据应用的实际情况,其所产生的日志量会增加多少,以及带来的IO性能问题。

2.2 Row

5.1.5版本的MySQL才开始支持row level的复制,它不记录sql语句上下文相关信息,仅保存哪条记录被修改。

优点: binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题.

缺点:所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。

ps:新版本的MySQL中对row level模式也被做了优化,并不是所有的修改都会以row level来记录,像遇到表结构变更的时候就会以statement模式来记录,如果sql语句确实就是update或者delete等修改数据的语句,那么还是会记录所有行的变更。

2.3 Mixed

从5.1.8版本开始,MySQL提供了Mixed格式,实际上就是Statement与Row的结合。

在Mixed模式下,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。

2.4 查看与配置binlog格式

(1)查看binlog_format

show variables like ‘binlog_format’
(2)修改binlog_format

直接修改

set globle binlog_format=‘MIXED’
或者修改配置文件

vi /etc/my.cnf

(三) binlog开启与查看、删除

3.1 查看是否开启binlog

show variables like ‘log_bin’

如果binlog没有开启,可以通过set sql_log_bin=1命令来启用;如果想停用binlog,可以使用set sql_log_bin=0。

查看mysql数据存在了哪里

show global variables like "%datadir%";

查看binLog文件在哪里

show binary logs;
show binlog events in 'mysql-bin.000007';

命令

 mysqlbinlog --no-defaults --database=test1 --start-datetime="2021-09-17 07:21:09" --stop-datetime="2021-09-23 07:59:50" mysql-bin.000007 | more

(四) binlog的扩展

当停止或重启服务器时,服务器会把日志文件记入下一个日志文件,Mysql会在重启时生成一个新的日志文件,文件序号递增;此外,如果日志文件超过max_binlog_size(默认值1G)系统变量配置的上限时,也会生成新的日志文件(在这里需要注意的是,如果你正使用大的事务,二进制日志还会超过max_binlog_size,不会生成新的日志文件,事务全写入一个二进制日志中,这种情况主要是为了保证事务的完整性);日志被刷新时,新生成一个日志文件。
flush logs

undoLog

undo log的定义

undo log主要记录数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

undo log的作用

undo是一种逻辑日志,有两个作用:

事务回滚
MVCC
重点关注如何利用undo log进行事务回滚。

undo日志,只将数据库逻辑地恢复到原来的样子,在回滚的时候,它实际上是做的相反的工作,比如一条INSERT ,对应一条 DELETE,对于每个UPDATE,对应一条相反的 UPDATE,将修改前的行放回去。通过undo log进行事务回滚操作可以保障事务的原子性。

undo log的写入时机

DML操作修改聚簇索引前,记录undo log
二级索引记录的修改,不记录undo log
需要注意的是,undo log页面的修改,同样需要记录redo日志。
也就是说,sql执行,更新数据前,记录undoLog,如果聚簇索引变更,会记录undoLog,且undoLog的修改需要记录到redoLog中,这样undoLog就没必要每次操作都刷到磁盘中。

undo log的存储位置

在InnoDB存储引擎中,undo log存储在回滚段(Rollback Segment)中,每个回滚段记录了1024个undo log segment,而在每个undo log segment段中进行undo 页的申请,在5.6以前,Rollback Segment是在共享表空间里的,5.6.3之后,可通过 innodb_undo_tablespace设置undo存储的位置。

undo的类型

在InnoDB存储引擎中,undo log分为:

insert undo log
update undo log
insert undo log是指在insert 操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

而update undo log记录的是对delete 和update操作产生的undo log,该undo log可能需要提供MVCC机制,因此不能再事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

补充:purge线程两个主要作用是:清理undo页和清除page里面带有Delete_Bit标识的数据行。在InnoDB中,事务中的Delete操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而不删除记录。是一种"假删除",只是做了个标记,真正的删除工作需要后台purge线程去完成。

undo log 是否是redo log的逆过程?

undo log 是否是redo log的逆过程?其实从前文就可以得出答案了,undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子,而redo log是物理日志,记录的是数据页的物理变化,显然undo log不是redo log的逆过程。

redo & undo总结

下面是redo log + undo log的简化过程,便于理解两种日志的过程:

假设有A、B两个数据,值分别为1,2.

  1. 事务开始
  2. 记录A=1到undo log
  3. 修改A=3
  4. 记录A=3到 redo log
  5. 记录B=2到 undo log
  6. 修改B=4
  7. 记录B=4到redo log
  8. 将redo log写入磁盘
  9. 事务提交
    实际上,在insert/update/delete操作中,redo和undo分别记录的内容都不一样,量也不一样。在InnoDB内存中,一般的顺序如下:

写undo的redo
写undo
修改数据页
写Redo

MVCC机制

内容引用自此连接

MVCC实现

并且也能保证只会读取到符合标准的行,不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作

隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID, DB_ROLL_PTR, DB_ROW_ID 等字段

DB_TRX_ID
6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID
DB_ROLL_PTR
7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)
DB_ROW_ID
6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
实际还有一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了
avatar

Read View 读视图

什么是 Read View?

什么是 Read View,说白了 Read View 就是事务进行快照读操作的时候生产的读视图 (Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID (当每个事务开启时,都会被分配一个 ID , 这个 ID 是递增的,所以最新的事务,ID 值越大)

所以我们知道 Read View 主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID 跟 Read View 的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR 回滚指针去取出 Undo Log 中的 DB_TRX_ID 再比较,即遍历链表的 DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的 DB_TRX_ID , 那么这个 DB_TRX_ID 所在的旧记录就是当前事务能看见的最新老版本

在展示之前,我先简化一下 Read View,我们可以把 Read View 简单的理解成有三个全局属性

trx_list(名称我随意取的)
一个数值列表
用于维护 Read View 生成时刻系统 正活跃的事务 ID 列表
up_limit_id
lower water remark
是 trx_list 列表中事务 ID 最小的 ID
low_limit_id
hight water mark
ReadView 生成时刻系统尚未分配的下一个事务 ID ,也就是 目前已出现过的事务 ID 的最大值 + 1
为什么是 low_limit ? 因为它也是系统此刻可分配的事务 ID 的最小值

首先比较 DB_TRX_ID < up_limit_id , 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
接下来判断 DB_TRX_ID >= low_limit_id , 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
判断 DB_TRX_ID 是否在活跃事务之中,trx_list.contains (DB_TRX_ID),如果在,则代表我 Read View 生成时刻,你这个事务还在活跃,还没有 Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在 Read View 生成之前就已经 Commit 了,你修改的结果,我当前事务是能看见的

rr的事务级快照、rc的语句级快照,都是在查询语句开始时才拿的,而不是begin的时候

RC , RR 级别下的 InnoDB 快照读有什么不同?

正是 Read View 生成时机的不同,从而造成 RC , RR 级别下快照读的结果的不同

在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及 Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个 Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个 Read View,所以对之后的修改不可见;
即 RR 级别下,快照读生成 Read View 时,Read View 会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
而在 RC 级别下的,事务中,每次快照读都会新生成一个快照和 Read View , 这就是我们在 RC 级别下的事务中可以看到别的事务提交的更新的原因
总之在 RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR 隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。

mvcc为什么解决不了幻读

Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。
a事务先select,b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意dml操作),a事务再select出来的结果在MVCC下还和第一次select一样,接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此。

如果这样理解的话,Mysql的RR级别确实防不住幻读