说起数据库事务,绕不开的就是两点,一个是事务的特性,另一个就是事务的隔离机制。
至于事务的隔离机制,之前已经分析过很多,传送门1,传送门2
今天我们来说一说数据库特性,有人会说了,数据库特性有什么啊,不就是ACID嘛,A-原子性,C-一致性,I-隔离性,D-持久性。那好,我问你那数据库是基于什么玩意儿去保证它的呢,比如怎么保证它的原子性,或者怎么保证它的持久性呢~~~~没想过的是不是懵逼了,别急,今天我们就是要去深入了解它,不过本文先申明,对于这个也是学习,并不是个人经验分享或者是个人感悟分享。
1. Redo log
Redo log叫做重做日志,主要用来实现事务的持久性,即D。由两部分组成:
1. 内存中的重做日志缓冲(redo log buffer),缺点是易失。
2. 重做日志(redo log file)特点是持久的。
1.1 过程:
事务的存储引擎是InnoDB,通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做到日志文件进行持久化,待事务的提交操作完成之后才算完成。
由于重做日志文件打开没有使用O_DIRECT选项,因此重做日志缓冲先写入到文件系统缓存。为了确保写入磁盘,必须进行一次fsync操作。因此fsync的效率取决于磁盘的性能,决定了事务提交的性能。
1.2 示例图
1.3 重做策略
innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略:
0:表示事务提交时不进行重做日志的刷新,该操作只在Master Thread中完成(Master Thread每1s会进行一次fsync操作)。
1:默认策略,每次事务提交时都进行刷新动作。
2:只写入文件系统的缓存内,不进行fsync的操作。
0和2一般不可取,不过还是提供了。
取值影响:默认插入的是50万条数据,时间的花费基本都是fsync上。
2. Undo log
Redo log是规范了重做的操作,因此如果一旦宕机,那么通过重做日志可以轻易恢复刚刚执行的数据变更,但是回滚的操作却不能解决,因此mysql提供了Undo log来记录原始数据,保证了事务能正常回滚,也就保证了原子性 A。
因此Innodb在执行变更操作时不仅仅生成了Redo日志,也同样生成了Undo日志,用来保证操作能够正常回滚。
2.1 存放地址
undo 存放在数据库内部一个特殊段中(segment),这个段称之为 undo段,位于共享表空间内(共享表是什么意思?不懂)。
2.2 误区
因为undo可以恢复回滚之前的数据,因此有人认为undo是将数据库物理地址恢复到执行语句或事务之前的样子,但是事实并非如此。undo是逻辑日志,所以只是将数据库逻辑的恢复到原来的样子。需要理解这句话,只是逻辑的取消之前的修改并且逻辑的恢复成原来的值。但是要注意的是数据结构和页本身在回滚之后可能大不相同,所以可以看出Innodb所做的工作其实是执行了相反的sql语句,,比如insert的语句回滚时执行delete,delete语句执行insert。
2.3 另一个作用MVCC(先写着,虽然不是很了解)
undo的另一个作用是MVCC。当用户读取一行记录时,若该记录已经被其他事务占用时,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
2.4 redo的产生
undo log也会同样产生redo log,因为undo log 同样也需要持久性的保护。
2.5 基本结构
此结构也是从网络总结而来,本人并不知道真正内部结构是否如此。
1. rseg0保留项
2. rseg1-rseg32 用于临时表的undo
3. rseg33-rseg128用于普通事务的undo。
每个回滚段维护的是一个段头页的地址,在该页中又划分了1024个槽用于记录undo对象,每个事务占用两个slot,一个用于insert,一个用于update/delete。
Innodb1.1 版本,只有一个回滚段,只能支持理论上1024个事务。
1.1版本之后,可以支持理论上128*1024个事务。
1.2版本开始,可以通过参数对Rseg进行设置。
innodb_undo_directory:设置文件位置,可以将回滚段放在共享表之外的地址。
innodb_undo_logs:设置回滚段的个数,默认是128。
innodb_undo_tablespaces:设置构成Rseg文件数量,可以均匀的分散在多个文件中。
2.6 执行过程
当事务提交时,InnoDB会做下面两件事:
1. 将undo log放入列表,为purge提供依据。
2.判断undo log是否可以重用,可以的话分配给下个事务使用
因为事务提交之后不能马上删除undo log和它所在的页,这是因为可能其他事务需要通过undo log来得到之前版本的行记录。因此事务提交时将undo log放入一个链表中,是否能删除由purge线程决定。
2.6 格式
分为2个格式:
2.6.1 insert undo log
2.6.2 update undo log
insert操作由于事务的隔离性要求,它只能对本事务可见,对其他事务不可见,所以该undo log可以在事务提交后直接删除,不需要进行purge操作。
而delete/update操作不行,需要放入链表中,由purge线程进行最后的删除。
3. purge
对于delete操作,并不是执行真正的删除,例如对于delete * from t where a = 1而是在讲delete flag设置成1,该记录仍然在B+树中。对于update语句,不是直接对记录进行更新,而是标识旧记录为删除,然后产生一条新记录。那么此时产生的旧版本的记录什么时候删除呢,怎么删除?就是通过purge操作。
purge线程开启purge操作,每10s进行一次,通过undo log来进行旧版本数据的删除。上面其实已经说过,一个undo 段页上允许多个对象重用产生undo log,由此InnoDB会维护一个history列表,它根据事务提交的顺序,将undo log进行连接。
1. 先提交的事务排在尾端。undo page中存放了很多不同事务的undo log。
2. 首先找到需要删除的trx1,清理之后会在trx1所在的undo page中寻找其他可以删除的trx,删除trx3之后发现了trx5,但是trx5被引用了,所以不删除,接着删除trx7。然后找到最尾端的trx2,发现trx2在Undo Page2,所以到Page2中再进行删除。
引用文章:
MysqlInnoDB存储引擎。