事务
概述
事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的目的,也是事务模型区别与文件系统的重要特征之一。
ACID特性
原子性、一致性、隔离性、持久性
分类
从事务理论的角度来说,也可以把事务分为以下几种类型:
扁平事务(Flat Transactions)
带有保存点的扁平事务(Flat Transactions with Savepoints)
链事务(Chained Transactions)
嵌套事务(Nested Transactions)
分布式事务(Distributed Transactions)
扁平事务(Flat Transaction)是事务类型中最简单的一种,但在实际生产环境中,这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGINWORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。
带有保存点的扁平事务(Flat Transactions with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点(Savepoint)用来通知系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。
链事务(Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样。图7-3显示了链事务的工作方式:
![](https://i-blog.csdnimg.cn/blog_migrate/f1a281282ed009a10e01de13a615086a.png)
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务( subtransaction),其控制每一个局部的变换。嵌套事务的层次结构如图7-4所示。
![](https://i-blog.csdnimg.cn/blog_migrate/aad474c921e87ba49f5bc0b8d40b103b.png)
嵌套事务定义:
1)嵌套事务是由若干事务组成的一棵树,子树既可以是嵌套事务,也可以是扁平事务。
2)处在叶节点的事务是扁平事务。但是每个子事务从根到叶节点的距离可以是不同的。
3)位于根节点的事务称为顶层事务,其他事务称为子事务。事务的前驱称( predecessor)为父事务(parent),事务的下一层称为儿子事务(child)。
4)子事务既可以提交也可以回滚。但是它的提交操作并不马上生效,除非其父事务已经提交。因此可以推论出,任何子事物都在顶层事务提交后才真正的提交。
5)树中的任意一个事务的回滚会引起它的所有子事务一同回滚,故子事务仅保留A、C、Ⅰ特性,不具有D的特性。
![](https://i-blog.csdnimg.cn/blog_migrate/3bc9689ebc6865d5148e66f812740d5c.png)
分布式事务(Distributed Transactions)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
事务的实现
原子性、一致性、持久性通过数据库的redo log 和undo log来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undolog用来保证事务的一致性。
Redo
重做日志
重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的。
InnoDB是事务的存储引擎,其通过Force Log at Commit机制实现事务的持久性,即当事务提交(COMMIT)时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成。这里的日志是指重做日志,在InnoDB存储引擎中,由两部分组成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助事务回滚及MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不需要对redo log 的文件进行读取操作。而undo log是需要进行随机读写的。
二进制日志
在MySQL数据库中还有一种二进制日志(binlog),其用来进行POINT-IN-TIME(PIT)的恢复及主从复制(Replication)环境的建立。从表面上看其和重做日志非常相似,都是记录了对于数据库操作的日志。然而,从本质上来看,两者有着非常大的不同。
首先,重做日志是在InnoDB存储引擎层产生,而二进制日志是在MySQL数据库的上层产生的,并且二进制日志不仅仅针对于InnoDB存储引擎,MySQL数据库中的任何存储引擎对于数据库的更改都会产生二进制日志。
其次,两种日志记录的内容形式不同。MySQL数据库上层的二进制日志是一种逻辑日志,其记录的是对应的SQL语句。而InnoDB存储引擎层面的重做日志是物理格式日志,其记录的是对于每个页的修改。
log block(日志块)
在 InnoDB存储引擎中,重做日志都是以512字节进行存储的。这意味着重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redolog block>,每块的大小为512字节。
若一个页中产生的重做日志数量大于512字节,那么需要分割为多个重做日志块进行存储。此外,由于重做日志块的大小和磁盘扇区大小一样,都是512字节,因此重做日志的写入可以保证原子性,不需要doublewrite技术。
重做日志块除了日志本身之外,还由日志块头(log block header)及日志块尾(logblock tailer)两部分组成。重做日志头一共占用12字节,重做日志尾占用8字节。故每个重做日志块实际可以存储的大小为492字节(512-12-8)。图7-7显示了重做日志块缓存的结构。
![](https://i-blog.csdnimg.cn/blog_migrate/a6e9deee8a3955470c383aa18207eda4.png)
log group(重做日志组)
log group为重做日志组,其中有多个重做日志文件。虽然源码中已支持log group的镜像功能,但是在ha_innobase.cc文件中禁止了该功能。因此InnoDB存储引擎实际只有一个log group。
log group是一个逻辑上的概念,并没有一个实际存储的物理文件来表示log group信息。log group由多个重做日志文件组成,每个log group中的日志文件大小是相同的,且在InnoDB 1.2版本之前,重做日志文件的总大小要小于4GB(不能等于4GB)。从InnoDB 1.2版本开始重做日志文件总大小的限制提高为了512GB。InnoSQL版本的InnoDB存储引擎在1.1版本就支持大于4GB的重做日志。
重做日志格式
不同的数据库操作会有对应的重做日志格式。此外,由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。虽然有着不同的重做日志格式,但是它们有着通用的头部格式,如图7-10所示。
![](https://i-blog.csdnimg.cn/blog_migrate/5a829853380db22ab9c079f50558fa3c.png)
通用的头部格式由以下三部分组成:
redo_log_type:重做日志的类型
space:表空间的ID
page_no:页的偏移量
LSN
LSN是Log Sequence Number 的缩写,其代表的是日志序列号。在 InnoDB存储引擎中,LSN占用8字节,并且单调递增。LSN表示的含义有:
重做日志写入的总量
checkpoint的位置
页的版本
恢复
InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如进制日志要快很多。与此同时,InnoDB存储引擎自身也对恢复进行了一定程度的优化,如顺序读取及并行应用重做日志,这样可以进一步地提高数据库恢复的速度。
由于checkpoint表示已经刷新到磁盘页上的LSN,因此在恢复过程中仅需恢复checkpoint开始的日志部分。
undo
基本概念
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段( segment)中,这个段称为undo段( undo segment)。undo段位于共享表空间内。可以通过py_innodb_page_info.py .工具来查看当前共享表空间中undo 的数量。
undo存储
undo log格式
查看undo信息
purge
delete和 update操作可能并不直接删除原有的数据。例如,对上一小节所产生的表t执行如下的SQL语句:
DELETE FROM t WHERE a=1 ;
group commit
为了提高磁盘fsync 的效率,当前数据库都提供了group commit的功能,即一次fsync可以刷新确保多个事务日志被写入文件。对于InnoDB存储引擎来说,事务提交时会进行两个阶段的操作:
修改内存中事务对应的信息,并且将日志写人重做日志缓冲。
2)调用fsync将确保日志都从重做日志缓冲写入磁盘。
3) InnoDB存储引擎层将日志写入重做日志文件。
a)修改内存中事务对应的信息,并且将日志写入重做日志缓冲。
b)调用fsync将确保日志都从重做日志缓冲写入磁盘。
![](https://i-blog.csdnimg.cn/blog_migrate/c1743766d6811f491094a524b9a5c413.png)
事务控制语句
隐式提交的SQL语句
以下这些SQL语句会产生一个隐式的提交操作,即执行完这些语句后,会有一个隐式的COMMIT操作。
DDL语句: ALTER DATABASE...UPGRADE DATA DIRECTORY NAME,ALTER EVENT,ALTER PROCEDURE,ALTER TABLE,ALTER VIEw,CREATE DATABASE,CREATE EVENT,CREATE INDEX,CREATEPROCEDURE,CREATE TABLE,CREATE TRIGGER,CREATE VIEw,DROP DATABASE,DROP EVENT,DROP INDEX,DROP PROCEDURE,DROP TABLE,DROP TRIGGER,DROP VIEW,RENAME TABLE,TRUNCATE TABLE。
用来隐式地修改MySQL架构的操作:CREATE USER、DROP USER、GRANT、RENAME USER、REVOKE、SET PASsWORD。
管理语句:ANALYZE TABLE、CACHE INDEX、CHECK TABLE、LOAD INDEX
INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE。
对于事务操作的统计
由于InnoDB存储引擎是支持事务的,因此InnoDB存储引擎的应用需要在考虑每秒请求数(Question Per Second,QPS)的同时,应该关注每秒事务处理的能力(TransactionPer Second,TPS)。
计算TPS的方法是(com_commit+com_rollback)/time。但是利用这种方法进行计算的前提是:所有的事务必须都是显式提交的,如果存在隐式地提交和回滚(默认autocommit=1),不会计算到com_commit和 com_rollback变量中。
事务的隔离级别
SQL标准定义的四个隔离级别为:
READ UNCOMMITTED
READ COMMITTED
REPEATABLE
READSERIALIZABLE
分布式事务
MySQL数据库分布式事务
InnoDB存储引擎提供了对XA事务的支持,并通过XA事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的ACID要求又有了提高。另外,在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为SERIALIZABLE。
XA事务允许不同数据库之间的分布式事务,如一台服务器是MySQL数据库的,另一台是Oracle数据库的,又可能还有一台服务器是SQL Server数据库的,只要参与在全局事务中的每个节点都支持XA事务。
XA事务由一个或多个资源管理器(Resource Managers)、一个事务管理器( Transaction Manager)以及一个应用程序(Application Program)组成。
资源管理器:提供访问事务资源的方法。通常一个数据库就是一个资源管理器。
事务管理器:协调参与全局事务中的各个事务。需要和参与全局事务的所有资源管理器进行通信。
应用程序:定义事务的边界,指定全局事务中的操作。
在MySQL数据库的分布式事务中,资源管理器就是MySQL数据库,事务管理器为连接MySQL服务器的客户端。图7-22显示了一个分布式事务的模型。
![](https://i-blog.csdnimg.cn/blog_migrate/12d15d3e82bd4077af61e87c86091c2c.png)
分布式事务使用两段式提交(two-phase commit)的方式。在第一阶段,所有参与全局事务的节点都开始准备(PREPARE),告诉事务管理器它们准备好提交了。在第二阶段,事务管理器告诉资源管理器执行ROLLBACK还是COMMIT。如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。可见与本地事务不同的是,分布式事务需要多一次的PREPARE操作,待收到所有节点的同意信息后,再进行COMMIT或是ROLLBACK操作。
内部XA事务
之前讨论的分布式事务是外部事务,即资源管理器是MySQL数据库本身。在MySQL数据库中还存在另外一种分布式事务,其在存储引擎与插件之间,又或者在存储引擎与存储引擎之间,称之为内部XA事务。
不好的事务习惯
在循环中提交
使用自动提交
使用自动回滚
长事务
长事务(Long-Lived Transactions),顾名思义,就是执行时间较长的事务。比如,对于银行系统的数据库,每过一个阶段可能需要更新对应账户的利息。如果对应账号的数量非常大,例如对有1亿用户的表account,需要执行下列语句;
UPDATE account
SET account__total = account_total +(1 + interest _rate)
这时这个事务可能需要非常长的时间来完成。可能需要1个小时,也可能需要4、5个小时,这取决于数据库的硬件配置。DBA和开发人员本身能做的事情非常少。然而,由于事务ACID的特性,这个操作被封装在一个事务中完成。这就产生了一个问题,在执行过程中,当数据库或操作系统、硬件等发生问题时,重新开始事务的代价变得不可接受。数据库需要回滚所有已经发生的变化,而这个过程可能比产生这些变化的时间还要长。因此,对于长事务的问题,有时可以通过转化为小批量(mini batch)的事务来进行处理。当事务发生错误时,只需要回滚一部分数据,然后接着上次已完成的事务继续进行。