事务是数据库区别于文件系统的重要特性之一。
事务会把数据库从一种一致状态转换为另一种一致状态。
由于隔离性关系到锁的相关知识内容比较多,下篇文章总结,先来总结其他的三个特性的实现。
原子性、一致性、持久性通过数据库的redo log和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的原子性和一致性。
redo log即重做日志。通常是物理日志(物理日志是幂等的),记录的是页的物理修改操作。由两部分组成一是内存中的日志缓冲,二是重做日志文件。在事务进行时,Innodb首先将重做日志信息先放入重做日志缓冲中,然后按照一定频率将其刷新到重做日志文件中。
重做日志的写入时间:
InnoDB是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性,即当事务提交COMMIT时,必须将该事务的所有日志写入到重做日志文件中进行持久化。重做日志是在事务进行中不断地写入,即并不是随事务提交的顺序进行写入的。
重做日志的写入方式:
在INNODB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓冲,重做日志文件都是以块的方式进行保存的。称之为重做日志块,每块的大小为512字节。
若一个页中产生的重做日志数量大于512字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。
重做日志格式:
| redo_log_type | space | page_no | redo log body |
由于innodb存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。
LSN 检查点技术:
LSN是Log Sequence Number的缩写,代表的是日志序列号。LSN代表的是事务写入重做日志的总量。例如当前重做日志LSN为1000,有一个事务T1写入了100字节的重做日志,那么LSN就变成了1100,若又有事务T2写入了200字节的重做日志,那么LSN就变成了1300.
LSN不仅记录在重做日志中,还存在每个页中。在每个页的头部,有一个值FIL_PAGE_LSN,记录了该页的LSN。在页中,LSN表示该页最后刷新时LSN的大小。因为重做日志记录的是每个页的日志,因此页中的LSN用来判断页是否需要进行恢复操作。
例如:页P1的LSN为10000,而数据库启动时,INNODB检测到写入重做日志中的LSN为13000,并且该事务已经提交,那么数据库需要进行恢复操作,将重做日志应用到P1页中,同样的,对于重做日志中LSN小于P1页中的LSN不需要进行重做,因为P1页中的LSN表示页已经被刷新到该位置。
在mysql中通过SHOW ENGINE INNODB STATUS可以查看LSN的情况:
log sequence number :当前的lsn
log flushed up to :刷新到重做日志文件中的lsn
last checkpoint at 表示刷新到磁盘的lsn,checkpoint的位置,在恢复过程中,通常仅需要恢复checkpoint位置之后的数据。
重做日志是如何保证持久性的?
为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,innodb存储引擎都需要调用一次fsync操作,通过fsync来确保日志总能被持久化到磁盘中。同时,重做日志文件的持久化操作是在数据库数据持久化操作之前已经完成的。
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。
undo 用于将数据库逻辑地恢复到数据库事务执行语句之前的样子。
在对数据库进行修改时,innodb存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户只需事务或语句由于某种原因失败了,或者rollback语句请求回滚,就可以利用undo将数据回滚到修改之前的样子。
undo log作用
undo log 存储
存放在数据库内部的一个特殊段(segment)中,这个段称为undo段。undo段位于共享表空间内。undo是逻辑日志,因此只是将数据库逻辑的恢复到原来的样子。
事务在undo log segment分配页并写入undo log的这个过程同样需要写入重做日志。当事务提交时,innodb存储引擎会做以下两件事情:
事务提交后并不能马上删除undo log 以及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以删除undo log及undo log所在的页由purge线程来判断。
undo log 分类
对于delete操作并不是直接删记录,而是将记录标记为已删除,也就是将记录的delete flag设置为1,而记录最终的删除是在purge操作中完成。
对于update操作,如果更新对象的非主键值,则记录一条TRX_UNDO_UPD_EXIST_REC的undo日志。对于更新对象的主键值,则会将原有记录标记为删除,再插入一条新的记录,会有一个类型为TRX_UNDO_DEL_MARK_REC和一条类型为TRX_UNDO_INSERT_MARK_REC两条undo日志。
undo log 存储的版本链
在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列:
对于不同的操作,存储的数据如下:
SELECT
innodb会根据以下两个条件检查每行记录:
a.innodb只查找版本号早于当前事务版本的数据行,<=当前事务版本号,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的
b.行的删除版本要么未定义,要么大于当前的事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。
INSERT
INNODB为新插入的每一行保存当前系统版本号作为行版本号
DELETE
innodb为删除的每一行保存当前系统版本号作为行删除标识
UPDATE
innodb为插入一行新纪录,保存当前系统版本号为行版本号,同时保存当前系统版本号到原来的行作为行删除标识
undo log如何保证事务的一致性?
由于在事务执行时,不仅会产生redo log,也同时会产生undo log,当事务执行时发生宕机或者异常时,通过回滚执行undo log能使数据返回初始状态来保证数据一致性。
innodb存储引擎如何保证事务的原子性?
原子性分为两种情况,一种是所有语句执行完成,或者一句失败,之前执行过的语句全部回滚。
第一种情况由redo log完成。当事务commit后,数据库持久化未完成时发生宕机,可以通过执行redo log完成事务的提交。
第二种情况由undo log完成。当一条语句执行失败时,事务回滚,执行undo log使得数据恢复到原来的样子。
以上关于innodb存储引擎保证事务的原子性,一致性和持久性的实现,还有很多待完善的地方,下片文章总结关于隔离性相关的知识。