目录
事务的隔离性由锁机制实现。
而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。它们在存储引擎层产生。
REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。 有的DBA或许会认为 UNDO 是 REDO 的逆过程,其实不然。
事务包含 持久性 的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩 溃,这个事务对数据库中所做的更改也不能丢失。
保证持久性的 一个简单的做法 :在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:
1.修改量可能跟刷新磁盘的工作量严重不成比例。因为一个页面默认16KB,假设我们只改动了一个字节,那刷盘就需要刷新16KB的数据到磁盘,小题大做。
2.随机IO刷新慢,事务所修改的页面可能并不相邻,直接刷盘会产生很多随机IO。
另一个解决的思路 :我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把 修改 了哪些东西 记录一下 就好。
redo日志降低了刷盘频率
redo日志占用的空间非常小
redo日志是顺序写入磁盘的
事务执行过程中,redo log不断记录
可分为两部分:
1.重做日志缓冲(redo log buffer),保存在内存中。
服务器启动时会申请一大片连续内存空间,就是redo日志的缓冲区,这片空间又被分成若干block,每个占用512字节的大小。
redo log buffer 大小:默认 16M ,最大值是4096M,最小值为1M。
参数设置:innodb_log_buffer_size;
2.重做日志文件(redo log file),保存在硬盘中,是持久的。
以一个更新事务为例,redo log 流转过程,如下图所示:
第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝
第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值
第3步:当事务commit时,将redo log buffer中的内容刷新到 redo log file,对 redo log file采用追加写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
在持久化一个数据页之前,先将内存中相应的日志页持久化。
redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以一定的频率刷入到真正的redo log file 中。
注意,redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到 文件系统缓存 (page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给OS自己来决定(比如page cache足够大了)。
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自己决定什么时候同步到磁盘文件。
如果日志满了怎么办?
采用循环使用的方式向redo日志文件组里写数据会导致后写入的redo日志覆盖掉前边写的redo日志,所以InnoDB的设计者提出了checkpoint的概念。
如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。
redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中 更新数据 的 前置操作 其实是要 先写入一个 undo log 。
事务需要保证 原子性 ,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:
情况一:事务执行过程中可能遇到各种错误,比如服务器本身的错误,操作系统错误 ,甚至是突然断电导致的错误。
情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。
以上情况出现,我们需要把数据改回原先的样子,这个过程称之为 回滚 ,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合 原子性 要求。
作用1:回滚数据
undo并不是将数据库物理地恢复到原来的样子,它是逻辑日志,只是将数据库逻辑地恢复到原来的样子,但数据结构和页本身可能已经被改变了。
作用2:MVCC
innoDB中地MVCC是通过undo来完成的。当用户读取一条行记录时,如果记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,实现非锁定读取。
InnoDB对undo log的管理采用段的方式,也就是 回滚段(rollback segment) 。
每个回滚段记录了 1024 个 undo log segment ,而在每个undo log segment段中进行 undo页 的申请。 在 InnoDB1.1版本之前 (不包括1.1版本),只有一个rollback segment,因此支持同时在线的事务 限制为 1024 。虽然对绝大多数的应用来说都已经够用。 从1.1版本开始InnoDB支持最大 128个rollback segment ,故其支持同时在线的事务限制提高到 了 128*1024 。
undo页的重用:
当开启一个事务需要写undo log 时,要先去undo log segment中申请空闲位置,当有空位的时候申请undo页,在undo页中进行undo log的写入。如果为每一个事务都分配一个页将会是非常浪费的,于是undo页就被设计为可重用的,当事务提交时并不会立刻删除undo页,这个undo页可能混杂着其他事务的undo log。undo log在事务提交之后会放入一个链表中,然后判断undo页的使用是否超过3/4,没有超过的话就表示该页可以重用,暂时不会被回收。其他事务的undo log可以记录在当前undo页的后面。undo log是离散的。
1. 每个事务只会使用一个回滚段,一个回滚段在同一时刻可能会服务于多个事务。
2. 当一个事务开始的时候,会制定一个回滚段,在事务进行的过程中,当数据被修改时,原始数据会被复制到回滚段。
3. 在回滚段中,事务会不断填充盘区,直到事务结束或所有的空间被用完。如果当前的盘区不够用,事务会在段中请求扩展下一个盘区,如果所有已分配的盘区都被用完,事务会覆盖最初的盘区或者在回滚段允许的情况下扩展新的盘区来使用。
4. 回滚段存在于undo表空间中,在数据库中可以存在多个undo表空间,但同一时刻只能使用一个 undo表空间。
5. 当事务提交时,InnoDB存储引擎会做以下两件事情:
将undo log放入列表中,以供之后的清除操作
判断undo log所在的页是否可以重用,若可以分配给下个事务使用
回滚段中的数据分类
1. 未提交的回滚数据(uncommitted undo information)-不能被其他事务的数据覆盖
2. 已经提交但未过期的回滚数据(committed undo information)
-过期之前不能被其他事务的数据覆盖
3. 事务已经提交并过期的数据(expired undo information)-会被优先覆盖掉
在InnoDB存储引擎中,undo log分为:
insert undo log-插入操作产生的undo log,事务提交之后就删除
update undo log-更新、删除操作产生的undo log,它可能需要提供MVCC机制,所以提交后放入链表中,等待purge线程来删除,不会立刻删除。
undo log的生命周期:
在更新Buffer Pool中的数据之前,我们需要先将该事务开始之前的状态写入undo log中。假设更新到一半出错了,我们就可以通过undo log回滚到事务开始前的状态。
undo log 的详细生成与回滚过程:
对于InnoDb引擎,每个行记录有这样两个隐藏列:事务ID、回滚指针(指向undo log)。
当我们插入一条数据时,会生成一条undo log,回滚指针指向它。
接着更新“姓名”字段,会再生成一条新的undo log,回滚指针指向新的undo log,新的undo log指向旧的。
假如我们再改变主键值,此时会把原来数据的deletemark标志打开,但并没有真正删除数据。然后在后面插入一条新数据,新数据也会产生undo log。
每次对数据的更改都会产生一个undo log,且序号是递增的。
回滚时按照序号依次向前推就能找到原始数据。
undo log是逻辑日志,对事务回滚时,只是将数据库逻辑地恢复到原来的样子。
redo log是物理日志,记录的是数据页的物理变化,undo log不是redo log的逆过程