一、认识事务
InnoDB储存引擎中的事务完全符合ACID的特性。ACID是以下4个词的缩写:
- 原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。
- 一致性(consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。(其实原子性和隔离性间接的保证了一致性)
- 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。
- 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。
而我们最常说的隔离性其实有对应的隔离级别,MySQL规定的隔离级别有4种,分别是:
- READ UNCOMMITTED(读未提交):在此级别里,事务的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,也就是会产生脏读,在实际应用中一般很少使用。
- READ COMMITTED(读已提交):大多数数据库系统的默认隔离级别都是它,但是MySQL不是。它能够避免脏读问题,但是在一个事务里对同一条数据的多次查询可能会得到不同的结果,也就是会产生不可重复读问题。
- REPEATABLE READ(可重复读):该隔离级别是MySQL默认的隔离级别,看名字就知道它能够防止不可重复读问题,但是在一个事务里对一段数据的多次读取可能会导致不同的结果,也就是会有幻读的问题(注:这里说的无法解决是MySQL定义层面,对于InnoDB引擎则完美的解决了幻读的问题,如果你正在使用InnoDB引擎,可忽略)
- SERIALIZABLE(可串行化):该隔离级别是级别最高的,它通过锁来强制事务串行执行,避免了前面说的所有问题。在高并发下,可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,因为RR级别解决了所有问题。
可以看到隔离级别里最重要的只有两个隔离级别:RC和RR。
二、事务的实现
事务隔离性由锁来实现。原子性、持久性、一致性通过数据库的redo log和undo log来完成。
redo log称为重做日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。
redo和undo的作用都可以视为是一种恢复操作,redo恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。因此两者记录的内容不同,redo通常是物理日志,记录的是页的物理修改操作。undo是逻辑日志,根据每行记录进行记录。
1、redo
基本概念
重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的。
InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成。redo log基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。而undo log是需要随机读写的。
为了确保每次日志都写入重做日志文件,在每次重做日志缓冲写入重做日志文件后,InnoDB都需要调用一次fsync操作。 由于重做日志文件打开并没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。
InnoDB允许用户手工设置非持久化的情况发生,以此提高数据库的性能。即当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync操作。但宕机会丢失一部分数据。
参数innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。默认值是1,表示事务提交时必须调用一次fsync操作, 0表示事务提交时不进行写入重做日志操作,这个操作仅在master thread中完成,而在master thread中每1s会进行一次重做日志文件的fsync操作。2表示事务提交时将重做日志写入重做日志文件,但仅写入缓存,不进行fsync操作。
在 MySQL数据库中还葙一种二进制日志(binlog),其用来进行POINT-IN-TIME
(PIT)的恢复及主从复制(Replication)环境的建立。从表面上看其和重做日志罪常相似,都是记录了对干数据库操作的日志。然而,从本质上来看,两者有着非常大的不同。
首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库的上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
此外,两种日志记录写入磁盘的时间点不同,如图7-6所示。二进制日志只在事务提交完成后进行一次写入。而InnoDB存储引擎的重做日志在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。
从图7-6中可以看到,二进制日志仅在事务提交时记录,并且对于每一个事务,仅包含对应亊务的一个日志。而对于InnoDB存储引擎的重做日志,由于其记录的是物理操作日志,因此每个事务对应多个日志条目,并且事务的重做日志写人是并发的,并非在事务提交时写人,故其茌文件中记录的顺序并非是事务开始的顺序。*T1、*T2、*T3表示的是事务提交时的日志。
log block
在InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块的大小为512字节。
若—个页中产生的重做日志数置大于512字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。
2、undo
基本概念
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段( undo segment)。undo段位于共享表空间内。可以通过py_innodb_page_info.py 工具来查看当前共草表空间中undo的数量。
除了回滚操作,undo的另一个作用是 MVCC,即在InnoDB存储引擎中 MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其它事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
最后也是最为重要的一点是, undo log会产生redo log,也就是 undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
此外,若为每一个事务分配一个单独的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操作需要涉及磁盘的离散读取操作,是一个比较缓慢的过程。
3、purge
delete和update操作可能并不直接删除原有的数据。例如,对上一小节所产生的表t执行如下的SQL语句:
DELETE FROM t WHERE a=1;
表 t上 列 a有聚集索引,列 b上有辅助索引。对于上述的 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操作,清理之前行记彔的版本。
本文整理自:《MySQL技术内幕 InnoDB存储引擎》
个人微信公众号:
作者:jiankunking 出处:http://blog.csdn.net/jiankunking