• 售前

  • 售后

热门帖子
入门百科

MySQL中的redo log和undo log日志详解

[复制链接]
却写杂布计 显示全部楼层 发表于 2021-8-14 14:16:35 |阅读模式 打印 上一主题 下一主题
MySQL日记系统中最重要的日记为重做日记redo log和归档日记bin log,后者为MySQL Server层的日记,前者为InnoDB存储引擎层的日记。
1 重做日记redo log

1.1 什么是redo log

redo log用于包管事务的长期性,即ACID中的D。
长期性:指一个事务一旦被提交,它对数据库中数据的改变就是永世性的,接下来即使数据库发生故障也不应该对其有任何影响。
redo log有两种范例,分别为物理重做日记和逻辑重做日记。在InnoDB中redo log大多数情况下是一个物理日记,记录数据页面的物理变化(现实的数据值)。
1.2 redo log的功能

redo log的主要功能是用于数据库瓦解时的数据规复。
1.3 redo log的构成

redo log可以分为以下两部分
存储在内存中的重做日记缓冲区存储在磁盘上的重做日记文件

1.4 记录redo log的时机

在完成数据的修改之后,脏页刷入磁盘之前写入重做日记缓冲区。即先修改,再写入。
脏页:内存中与磁盘上不同等的数据(并不是坏的!)
在以下情况下,redo log由重做日记缓冲区写入磁盘上的重做日记文件。
       
  • redo log buffer的日记占据redo log buffer总容量的一半时,将redo log写入磁盘。   
  • 一个事务提交时,他的redo log都刷入磁盘,这样可以包管数据绝不丢失(最常见的情况)。留意这时内存中的脏页可能尚未全部写入磁盘。   
  • 后台线程定时革新,有一个后台线程每过一秒就将redo log写入磁盘。   
  • MySQL关闭时,redo log都被写入磁盘。
第一种情况和第四种情况肯定会实行redo log的写入,第二种情况和第三种情况的实行要根据参数
  1. innodb_flush_log_at_trx_commit
复制代码
的设定值,在下文会有详细形貌。
索引的创建也需要记录redo log。
1.5 一个重做全过程的示例


以更新事务为例。
       
  • 将原始数据读入内存,修改数据的内存副本。   
  • 天生redo log并写入重做日记缓冲区,redo log中存储的是修改后的新值。   
  • 事务提交时,将重做日记缓冲区中的内容革新到重做日记文件。   
  • 随后正常将内存中的脏页刷回磁盘。
1.6 长期性的包管

1.6.1 Force Log at Commit机制
Force Log at Commit机制实现了事务的长期性。在内存中操作时,日记被写入重做日记缓冲区。但在事务提交之前,必须起首将所有日记写入磁盘上的重做日记文件。
为了确保每个日记都写入重做日记文件,必须利用一个fsync系统调用,确保OS buffer中的日记被完整地写入磁盘上的log file。
fsync系统调用:需要你在入参的位置上转达给他一个fd,然后系统调用就会对这个fd指向的文件起作用。fsync会确保一直到写磁盘操作竣事才会返回,所以当你的步伐利用这个函数而且它乐成返回时,就阐明数据肯定已经安全的落盘了。所以fsync适合数据库这种步伐。

1.6.2 innodb_flush_log_at_trx_commit参数
InnoDB提供了一个参数
  1. innodb_flush_log_at_trx_commit
复制代码
控制日记革新到磁盘的策略。
       
    1. innodb_flush_log_at_trx_commit
    复制代码
    值为1时(默认)。事务每次提交都必须将log buffer中的日记写入os buffer并调用fsync()写入磁盘中。
这种方式即使系统瓦解也不会丢失任何数据,但是由于每次提交都写入磁盘,IO性能较差。
       
    1. innodb_flush_log_at_trx_commit
    复制代码
    值为0时。事务提交时不将log buffer写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。
这现实上相当于在内存中维护了一个用户计划的缓冲区,它减少了和os buffer之间的数据传输,有更好的性能。
每秒写入磁盘,系统瓦解会丢失1s的数据。
       
    1. innodb_flush_log_at_trx_commit
    复制代码
    值为2时。每次提交都仅写入os buffer,然后每秒调用fsync()将os buffer中的日记写入到log file on disk中。
固然说我们是每秒调用fsync()将os buffer中的日记写入到log file on disk中,但是寻常即使不调用fsync,数据也会2自主地逐渐进入磁盘。所以当发生系统瓦解,相比第二种情况,会丢失较少的数据。
但同时,由于每次提交都写入os buffer,所以相比第二种情况,性能会差一些,但还是比第一种好的。
无论是哪种情况

1.6.3 一个小的性能测试
几个选项之间的性能差距是极大的,下面做一个简朴的测试。
  1. #创建测试表
  2. drop table if exists test_flush_log;
  3. create table test_flush_log(id int,name char(50))engine=innodb;
  4. #创建插入指定行数的记录到测试表中的存储过程
  5. drop procedure if exists proc;
  6. delimiter $$
  7. create procedure proc(i int)
  8. begin
  9.     declare s int default 1;
  10.     declare c char(50) default repeat('a',50);
  11.     while s<=i do
  12.         start transaction;
  13.         insert into test_flush_log values(null,c);
  14.         commit;
  15.         set s=s+1;
  16.     end while;
  17. end$$
  18. delimiter ;
复制代码
下面均插入十万条记录。
Ⅰ 当
  1. innodb_flush_log_at_trx_commit
复制代码
值为1时
  1. test> call proc(100000)
  2. [2021-07-25 13:22:02] completed in 27 s 350 ms
复制代码
需要长达27.35s。
Ⅱ 当
  1. innodb_flush_log_at_trx_commit
复制代码
值为2时
  1. test> set @@global.innodb_flush_log_at_trx_commit=2;   
  2. test> truncate test_flush_log;
  3. test> call proc(100000)
  4. [2021-07-25 13:27:33] completed in 5 s 774 ms
复制代码
只需5.774s,性能大大提升。
Ⅲ 当
  1. innodb_flush_log_at_trx_commit
复制代码
值为0时
  1. test> set @@global.innodb_flush_log_at_trx_commit=0;
  2. test> truncate test_flush_log;
  3. test> call proc(100000)
  4. [2021-07-25 13:30:34] completed in 3 s 537 ms
复制代码
只需3.537s,性能更高。
显然,
  1. innodb_flush_log_at_trx_commit
复制代码
值为1时性能差得非常显着,改为0和2后性能都有大幅提升,其中0更快但相比2提升不大。
固然改为0和2可以大幅提升性能,但会严重影响安全性。我们可以通过修改存储过程,将事务的创建和提交放到循环外,同一提交,减少了IO频率。
  1. drop procedure if exists proc;
  2. delimiter $$
  3. create procedure proc(i int)
  4. begin
  5.     declare s int default 1;
  6.     declare c char(50) default repeat('a',50);
  7.     start transaction;
  8.     while s<=i DO
  9.         insert into test_flush_log values(null,c);
  10.         set s=s+1;
  11.     end while;
  12.     commit;
  13. end$$
  14. delimiter ;
复制代码
1.6.4 迷你事务mini-transaction
mini-trasaction是InnoDB处置惩罚小型事务时利用的一种机制,它可以确保并发事务操作和数据库非常发生时,数据页中的数据同等性。
迷你事务必须遵循下面三个协议:
       
  • FIX规则。写时必须利用独占锁,读时必须利用共享锁。反正就是要锁住。   
  • 预写日记。预写日记即WAL,Write-Ahead Log。长期化数据之前,必须先长期化内存中的日记。每个页面都有一个LSN(日记序列号)。在将数据写入磁盘前,要先将内存中序列号小于LSN的日记写入磁盘。WAL提供三种长期化模式
最严格的是full-sync,fsync包管在返回之前将记录革新到磁盘,最大化了数据的安全性。

第二个级别是write-only,包管记录写入操作系统。这允许数据在历程级别的瓦解后幸存。

最不严格的是no-sync,将记录生存在内存缓冲区中,不包管立即写入文件系统。

逼迫日记再提交。即Force-log-at-commit,它要求提交事务时必须把所有迷你事务日记革新到磁盘。
1.7 写redo log的过程


如上图,展示了redo log是怎样被写入log buffer的。每个mini-trasaction对应于每个DML操作,比方更新语句等。
       
  • 每个数据修改后被写入迷你事务私有缓冲区。   
  • 当更新语句完成,redo log从迷你事务私有缓冲区被写入内存中的公共日记缓冲区。   
  • 提交外部事务时,会将重做日记缓冲区刷入重做日记文件。
1.8 日记块 log block

redo log以块为单位进行存储,每个块巨细为512字节。无论是在内存重做日记缓冲区、操作系统缓冲区还是重做日记文件中,都是以这样的512字节巨细的块进行存储的。

每个日记块头由以下四个部分构成
       
  • log_block_hdr_no:(4字节)该日记块在redo log buffer中的位置ID。   
  • log_block_hdr_data_len:(2字节)该log block中已记录的log巨细。写满该log block时为0x200,表现512字节。   
  • log_block_first_rec_group:(2字节)该log block中第一个log的开始偏移位置。   
  • lock_block_checkpoint_no:(4字节)写入查抄点信息的位置。
1.9 log group

log group代表redo log的分组,由多个巨细相同的redo log file构成。由一个参数
  1. innodb_log_files_group
复制代码
决定,默认为2。
[外链图片转存失败,源站可能有防盗img-qAyaSeL3543740G:61311akw89MySQL[外链图片转存失败,源站可能有防盗链机制,建议将图片生存下来直接上传(img-h01w68EG-1627284031849)(G:\markdown\MySQL\image-20210726131134489.png)].png)]
这个group是逻辑上的概念,但可以通过变量
  1. innodb_log_group_home_dir
复制代码
来定义组的目录,redo log file都放在这个目录下,默认是在datadir下。

2 取消日记undo log
2.1 关于undo log
undo log存在的意义是确保数据库事务的原子性。
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
       
  • edo log记录了事务的行为,可以很好地包管同等性,对数据进行“重做”操作。但事务偶然还需要进行“回滚”操作,这时就需要undo log。当我们对记录做了变动操作的时间就需要产生undo log,其中记录的是老版本的数据,当旧事务需要读取数据时,可以顺着undo链找到满足其可见性地记录。   
  • undo log通常以逻辑日记的情势存在。我们可以认为当delete一条记录时,undo log会产生一条对应的insert记录,反之亦然。当update一条记录时,会产生一条相反的update记录。   
  • undo log接纳段segment的方式来记录,每个undo操作在记录的时间占用一个undo log segment。   
  • undo log也会产生redo log,由于undo log也要实现长期性掩护。
undo log通常以逻辑日记的情势存在。我们可以认为当delete一条记录时,undo log会产生一条对应的insert记录,反之亦然。当update一条记录时,会产生一条相反的update记录。
undo log接纳段segment的方式来记录,每个undo操作在记录的时间占用一个undo log segment。
undo log也会产生redo log,由于undo log也要实现长期性掩护。
2.2 undo log segment

为了包管事务并发操作时,写各自的undo log时不发生冲突,nnodb用段的方式管理undo log。rollback segment称为回滚段,每个回滚段中有1024个undo log segment。MySQL5.5以后的版本支持128个rollback segment,就可以存储128*1024个操作,还可以通过
  1. innodb_undo_logs
复制代码
参数定义盯梢个rollback segment。

2.3 purge

在聚集索引列的操作中,MySQL是这样计划的。对一条delete语句
  1. delete from t where a = 1
复制代码
如果a有聚集索引(主键),那么不会进行真正的删除,而是在主键列即是1的记录处设置delete flag为1,即把记录生存在B+树中。同理,对于update操作,不是直接更新记录,而是把旧记录标识为删除,再创建一条新记录。
那么,旧版本记录什么时间真正的删除呢?
InnoDB利用undo日记进行旧版本的删除操作,这个操作称为purge操作。InnoDB开发了purge线程进行purge操作,而且可以控制purge线程的数目,每个purge线程每10s 进行一次purge操作。
InnoDB的undo log计划
一个页上允许多个事务的undo log存在,undo log的存储顺序是随时的。InnoDB维护了一个history链表,按照事务提交的顺序将undo log进行连接。

在实行purge过程中,InnoDB存储引擎起首从history list中找到第一个需要被清理的记录,这里为trx1,清理之后InnoDB存储引擎会在trx1地点的Undo page中继续探求是否存在可以被清理的记录,这里会找到事务trx3,接着找到trx5,但是发现trx5被其他事务所引用而不能清理,故再去history list中取查找,发现最尾端的记录时trx2,接着找到trx2地点的Undo page,依次把trx6、trx4清理,由于Undo page2中所有的记录都被清理了,因此该Undo page可以进行重用。
InnoDB存储引擎这种先从history list中找undo log,然后再从Undo page中找undo log的计划模式是为了制止大量随机读操作,从而提高purge的服从。
3 InnoDB的规复操作

3.1 数据页刷盘的规则和checkpoint

内存中(buffer pool)未刷到磁盘的数据称为脏数据(dirty data)。由于数据和日记都以页的情势存在,所以脏页表现脏数据和脏日记。
在InnoDB中,checkpoint是数据刷盘的唯一规则。checkpoint触发后,会将内存中的脏数据刷到磁盘。
innodb存储引擎中checkpoint分为两种:
       
  • sharp checkpoint:在重用redo log文件(比方切换日记文件)的时间,将所有已记录到redo log中对应的脏数据刷到磁盘。   
  • fuzzy checkpoint:一次只刷一小部分的日记到磁盘,而非将所有脏日记刷盘。有以下几种情况会触发该查抄点:
master thread checkpoint。由master线程控制,每秒或每10秒刷入肯定比例的脏页到磁盘。
flush_lru_list checkpoint。从MySQL5.6开始可通过 innodb_page_cleaners 变量指定专门负责脏页刷盘的page cleaner线程的个数,该线程的目的是为了包管lru列表有可用的空闲页。
async/sync flush checkpoint。同步刷盘还是异步刷盘。比方还有非常多的脏页没刷到磁盘(非常多是多少,有比例控制),这时间会选择同步刷到磁盘,但这很少出现;如果脏页不是许多,可以选择异步刷到磁盘,如果脏页很少,可以临时不刷脏页到磁盘
dirty page too much checkpoint。脏页太多时逼迫触发查抄点,目的是为了包管缓存有足够的空闲空间。too much的比例由变量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默认的值为75,即当脏页占缓冲池的百分之75后,就逼迫刷一部分脏页到磁盘。
由于刷脏页需要肯定的时间来完成,所以记录查抄点的位置是在每次刷盘竣事之后才在redo log中标记的。
3.2 LSN

3.2.1 LSN概念
LSN称为日记的逻辑序列号,在InnoDB中占用8个字节
我们可以通过LSN相识到下面这些信息:
       
  • 数据页的版本信息。   
  • 写入的日记总量。   
  • 查抄点的位置。
在下面两个位置存在LSN:
       
  • redo log的记录中。   
  • 每个数据页的头部有一个变量
    1. fil_page_lsn
    复制代码
    记录了本页终极的LSN值是多少。
显然,如果页中的LSN值小于redo log中的LSN值,阐明数据出现了丢失。
通过
  1. show engine innodb status
复制代码
可以检察当前InnoDB的运行信息,其中有一栏log中有关于lsn的记录。

       
  • log sequence number记录了当前的redo log(in buffer)中的LSN。   
  • log flushed up to是刷到磁盘重做日记文件中的LSN。   
  • pages flushed up to是已经刷到磁盘数据页上的LSN。   
  • last checkpoint at是上一次查抄点地点位置的LSN。
3.2.2 LSN处置惩罚流程
(1).起首修改内存中的数据页,并在数据页中记录LSN,临时称之为data_in_buffer_lsn;
(2).而且在修改数据页的同时(几乎是同时)向redo log in buffer中写入redo log,并记录下对应的LSN,临时称之为redo_log_in_buffer_lsn;
(3).写完buffer中的日记后,当触发了日记刷盘的几种规则时,会向redo log file on disk刷入重做日记,并在该文件中记下对应的LSN,临时称之为redo_log_on_disk_lsn;
(4).数据页不可能永久只停留在内存中,在某些情况下,会触发checkpoint来将内存中的脏页(数据脏页和日记脏页)刷到磁盘,所以会在本次checkpoint脏页刷盘竣事时,在redo log中记录checkpoint的LSN位置,临时称之为checkpoint_lsn。
(5).要记录checkpoint地点位置很快,只需简朴的设置一个标记即可,但是刷数据页并不肯定很快,比方这一次checkpoint要刷入的数据页非常多。也就是说要刷入所有的数据页需要肯定的时间来完成,中途刷入的每个数据页都会记下当前页地点的LSN,临时称之为data_page_on_disk_lsn。

上图中,从上到下的横线分别代表:时间轴、buffer中数据页中记录的LSN(data_in_buffer_lsn)、磁盘中数据页中记录的LSN(data_page_on_disk_lsn)、buffer中重做日记记录的LSN(redo_log_in_buffer_lsn)、磁盘中重做日记文件中记录的LSN(redo_log_on_disk_lsn)以及查抄点记录的LSN(checkpoint_lsn)。
假设在最初时(12:0:00)所有的日记页和数据页都完成了刷盘,也记录好了查抄点的LSN,这时它们的LSN都是完全同等的。
假设此时开启了一个事务,并立即实行了一个update操作,实行完成后,buffer中的数据页和redo log都记录好了更新后的LSN值,假设为110。这时间如果实行 show engine innodb status 检察各LSN的值,即图中①处的位置状态,效果会是:
  1. log sequence number(110) > log flushed up to(100) = pages flushed up to = last checkpoint at
复制代码
之后又实行了一个delete语句,LSN增长到150。比及12:00:01时,触发redo log刷盘的规则(其中有一个规则是 innodb_flush_log_at_timeout 控制的默认日记刷盘频率为1秒),这时redo log file on disk中的LSN会更新到和redo log in buffer的LSN一样,所以都即是150,这时 show engine innodb status ,即图中②的位置,效果将会是:
  1. log sequence number(150) = log flushed up to > pages flushed up to(100) = last checkpoint at
复制代码
再之后,实行了一个update语句,缓存中的LSN将增长到300,即图中③的位置。
假设随后查抄点出现,即图中④的位置,正如前面所说,查抄点会触发数据页和日记页刷盘,但需要肯定的时间来完成,所以在数据页刷盘还未完成时,查抄点的LSN还是上一次查抄点的LSN,但此时磁盘上数据页和日记页的LSN已经增长了,即:
  1. log sequence number > log flushed up to 和 pages flushed up to > last checkpoint at
复制代码
但是log flushed up to和pages flushed up to的巨细无法确定,由于日记刷盘可能快于数据刷盘,也可能即是,还可能是慢于。但是checkpoint机制有掩护数据刷盘速率是慢于日记刷盘的:当数据刷盘速率凌驾日记刷盘时,将会临时制止数据刷盘,等待日记刷盘进度凌驾数据刷盘。
比及数据页和日记页刷盘完毕,即到了位置⑤的时间,所有的LSN都即是300。
随着时间的推移到了12:00:02,即图中位置⑥,又触发了日记刷盘的规则,但此时buffer中的日记LSN和磁盘中的日记LSN是同等的,所以不实行日记刷盘,即此时 show engine innodb status 时各种lsn都相称。
随后实行了一个insert语句,假设buffer中的LSN增长到了800,即图中位置⑦。此时各种LSN的巨细和位置①时一样。
随后实行了提交动作,即位置⑧。默认情况下,提交动作会触发日记刷盘,但不会触发数据刷盘,所以 show engine innodb status 的效果是:
  1. log sequence number = log flushed up to > pages flushed up to = last checkpoint at
复制代码
最后随着时间的推移,查抄点再次出现,即图中位置⑨。但是这次查抄点不会触发日记刷盘,由于日记的LSN在查抄点出现之前已经同步了。假设这次数据刷盘速率极快,快到一刹时内完成而无法捕捉到状态的变化,这时 show engine innodb status 的效果将是各种LSN相称。
3.3 InnoDB的规复行为

启动InnoDB时,肯定会进行规复操作,无论上次是由于什么缘故起因退出。
checkpoint表现已经完整刷到磁盘上data page上的LSN,因此规复时仅需要规复从checkpoint开始的日记部分。比方,当数据库在上一次checkpoint的LSN为10000时宕机,且事务是已经提交过的状态。启动数据库时会查抄磁盘中数据页的LSN,如果数据页的LSN小于日记中的LSN,则会从查抄点开始规复。
还有一种情况,在宕机前正处于checkpoint的刷盘过程,且数据页的刷盘进度凌驾了日记页的刷盘进度。这时间一宕机,数据页中记录的LSN就会大于日记页中的LSN,在重启的规复过程中会查抄到这一情况,这时超出日记进度的部分将不会重做,由于这本身就表现已经做过的事情,无需再重做。
另外,事务日记具有幂等性,所以多次操作得到同一效果的行为在日记中只记录一次。而二进制日记不具有幂等性,多次操作会全部记录下来,在规复的时间会多次实行二进制日记中的记录,速率就慢得多。比方,某记录中id初始值为2,通过update将值设置为了3,厥后又设置成了2,在事务日记中记录的将是无变化的页,根本无需规复;而二进制会记录下两次update操作,规复时也将实行这两次update操作,速率比事务日记规复更慢。
到此这篇关于MySQL中的redo log和undo log的文章就先容到这了,更多干系MySQL中的redo log和undo log内容请搜索草根技能分享从前的文章或继续欣赏下面的干系文章渴望大家以后多多支持草根技能分享!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作