01 概述
事务一般是指数据库事务,简称事务,是一组不可分割的操作。
事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以保证要么所有修改都保存了,要么所有修改都不保存(事务是数据库区别于文件系统的重要特征之一)。
说明:
1、每个SQL语句都是一个事务;
2、事务只对DML语句有效,对于DQL无效。
用转账的例子来说,A 账户要给 B 账户转 100块,这中间至少包含了两个操作:
1、A 账户减100块
2、B 账户加100块
在支持事务的数据库管理系统来说,就是得确保上面两个操作都能完成,不能存在,A的100块扣了,然后B的账户又没加上去的情况。
02 分类
2.1 扁平事务
在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束,其间的操作是原子的,要么都执行,要么都回滚。因此,扁平事务是应用程序成为原子操作的基本组成模块。
扁平事务的主要限制是不能提交或者回滚事务的某一部分,或分几个步骤提交。因此就出现了带有保存点的扁平事务。
2.2 带有保存点的扁平事务
带有保存点的扁平事务(Flat Transaction with Savepoint),除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态。这是因为某些事物可能在执行过程中出现的错误并不会导致所有的操作都无效,放弃整个事务不合乎要求,开销也太大。
2.3 链事务
链事务(Chained Transaction)可视为保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent)。这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行。
链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务看到上一个事务的结果,就好像在一个事务中进行的一样。
链事务与带有保存点的扁平事务不同的是,带有保存点的扁平事务能够回滚到任意正确的保存点。而链事务中的回滚仅限于当前事务,即只能恢复到最近一个的保存点。对于锁的处理,二者也不相同。链事务在执行COMMIT后即释放当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。
2.4 嵌套事务
嵌套事务(Nested Transaction)是一个层次结构框架。由一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。
2.5 分布式事务
分布式事务(Distributed Transaction)通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。
03 ACID
一个支持事务(Transaction)的数据库,必须要具有ACID这四种特性,否则在事务过程当中无法保证数据的正确性,交易过程极可能达不到交易方的要求。
注:在单机环境下,事务遵循ACID特性,但是在分布式事务中,ACID已经不能保证事务的有效性,还需要遵循CAP和BASE理论。
3.1原子性(Atomicity)
整个事务中所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
原子性实现:undo log
3.2 一致性(Consistency)
事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。
在事务的四个特点中,一致性是事务的根本追求,而在某些情况下会对一致性造成破坏:
1、事务的并发执行
2、事务故障或系统故障
数据库系统通过并发控制技术和日志恢复技术来避免这种情况的发生:
1、并发控制技术(加锁+无锁MVCC)保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏;
2、日志恢复技术保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。
实现机制:原子性,隔离性,持久性
3.3 隔离性(Isolation)
隔离状态执行事务,使他们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一个事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或者序列化请求,使得在同一时间仅有一个请求用于同一数据。
实现机制:读写锁+MVCC(不加锁)
3.4 持久性(Durability)
在事务完成以后,该事务对数据库所作的变更会持久地保存在数据库之中,并不会被回滚。
实现机制:redo log重做日志
04 原理
4.1 回滚日志(undo)
undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
undo log用于存放数据被修改前的值,如果修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。另外InnoDB MVCC事务特性也是基于undo日志实现的。
因此,undo log有两个作用:提供回滚和多个行版本控制(MVCC)。
4.2 重做日志(redo)
redo log重做日志记录的是新数据的备份,属于物理日志。在事务提交前,只要将redo log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是redo log已经持久化。系统可以根据redo log的内容,将所有数据恢复到最新的状态。
redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。
MySQL中redo log刷新规则采用一种称为Checkpoint的机制(利用LSN实现),为了确保安全性,又引入double write机制。
4.3 MVCC
MVCC(Multi-Version Concurrency Control)多版本并发控制,可以简单地认为:MVCC就是行级锁的一个变种(升级版)。
事务的隔离级别就是通过锁的机制来实现,只不过隐藏了加锁细节。
在表锁中我们读写是阻塞的,基于提升并发性能的考虑,MVCC一般读写是不阻塞的。
MySQL中MVCC是通过redo log版本链+一致性视图Read-view实现的。
05 隔离级别
5.1 UR:Read uncommitted
查询时,读取的数据中允许含有脏数据,不检查来自各存储节点的数据是否活跃,即不检查来自各个存储节点的数据是否来自同一版本或同一时刻的副本数据。
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。
读未提交存在脏读问题。
读取未提交的数据,被称之为脏读(Dirty Read)。
5.2 CR:Read commited
查询时,读取的数据中不允许包含脏数据,检查来自各存储节点的数据不能为活跃状态,即检查来自各存储节点的数据必须为同一版本或同一时刻的副本数据。
这是大多数数据库系统的默认隔离级别(但是不是MySQL默认隔离级别)。
已提交读通过对数据的版本进行校验解决了脏读问题,但是已提交读存在不可重复读。
不可重复读(Nonrepeatable Read),同一select可能返回不同结果。
5.3 RR:Repetable read
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发(多次)读取数据时,会看到同样的数据行。
可重复读通过两种机制解决不可重复读:间隙锁(加锁方式),MVCC(不加锁方式)。
可重复读存在幻读(Phantom Read)。简单地说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影”行。
上述是标准SQL规定的隔离级别及存在的问题,但是,在MySQL中可重复读已经解决了幻读!
5.4 Serializable
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
5.5 总结

06 事务控制语句
6.1 事务基本操作
开启事务:starttransaction
回滚事务:rollback
提交事务:commit
保存点:
SAVEPOINT保存点名称;
ROLLBACK[WORK]TO[SAVEPOINT] 保存点名称;
RELEASESAVEPOINT保存点名称;
6.2 设置自动提交
通过指令SHOW VARIABLES LIKE ‘autocommit’;查看是否开启事务自动提交,默认是开启的。
把系统变量autocommit设置为OFF,即SET autocommit=OFF,这样写入的多条语句就算是属于一个事务了,直到我们显式地写出COMMIT语句才把该事务提交,或者显式的写出ROLLBACK语句把这个事务回滚。
6.3 事务隔离级别设置
查看事务隔离级别
select@@global.tx_isolation.@@tx_isolations;
设置事务隔离级别
全局的:setglobal/sessiontransactionisolationlevelreadcommited;
当前会话:select@@tx_isolation;
07 隐式提交
当我们使用START TRANSACTION或者BEGIN语句开启一个事务,或者把系统变量autocommit的值设置为OFF时,事务就不会进行自动提交,但是如果我们输入了某些语句之后就会悄悄地提交,就像是输入了COMMIT语句一样,这种因为某些特殊的语句而导致事务提交的情况称为隐式提交,这些会导致事务隐式提交的语句包括:
1、定义或修改数据库对象的DDL。所谓的数据库对象,指的就是数据库、表、视图、存储过程等这些东西,当我们使用CREATE、ALTER、DROP等语句去修改这些所谓的数据库对象时,就会隐式的提交前面语句所属于的事务;
2、隐式使用或修改数据库中的表:当我们使用ALTER USER、CREATE USER、DROP USER、GRANT、RENAME USER、SET PASSWORD等语句时也会隐式的提交前边语句所属于的事务;
3、事务控制或关于锁定的语句:
当我们在一个事务还没有提交或者回滚时就又使用START TRANSACTION或者BEGIN语句开启了另一个事务时,就会隐式的提交上一个事务。
或者当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前面语句所属的事务。
或者使用LOCK TABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前面语句所属的事务。
4、加载数据的语句:比如我们使用LOAD DATA语句来批量往数据库中导入数据时,也会隐式的提交前面语句所属的事务。
5、其他一些语句:使用ANALYSE TABLE、CACHE INDEX、CHECK TABLE、FLUSH、LOAD INDEXINTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET等语句也会隐式的提交前面语句所属的事务。
常用的select、insert、update和delete命令,都不会强制提交事务。
08 事务使用建议
1、尽量避免使用长事务;
2、关闭自动提交,自己控制事务的开启和提交;
3、不要在循环操作中反复执行事务提交;
4、不要自动提交事务。