事务有四种特性:原子性(A),一致性(C),隔离性(I),持久性(D),那么其中持久性就算靠redo log来完成的,原子性就是靠undo log来完成的。by way: 事务的隔离性由 锁机制 实现。一致性靠什么?我的理解是原子性、隔离性、持久性这三个特性的目的就是一致性。
1、redo log
redo log中文名就是重做日志,它是存储引擎层( (InnoDB)生成的日志,记录的是"物理级别"的页修改操作(数据库存储的最小单位:数据页),比如页号xx,偏移量yyy写入了'zzz'数据。在mysql服务宕机重启后,根据redo log来重写没有来得及持久化的数据。
(ps:undo log也是存储引擎层生成的,但是bin log不是,bin log是数据库层生成的)。
1.1 redo log生成的过程
以一个更新事务为例,redo log 流转过程,如下图所示:

第1步:将要修改的数据从磁盘读取到内存中,具体就是buffer pool(图中的data buffer)中,这是一个mysql专门存数据的内存划分区域;然后修改buffer pool中的内容
第2步:生成redo log写到另一块内存区域叫 redo log buffer 中,
第3步:当事务commit的时候,将redo log的内容写到物理磁盘的redo log 文件中。
第4步:定期将内存中修改的数据写入到磁盘中,也就是刷盘动作。
1.2 为什么要用redo log
可能有人就会有疑问,上面第三步事务提交为什么将内存中内容持久化到磁盘中呢?这样多简单、粗暴。那么这样多会有3个弊端的 :
1、修改量与刷新磁盘工作量严重不成比例:
有时候我们仅仅修改了某个页面中的一个字节,但是我们知道在InnoDB中是以页为单位来进行磁盘lo的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据到磁盘上显然是太小题大做了。
2、随机lo刷新较慢
一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,假如该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的Buffer Pool中的页面刷新到磁盘时需要进行很多的随机IO,随机Io比顺序IO要慢,尤其对于传统的机械硬盘来说。而写入redo log 文件时顺序写入,生成redo log文件的时候就会预留足够的大写,后面追加写入文件的时候都是顺序写了。
3、如果一个事务是写入很多的数据,比如10w条,那么写入redo log buffer是同步进行的,写入内存是很快的,如果是直接写入磁盘,那么会很耗时的,磁盘的io比内存慢很多很多。
所以mysql采用Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。
1.3 写入磁盘(刷盘)的策略(简化版)
redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
-
设置为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步) -
设置为1:表示每次事务提交时都将进行同步,刷盘操作(默认值) -
设置为2:表示每次事务提交时都只把 redo log buffer 内容写入page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。
输入show variables like 'innodb_flush_log_at_trx_commit';这个命令可以查看你的mysql设置的是哪个。
简单总结下:0最快,但是mysql服务宕机会丢失1s的数据,
1最慢,但是不会丢数据
2,速度居中,但是在服务器挂了之后会丢一秒的数据,(不是mysql服务)但是服务器宕机的几率远小于mysql服务宕机。
2、undo log
undo log也是存储引擎层生成的日志,它记录的是逻辑操作日志,比如你insert 了一条数据,它就一条与之相反的delete操作。主要用于事务的回滚(undo log 记录的是每个修改操作的逆操作)和一致性非锁定读(undo log回滚行记录到某种特定的版本---MVCC,即多版本并发控制)
2.1 undo的类型
在InnoDB存储引擎中,undo log分为:
-
insert undo log
insert undo log是指在insert操作中产生的undo log。因为insert操作的记录,只对事务本身可见,对其他事务不可见(这是事务隔离性的要求),故该undo log可以在事务提交后直接删除。不需要进行purge操作。
-
update undo log
update undo log记录的是对delete和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能 在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
2.2 undo log的生命周期
1. 简要生成过程
以下是undo+redo事务的简化过程
假设有2个数值,分别为A=1和B=2,然后将A修改为3,B修改为4
1. start transaction;
2.记录A=1到undo log;
3. update A = 3;
4.记录A=3 到redo log;
5.记录 B=2到undo loq;
6. update B = 4;
7.记录B = 4到redo log;
8.将redo log刷新到磁盘;
9. commit
-
在1-8步骤的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响。
-
如果在8-9之间宕机。
-
redo log 进行恢复
-
undo log 发现有事务没完成进行回滚。
-
-
若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据redo log把数据刷回磁盘。

拓展1:redo log写入磁盘(刷盘)的策略(详细版)
redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以 一定的频率 刷入到真正的redo log file 中。这里的一定频率怎么看待呢?这就是我们要说的刷盘策略。

注意,redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
-
设置为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步) -
设置为1:表示每次事务提交时都将进行同步,刷盘操作(默认值) -
设置为2:表示每次事务提交时都只把 redo log buffer 内容写入page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。show variables like 'innodb_flush_log_at_trx_commit'; mysql> show variables like 'innodb_flush_log_at_trx_commit'; +--------------------------------+-------+ | Variable_name | Value | +--------------------------------+-------+ | innodb_flush_log_at_trx_commit | 1 | +--------------------------------+-------+ 1 row in set (0.00 sec)
另外,InnoDB存储引擎有一个后台线程,每隔1秒,就会把redo log buffer中的内容写到文件系统缓存( page cache ),然后调用刷盘操作。

也就是说,一个没有提交事务的redo log记录,也可能会刷盘。因为在事务执行过程redo log记录是会写入redo log buffer 中,这些redo log记录会被后台线程刷盘。

除了后台线程每秒1次的轮询操作,还有一种情况,当redo log buffer占用的空间即将达到innodb_log_buffer_size(这个参数默认是16M)的一半的时候,后台线程会主动刷盘。
不同刷盘策略演示
1.流程图

除了1秒刷盘,提交了也刷盘。效率差一些。
小结: innodb_flush_log_at_trx_commit=1
为1时,只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。
如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。可以保证ACID的D,数据绝对不会丢失,但是效率最差的。
建议使用默认值,虽然操作系统宕机的概率理论小于数据库宕机的概率,但是一般既然使用了事务,那么数据的安全相对来说更重要些。|

除了1s 强制刷盘,,page cache 由系统决定啥时候刷盘
小结: innodb_flush_log_at_trx_commit=2
为2时,只要事务提交成功,
redo log buffer中的内容只写入文件系统缓存( page cache ) 。如果仅仅只是
MySQL挂了不会有任何数据丢失,但是操作系统宕机可能会有1秒数据的丢失,这种情况下无法满足ACID中的D。但是数值2肯定是效率最高的。

2.举例

可以看到:
-
1最慢 但最安全
-
0 最快最不安全
-
2 折中。
拓展2:undolog的详细生成过程
对于InnoDB引擎来说,每个行记录除了记录本身的数据之外,还有几个隐藏的列:
-
DB_ROW_ID: 如果没有为表显式的定义主键,并且表中也没有定义唯一索引,那么InnoDB会自动为表添加一个row_id的隐藏列作为主键。 -
DB_TRX_ID︰每个事务都会分配一个事务ID,当对某条记录发生变更时,就会将这个事务的事务ID写入trx_id中。疑问, 就一个字段,如果有两个事务怎么办。两个事务会不会有锁呢?
-
DB_ROLL_PTR:回滚指针,本质上就是指向undo log的指针。(一个事务多个操作的话,undo log就与多条,它里面存的一个字段就指向上一条undolog,这样就形成了一个链表,回滚指针记录的最新的undo log的指针)
ps:记住DB_TRX_ID和DB_ROLL_PTR,这个是实现MVCC的重要组成部分

当我们执行INSERT时:
begin;
INSERT INTO user (name) VALUES ("tom");
插入的数据都会生成一条insert undo log,并且数据的回滚指针会指向它。undo log会记录undo log的序号、插入主键的列和值...,那么在进行rollback的时候,通过主键直接把对应的数据删除即可。

当我们执行UPDATE时:
对于更新的操作会产生update undo log,并且会分更新主键的和不更新主键的,假设现在执行:
UPDATE user SET name= "Sun" WHERE id=1;

这时会把老的记录写入新的undo log,让回滚指针指向新的undo log,它的undo no是1,并且新的undo log会指向老的undo log (undo no=0)。
假设现在执行:
UPDATE user SET id=2 WHERE id=1;

对于更新主键的操作,会先把原来的数据deletemark标识打开,这时并没有真正的删除数据,真正的删除会交给清理线程去判断,然后在后面插入一条新的数据,新的数据也会产生undo log,并且undo log的序号会递增。
可以发现每次对数据的变更都会产生一个undo log,当一条记录被变更多次时,那么就会产生多条undo log,undo log记录的是变更前的日志,并且每个undo log的序号是递增的,那么当要回滚的时候,按照序号依次向前推,就可以找到我们的原始数据了。
undo log是如何回滚的
以上面的例子来说,假设执行rollback,那么对应的流程应该是这样:
-
通过undo no=3的日志把id=2的数据删除
-
通过undo no=2的日志把id=1的数据的deletemark还原成0
-
通过undo no=1的日志把id=1的数据的name还原成Tom
-
通过undo no=0的日志把id=1的数据删除
undo log的删除
-
针对于insert undo log
因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。
-
针对于update undo log
该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
补充:
purge线程两个主要作用是:
清理undo页和清除page里面带有Delete_Bit标识的数据行。仕InnoDB中,事分中的Delete操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而 不删除记录。是一种"假删除"只是做了个标记,真正的删除工作需要后台purge线程去完成。