【1】事务概述
查看存储引擎事务支持情况
show ENGINES ;
① 基本概念
在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态
。
事务处理的原则
保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来。要么数据库管理系统将放弃所做的所有修改,整个事务回滚(rollback)到最初状态。
事务的开始和结束
以第一个DML 语句的执行作为开始,以下面的其中之一作为结束:
- COMMIT 或ROLLBACK 语句
- DDL 或DCL 语句(自动提交)
- 用户会话正常结束
- 系统异常终了
关于DDL和DCL参考如下:
事务安全
一种保护连续操作同时满足(实现)的一种机制。事务安全的意义在于保证数据操作的完整性。只有InnoDB和BDB支持事务安全机制,其他不支持。
事务操作过程中,会触发MySQL的锁机制。InnoDB默认是行锁,但是如果在事务操作过程中,没有用到索引,那么系统会自动全表检索数据,自动升级为表锁。
- 行锁:只有当前行被锁住,别的用户不能操作。
- 表锁:整个表被锁住,别的用户不能操作。
显式/隐式事务
事务分为显示事务和隐式事务
显示事务
显式事务,事务具有明显的开启和结束的标记,需要注意的是必须先设置自动提交功能为禁用。set autocommit=0;
步骤1:开启事务 set autocommit=0; start transaction;可选的 步骤2:编写事务中的sql语句(select insert update delete) 语句1; 语句2; ... 步骤3:结束事务 commit;提交事务 rollback;回滚事务
隐式事务
隐式事务,事务没有明显的开启和结束的标记,比如insert、update、delete语句。
② 事务属性
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
① 回滚属性
默认情况下只有未检查异常(RuntimeException和Error类型的异常
)会导致事务回滚,而受检查异常不会。
事务的回滚规则可以通过 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[]
类型的, 因此可以为这两个属性指定多个异常类.
rollbackFor: 遇到时必须进行回滚 noRollbackFor: 一组异常类,遇到时必须不回滚 rollbackFor={UserAccountException.class}, noRollbackFor={ArithMeticException.class}
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常
时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。
同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
②超时属性
由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响。
如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化。
超时事务属性: ( timeout=millons
)事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
③ 只读属性
只读事务属性:(readOnly=true/false) 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
此外还有隔离级别和传播行为两个属性,我们用单独章节说明。
③ 事务的状态
我们现在知道事务是一个抽象的概念,它其实对应着一个或多个数据库操作,MySQL根据这些操作所执行的不同阶段把事务大致划分称几个状态:
活动的(active)
事务对应的数据库操作正在执行过程中时,我们就说该事务处在活动的状态。
部分提交的(partially committed)
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在部分提交的状态。
失败的(failed)
当事务处在活动的或者部分提交的状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在失败的状态。
中止的(aborted)
如果事务执行了一部分而变为失败的状态,那么就需要把已经修改的事务中的操作还原到事务执行前的状态。换句话说,就是要撤销失败事务对当前数据库造成的影响。我们把这个撤销的过程称之为回滚。当回滚操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了中止的状态。
举例:
update accounts set money=money-50 where name ='AA'; update accounts set money=money+50 where name ='BB';
提交的(committed)
当一个处在部分提交的状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了提交的状态。
一个基本的状态转换图如下所示:
图中可见,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。
【2】显示事务操作
① 开始事务
可以使用关键字start transaction
或者 begin 。前者语义更丰富,start transaction 后面可以跟:read only / read write (默认) / with consistent snapshot 。
- read only : 表示当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
- read write : 表示当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
- with consistent snapshot : 启动一致性读。
start transaction read only ; # 开启一个只读事务 start transaction read only,with consistent snapshot; # 开启只读事务和一致性读
另外,只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用create temporary table创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。
注意:
read only 和 read write 是用来设置所谓的事务访问模式的,就是以只读还是读写的方式来访问数据库中的数据。一个事务的访问模式不能同时既设置为只读的也设置为读写的,所以不能同时把read only 和read write放到start transaction 语句后边。
- 如果我们不显示指定事务的访问模式,那么该事务的访问模式就是读写模式。
开启时候之后,我们就可以执行一系列的事务操作,主要是DML语句。
② 提交事务或中止事务(即回滚事务)
#提交事务,当提交事务后,对数据库的修改是永久性的 commit; # 回滚事务,即撤销正在进行的所有没有提交的修改 rollback; #将事务回滚到某个保存点 rollback to [savepoint]
其中关于savepoint相关操作有:
# 在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点 savepoint 保存点名称; #删除某个保存点 release savepoint 保存点名称;
在某个成功的操作完成之后,后续的操作有可能成功有可能失败。但是不管成功或者失败,前面的操作都已经成功。故可以在当前成功的位置,设置一个点---可以供后续失败操作返回到该位置,而不是返回所有操作,这个点称之为回滚点
。
--开启事务 START TRANSACTION; update p_user set age = 10 where id = 1; --设置回滚点 SAVEPOINT point_name; -- 假设错误操作 update p_user set age = 11 where id = 1; --回滚到回滚点 ROLLBACK to point_name; --关闭事务 COMMIT; SELECT * from p_user where id = 1;
③ completion_type参数
这个参数有三个值:0、1、2。
- completion_type=0,这是默认情况。当我们执行commit的时候会提交事务,在执行下一个事务时,还需要使用start transaction 或者 begin 来开启。
- completion_type=1,这种情况下,当我们提交事务后,相当于执行了commit and chain,也就是开启一个链式事务,即当我们提交事务之后会开启一个相同隔离级别的事务。
- ompletion_type=2,这种情况下commit=commit and release ,也就是当我们提交后,会自动与服务器断开连接。
【3】隐式事务
在MySQL中,默认的是隐式事务处理,用户操作完直接同步到数据表。隐式事务是系统通过autocommit变量进行控制。
SHOW VARIABLES LIKE 'autocommit';#默认是ON
默认情况下,如果我们不显式的使用 start transaction 或者 begin 语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的自动提交。也就是说,不以 start transaction 或者 begin 语句显式的开启一个事务,那么下边这两条语句就相当于放到两个独立的事务中去执行:
UPDATE account SET balance = balance - 10 WHERE id = 1; #此时这条DML操作是一个独立的事务 UPDATE account SET balance = balance + 10 WHERE id = 2; #此时这条DML操作是一个独立的事务
当然,如果我们想关闭这种自动提交的功能,可以使用下边两种方法之一:
- 显式的使用start transaction 或者 begin 语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。
把系统变量 autocommit 的值设置为 off ,就像下面这样:
关闭隐式事务语法:
# #针对于DML操作是有效的,对DDL操作是无效的。 set autocommit = 0/off;
开启隐式事务语法:
set autocommit = 1/on;
这样的话,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出commit语句来把这个事务提交掉,或者显式的写出rollback语句来把这个事务回滚掉。
Oracl 默认不自动提交,需要手写commit命令,而MySQL默认自动提交。
我们在autocommit为true的情况下,使用start transaction 或begin开启事务,那么DML操作就不会自动提交数据
START TRANSACTION; UPDATE account SET balance = balance - 10 WHERE id = 1; UPDATE account SET balance = balance + 10 WHERE id = 2; COMMIT; #或rollback;
如果同时在两个窗口更新同一条数据呢?一个开启手动事务,一个使用默认事务
那么不好意思,后面那个将会抛出错误:
这就是MySQL的锁机制。
InnoDB支持事务,同Oracle类似,事务提交需要写redo、undo。采用日志先行的策略,将数据的变更在内存中完成,并且将事务记录成redo,顺序的写入redo日志中,即表示该事务已经完成,就可以返回给客户已提交的信息。
但是实际上被更改的数据还在内存中,并没有刷新到磁盘,即还没有落地,当达到一定的条件,会触发checkpoint,将内存中的数据(page)合并写入到磁盘,这样就减少了离散写、IOPS,提高性能。
在这个过程中,如果服务器宕机了,内存中的数据丢失,当重启后,会通过redo日志进行recovery重做。确保不会丢失数据。因此只要redo能够实时的写入到磁盘,InnoDB就不会丢数据。
【4】隐式提交数据的情况
① 数据定义语言(Data definition language ,DDL)
数据库对象,指的就是数据库、表、视图、存储过程等结构。当我们使用create、alter、drop等语句去修改数据库对象时,就会隐式的提交前边语句所属于的事务。即
begin; select ...#事务中的一条语句 update ...#事务中的一条语句 ... #事务中的其他语句 create table ... # 此语句会隐式的提交前边语句所属于的事务
② 隐式使用或修改MySQL数据库中的表
当我们使用alter user、create user、drop user、grant、rename user、revoke、set password等语句时也会隐式的提交前边语句所属于的事务。
③ 事务控制或关于锁定的语句
① 当我们在一个事务还没提交或者回滚时就又使用start transaction 或者 begin 语句开启了另一个事务时,会隐式的提交上一个事务。即:
begin; select ...#事务中的一条语句 update ...#事务中的一条语句 ... #事务中的其他语句 begin ... # 此语句会隐式的提交前边语句所属于的事务
② 当前的autocommit 系统变量的值为off,我们手动把它调为 on 时,也会隐式的提交前边语句所属的事务。
③ 使用lock tables、unlock tables 等关于锁定的语句也会隐式的提交前边语句所属的事务。
④ 加载数据的语句
使用load data 语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。
⑤ 关于MySQL复制的一些语句
使用start slave、stop slave、reset slave、change master to等语句时会隐式的提交前边语句所属的事务。
⑥ 其他的一些语句
使用analyze table、cache index、check table 、flush、load index into cache 、optimize table、repair table、reset等语句也会隐式的提交前边语句所属的事务。
【5】事务的常见分类总结
从事务理论的角度来看,可以把事务分为以下几种类型:
- 扁平事务(Flat Transactions)
- 带有保存点的扁平事务(Flat Transactions with Savepoints)
- 链式事务(Chained Transactions)
- 嵌套事务(Nested Transactions)
- 分布式事务(Distributed Transactions)
① 扁平事务
其是事务类型中最简单的一种,但是在实际生产环境中,这可能是使用最频繁地事务。在扁平事务中,所有操作都处于同一层次,其有Begin work开始,由commit work或rollback work结束。其间的操作时原子的,要么都执行,要么都回滚。因此扁平事务是应用程序成为原子操作的基本组成模块。
扁平事务虽然简单,但是在实际环境中使用最为频繁,也正因为其简单,使用频繁,故每个数据库系统都实现了对扁平事务的支持。扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。
扁平事务一般有三种不同的结果:① 事务成功完成。在平常应用中约占所有事务的96%。② 应用程序要求停止事务。比如应用程序在捕获到异常时会回滚事务,约占事务的3%。③ 外界因素强制终止事务。如连接超时或连接断开,约占所有事务的1%。
② 带有保存调的扁平事务
除了支持扁平事务支持的操作外,还允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事务可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销太大。
保存点(savepoint)用来通知事务系统应该记住事务当前的状态,以便当之后发生错误时,事务能回到保存点当时的状态。对于扁平的事务来说,隐式的设置了一个保存点,然而在整个事务中,只有这一个保存点。因此,回滚只能回滚到事务开始时的状态。
③ 链式事务
链式事务是指一个事务由多个子事务链式组成,它可以被视为保存点模式的一个变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务,前一个子事务的提交操作和下一个子事务的开始操作合并成一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行一样。这样,在提交子事务时就可以释放不需要的数据对象,而不必等到整个事务完成后才释放。其工作方式如下:
链事务与带有保存点的扁平事务的不同之处体现在:
- ① 带有保存点的扁平事务能回滚到任意正确的保存点,而链事务中的回滚仅限于当前事务,即只能恢复到最近的一个保存点。
- ② 对于锁的处理,两者也不相同。链事务在执行commit后即释放了当前所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。
④ 嵌套事务
是一个层级结构框架,由一个顶层事务(Top-Level Transaction)控制着各个层次的事务,顶层事务之下嵌套的事务被称为子事务(Subtransaction),其控制着每一个局部的变换,子事务本身也可以是嵌套事务。因此,嵌套事务的层次结构可以看成是一棵树。
⑤ 分布式事务
通常是在一个分布式环境下运行的扁平事务。因此需要根据数据所在位置访问网络中不同节点的数据库资源。常见的方案有2PC、3PC、最终一致性、最大努力通知型以及Seata。