数据库篇-数据库事务
背景
编程其实就是将现实世界的操作逻辑转为计算机进行操作。以最经典的银行转账为案例,我们在数据库中会有一张表存放账户的的信息(所属用户、余额等),如下表所示
1 |
|
如下图中为我们的转账业务流程,在执行蓝色区域中的步骤时可能会出现在执行对mikey扣减100后突然断电了,到账mikey被扣减了,但是leo并没有增加100,出现数据不一致的问题。
在这种场景下数据库事务应运而生,专门来解决这种问题。在调用转账的操作时启用事务,此后的所有操作数据库会将我们的操作视为一个整体,只要其中一个操作出现问题,那么在这个整体中的所有的操作都会被回滚。
简介
数据库事务(transaction)是数据库的一个重要的组成部分,它能保证我们的数据能否安全的执行存储,在出现异常的时候能及时回滚,保证业务的正常执行。
事务的定义
数据库事务 (transaction) 是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
事务的四大特性ACID
ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)
原子性
事务要么执行,要么不执行,不会出现部分执行的情况,是一个不可分割的工作单位。一旦在这个操作的过程中只要有任意一个步骤出现错误,将回滚到起始位置,所有对数据的操作都无效。
一致性
在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)
持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
数据库隔离级别
事务指定一个隔离级别,该隔离级别定义一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读或幻读)的角度进行描述。
脏读:一个事务能读到另一个事务修改了但未提交事务的数据。
不可重复读:两次执行同样的查询,可能会得到不一样的结果。一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
幻读:指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插人了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行 (Phantom Row)
读未提交
READ_UNCOMMITTED
在READ UNCOMMITTED 级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读 (Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMNITTED 不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。
读已提交
READ_COMMITTED
大多数数据库系统的默认隔离级别都是 READ COMMITTED(但 MySQL 不是)。READ COMMITTED 满足前面提到的隔离性的简单定义:一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读 (nonrepeatableread),因为两次执行同样的查询,可能会得到不一样的结果。
可重复读
REPEATABLE_READ
可重复读(Repeatable Read),当使用可重复读隔离级别时,在事务执行期间会锁定该事务以任何方式引用的所有行。因此,如果在同一个事务中发出同一个SELECT语句两次或更多次,那么产生的结果数据集总是相同的。因此,使用可重复读隔离级别的事务可以多次检索同一行集,并对它们执行任意操作,直到提交或回滚操作终止该事务。
REPEATABLE READ 解決了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插人了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行 (Phantom Row)。InnoDB 和 XtraDB 存储引擎通过多版本并发控制 (MVCC, Multiversion Concurrency Control)解决了幻读的问题。
可重复读是MySQL 的默认事务隔离级别。
串行化
SERIALIZABLE
SERIALIZABLE 是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE 会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁年用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | ✅ | ✅ | ✅ |
已提交读(Read committed) | ❌ | ✅ | ✅ |
可重复读(Repeatable read) | ❌ | ❌ | ✅ |
可串行化(Serializable ) | ❌ | ❌ | ❌ |
案例
简单案例
1 |
|
修改事务的隔离级别
1 |
|
开启事务操作数据
1 |
|
Spring中的事务
Spring事务相关的API主要在spring-tx.jar包中,提供了xml、注解的方式支持事务
声明式事务配置
1.引入依赖
1 |
|
2.配置事务管理器
1 |
|
3.开启事务
1 |
|
Spring事务的传播机制
Spring对事务支持符合ACID规范,同时对事务在多个多个方法中调用如何传递进行进一步细分,共有七种传播机制
传播机制 | 说明 | 备注 |
---|---|---|
required | 如果当前存在事务,就加入该事务。 | |
如果当前没有事务,就创建一个新事务。 | ||
这是最常用的设置。 | 只创建一个事务。 | |
requires_new | 不管是否存在事务,都创建一个新的事务。 | |
老事务 先挂起,再创建 新事务, 新事务 执行完并提交, | ||
接着,继续执行 老事务,最后提交。 | 1、每次都创建一个新的事务。 | |
2、创建 新事务 前,老事务 先挂起。 | ||
3、先执行的方法后提交事务,后执行的方法先提交事务。 | ||
4、老事务 的回滚,不会影响 新事务 的提交。 | ||
nested | 如果当前存在事务,则在嵌套事务内执行。 | |
如果当前没有事务,则执行与 required 类似的操作。 | nested (嵌套)创建事务。 | |
supports | 支持当前事务。 | |
如果当前存在事务,就加入该事务, | ||
如果当前不存在事务,就以非事务执行。 | supports 不会创建事务。 | |
not_supported | 不支持事务。 | |
如果当前存在事务,就把当前事务 挂起。 | ||
如果当前没有事务,就以非事务执行。 | ||
mandatory | 强制、必须使用事务。 | |
如果当前 已经存在事务,就加入该事务, | ||
如果当前不存在事务,就 抛出异常。 | 1、mandatory 不会创建事务。 | |
2、mandatory 执行的前提是已经存在事务。 | ||
never | 禁止事务 。 | |
如果当前存在事务,则 抛出异常, | ||
如果当前没有事务,以非事务方式执行, | 必须在一个没有事务中执行,否则报错。 |