从事务理论的角度来说,可以把事务分为以下几种类型:
❑扁平事务(Flat Transactions)
❑带有保存点的扁平事务(Flat Transactions with Savepoints)
❑链事务(Chained Transactions)
❑嵌套事务(Nested Transactions)
❑分布式事务(Distributed Transactions)
保存点的扁平事务(Flat Transactions with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。
链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。
链事务在执行COMMIT后即释放了当前事务所持有的锁
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。
分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。假设一个用户在ATM机进行银行的转账操作,例如持卡人从招商银行的储蓄卡转账10 000元到工商银行的储蓄卡。
事务的实现
redo和undo的作用都可以视为是一种恢复操作,redo恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。因此两者记录的内容不同,redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。
redo
- 首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库的上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
- 其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
- 此外,两种日志记录写入磁盘的时间点不同,如图7-6所示。二进制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。
*T1, *T2, *T3 为事务提交时的日志
log block
log block header(12 字节)、log body、log block tailer(8 字节)
在InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块的大小为512字节。
若一个页中产生的重做日志数量大于512字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。
log block header
- LOG_BLOCK_HDR_NO用来标记这个数组中的位置。其是递增并且循环使用的,占用4个字节,但是由于第一位用来判断是否是flush bit,所以最大的值为2G。
- LOG_BLOCK_HDR_DATA_LEN占用2字节,表示log block所占用的大小。当log block被写满时,该值为0x200,表示使用全部log block空间,即占用512字节。
- LOG_BLOCK_FIRST_REC_GROUP占用2个字节,表示log block中第一个日志所在的偏移量。如果该值的大小和LOG_BLOCK_HDR_DATA_LEN相同,则表示当前log block不包含新的日志。
- LOG_BLOCK_CHECKPOINT_NO占用4字节,表示该log block最后被写入时的检查点第4字节的值。
log block tailer
- 只由1个部分组成,且其值和LOG_BLOCK_HDR_NO相同,并在函数log_block_init中被初始化。
log group
重做日志组,InnoDB 引擎只有一个重做日志组。
仅在每个log group的第一个redo log file中进行存储。
重做日志格式
❑redo_log_type:重做日志的类型。
❑space:表空间的ID。
❑page_no:页的偏移量。
LSN
LSN是Log Sequence Number的缩写,其代表的是日志序列号。在InnoDB存储引擎中,LSN占用8字节,并且单调递增。LSN表示的含义有:
❑重做日志写入的总量
❑checkpoint的位置
❑页的版本
Log sequence number表示当前的LSN
Log flushed up to表示刷新到重做日志文件的LSN
Last checkpoint at表示刷新到磁盘的LSN。
恢复
InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如二进制日志,要快很多。与此同时,InnoDB存储引擎自身也对恢复进行了一定程度的优化,如顺序读取及并行应用重做日志,这样可以进一步地提高数据库恢复的速度。
undo
在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undo segment)。undo段位于共享表空间内。
undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。
undo的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
最后也是最为重要的一点是,undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
undo存储管理
InnoDB存储引擎对undo的管理同样采用段的方式。有rollback segment,每个回滚段种记录了1024个undo log segment,而在每个undo log segment段中进行undo页的申请。共享表空间偏移量为5的页(0,5)记录了所有rollback segment header所在的页,这个页的类型为FIL_PAGE_TYPE_SYS。
支持最大128个rollback segment,故其支持同时在线的事务限制提高到了128*1024。这些rollback segment都存储于共享表空间中。
❑innodb_undo_directory,设置rollback segment文件所在的路径。
❑innodb_undo_logs,设置rollback segment的个数,默认值为128。
❑innodb_undo_tablespaces
InnoDB存储引擎会做以下两件事情:
❑将undo log放入列表中,以供之后的purge操作
❑判断undo log所在的页是否可以重用,若可以分配给下个事务使用事务提交后并不能马上删除undo log及undo log所在的页。这是因为可能还有其他事务需要通过undo log来得到行记录之前的版本。故事务提交时将undo log放入一个链表中,是否可以最终删除undo log及undo log所在页由purge线程来判断。
此外,若为每一个事务分配一个单独的undo页会非常浪费存储空间,特别是对于OLTP的应用类型。因为在事务提交时,可能并不能马上释放页。假设某应用的删除和更新操作的TPS(transaction per second)为1000,为每个事务分配一个undo页,那么一分钟就需要1000*60个页,大约需要的存储空间为1GB。若每秒的purge页的数量为20,这样的设计对磁盘空间有着相当高的要求。因此,在InnoDB存储引擎的设计中对undo页可以进行重用。具体来说,当事务提交时,首先将undo log放入链表中,然后判断undo页的使用空间是否小于3/4,若是则表示该undo页可以被重用,之后新的undo log记录在当前undo log的后面。由于存放undo log的列表是以记录进行组织的,而undo页可能存放着不同事务的undo log,因此purge操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。
undo log 的格式
在InnoDB存储引擎中,undo log分为:
❑insert undo log
❑update undo log
purge
对于上述的delete操作,通过前面关于undo log的介绍已经知道仅是将主键列等于1的记录delete flag设置为1,记录并没有被删除,即记录还是存在于B+树中。其次,对辅助索引上a等于1,b等于1的记录同样没有做任何处理,甚至没有产生undo log。而真正删除这行记录的操作其实被“延时”了,最终在purge操作中完成。
purge用于最终完成delete和update操作。这样设计是因为InnoDB存储引擎支持MVCC,所以记录不能在事务提交时立即进行处理。这时其他事物可能正在引用这行,故InnoDB存储引擎需要保存记录之前的版本。而是否可以删除该条记录通过purge来进行判断。若该行记录已不被任何其他事务引用,那么就可以进行真正的delete操作。可见,purge操作是清理之前的delete和update操作,将上述操作“最终”完成。而实际执行的操作为delete操作,清理之前行记录的版本。
InnoDB存储引擎这种先从history list中找undo log,然后再从undo page中找undo log的设计模式是为了避免大量的随机读取操作,从而提高purge的效率。
delay的单位是毫秒。此外,需要特别注意的是,delay的对象是行,而不是一个DML操作。例如当一个update操作需要更新5行数据时,每行数据的操作都会被delay,故总的延时时间为5*delay。而delay的统计会在每一次purge操作完成后,重新进行计算。
group commit
一次fsync可以刷新确保多个事务日志被写入文件。
对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:
1)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
2)调用fsync将确保日志都从重做日志缓冲写入磁盘。
开启二进制日志后,为了保证存储引擎层中的事务和二进制日志的一致性,二者之间使用了两阶段事务,其步骤如下:
1)当事务提交时InnoDB存储引擎进行prepare操作。
2)MySQL数据库上层写入二进制日志。
3)InnoDB存储引擎层将日志写入重做日志文件。
a)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
b)调用fsync将确保日志都从重做日志缓冲写入磁盘。
在MySQL数据库上层进行提交时首先按顺序将其放入一个队列中,队列中的第一个事务称为leader,其他事务称为follower,leader控制着follower的行为。BLGC的步骤分为以下三个阶段:
❑Flush阶段,将每个事务的二进制日志写入内存中。
❑Sync阶段,将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次fsync操作就完成了二进制日志的写入,这就是BLGC。
❑Commit阶段,leader根据顺序调用存储引擎层事务的提交,InnoDB存储引擎本就支持group commit,因此修复了原先由于锁prepare_commit_mutex导致group commit失效的问题。
当有一组事务在进行Commit阶段时,其他新事物可以进行Flush阶段,从而使group commit不断生效。当然group commit的效果由队列中事务的数量决定,若每次队列中仅有一个事务,那么可能效果和之前差不多,甚至会更差。但当提交的事务越多时,group commit的效果越明显,数据库性能的提升也就越大。
事务控制语句
- start transaction
- commit
- rollback
- savepiont identifier
- release savepoint identifier
- rollback to [savepoint] identifier
- set transaction
隐式提交的SQL语句
执行完这些语句后,会有一个隐式的COMMIT操作。
事务操作的统计
- InnoDB存储引擎的应用需要在考虑每秒请求数(Question Per Second,QPS)
- 也应该关注每秒事务处理的能力(Transaction Per Second,TPS)。
事务基本就是围着 redo、 undo转呀
分布式事务、长事务,这么高级的东西就不用管咯