事务Transaction
什么是事务?
一个最小的不可再分的工作单元;
通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)
一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成
事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同
事物的特性(ACID):
-
原子性: 事务是最小单位,不可再分;要么全部执行成功,要么全部失败回滚。
-
一致性: 事务必须使数据库从一个一致的状态变到另外一个一致的状态,也就是执行事务之前和之后的状态都必须处于一致的状态。
不一致性包含三点:脏读,不可重复读,幻读
-
隔离性: 事务A和事务B之间具有隔离性;
-
持久性: 是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中);
关于事务的一些术语
- 开启事务:Start Transaction
- 事务结束:End Transaction
- 提交事务:Commit Transaction
- 回滚事务:Rollback Transaction
两条重要的SQL语句(TCL)
- commit: 提交
- rollback:回滚
开启标志:
- 任何一条DML语句(insert、update、delete)执行,标志事务的开启
结束标志(提交或者回滚):
- 提交:成功的结束,将所有的DML语句操作历史记录和底层硬盘数据来一次同步
- 回滚:失败的结束,将所有的DML语句操作历史记录全部清空
锁机制
MySQL大致可归纳为以下3种锁:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
表锁:
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写请求;
读读:可以允许
读写:不允许
写写:不允许
InnoDB采用行锁
InnoDB实现了以下两种类型的行锁。
共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。
InnoDB行锁是通过索引上的索引项来实现的。
InnoDB这种行锁实现特点意味者:
只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!
对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;
对于普通SELECT语句,InnoDB不会加任何锁。
间隙锁(Next-Key锁)
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;
对于键值在条件范围内但并不存在的记录,叫做“间隙”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。
比如会给between and 中间所有存在或者不存在数据加锁。可以防止幻读。
MVCC(无锁实现) 多版本并发控制机制
给每一个事务维护一个数据最初的快照
数据库隔离级别
1,未提交读:
读读/读写:事务不做任何隔离操作
写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
2,已提交读:
(其他事务提交或者回滚,它会立即读到)
读读:事务读的是事务最初的快照 mvcc机制
读写:读的是快照数据,写的也是快照数据 mvcc机制
写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
3,可重复读 (jdbc默认隔离级别)
读读:事务读的是快照数据 mvcc机制
读写:读的是快照数据,写的也是快照数据(除非当前事务提交或回滚,否则访问的都是快照数据) mvcc机制
写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
4,串行化
读读 :共享锁多个事务可以同时获取
读写 : 共享锁和排它锁
写写 : 排它锁和排它锁
Spring事务
Spring事务可以分为两种:
- 编程式事务(通过代码的方式来实现事务)
- 声明式事务(通过配置的方式来实现事务)
编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。
Spring事务基于Spring AOP,Spring AOP底层用的动态代理,动态代理有两种方式:
-
基于接口代理(JDK代理)
-
- 基于接口代理,凡是类的方法非public修饰,或者用了static关键字修饰,
- 那这些方法都不能被Spring AOP增强
-
基于CGLib代理(子类代理)
-
- 基于子类代理,凡是类的方法使用了private、static、final修饰,
- 那这些方法都不能被Spring AOP增强
那些不能被Spring AOP增强的方法并不是不能在事务环境下工作了。只要它们被外层的事务方法调用了,由于Spring事务管理的传播级别,内部方法也可以工作在外部方法所启动的事务上下文中。
Spring事务可以分为两种:
- 编程式事务(通过代码的方式来实现事务)
- 声明式事务(通过配置的方式来实现事务)
编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。
编程式事务
接口PlatformTransactionManager
定义了事务操作的行为,其依赖TransactionDefinition
和TransactionStatus
接口
PlatformTransactionManager:
事务管理器接口
(定义了一组行为,具体实现交由不同的持久化框架来完成—类比JDBC)
按照给定的事务规则来执行提交或者回滚操作
Public interface PlatformTransactionManager()...{
// Return a currently active transaction or create a new one, according to the specified propagation behavior(根据指定的传播行为,返回当前活动的事务或创建一个新事务。)
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// Commit the given transaction, with regard to its status(使用事务目前的状态提交事务)
Void commit(TransactionStatus status) throws TransactionException;
// Perform a rollback of the given transaction(对执行的事务进行回滚)
Void rollback(TransactionStatus status) throws TransactionException;
}
AbstractPlatformTransactionManager
抽象类实现了Spring事务的标准流程,其子类DataSourceTransactionManager
是我们使用较多的JDBC单数据源事务管理器
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
事务管理器接口 PlatformTransactionManager
通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,
这个方法里面的参数是 TransactionDefinition类 ,这个类就定义了一些基本的事务属性。
TransactionDefinition:
事务定义信息
定义了Spring兼容的事务属性
(比如事务隔离级别、事务传播、事务超时、是否只读状态)
public interface TransactionDefinition {
// 返回事务的传播行为 默认是PROPAGATION_REQUIRED
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
// 事务隔离级别只有在创建新事务时才有效,也就是说只对应传播属性PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW
int getIsolationLevel();
// 返回事务必须在多少秒内完成 同样只有在创建新事务时才有效
int getTimeout();
//返回事务的名字 声明式事务中默认值为“类的完全限定名.方法名”
String getName();
// 返回是否优化为只读事务。
boolean isReadOnly();
}
(1)事务隔离级别
(定义了一个事务可能受其他并发事务影响的程度)
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
TransactionDefinition.ISOLATION_DEFAULT:
使用后端数据库默认的隔离级别
Mysql 默认采用的 REPEATABLE_READ隔离级别
Oracle 默认采用的 READ_COMMITTED隔离级别.
-
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:
最低的隔离级别,允许读取尚未提交的数据变更
可能会导致脏读、幻读或不可重复读
-
TransactionDefinition.ISOLATION_READ_COMMITTED:
允许读取并发事务已经提交的数据
可以阻止脏读,但是幻读或不可重复读仍有可能发生
-
TransactionDefinition.ISOLATION_REPEATABLE_READ:
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
可以阻止脏读和不可重复读,但幻读仍有可能发生。
-
TransactionDefinition.ISOLATION_SERIALIZABLE:
最高的隔离级别,完全服从ACID的隔离级别。
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰
该级别可以防止脏读、不可重复读以及幻读。
但是这将严重影响程序的性能。通常情况下也不会用到该级别。
(2)事务传播行为
(为了解决业务层方法之间互相调用的事务问题)
支持当前事务的情况:
-
TransactionDefinition.PROPAGATION_REQUIRED:
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
TransactionDefinition.PROPAGATION_SUPPORTS:
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-
TransactionDefinition.PROPAGATION_MANDATORY:
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
-
TransactionDefinition.PROPAGATION_REQUIRES_NEW:
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
-
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-
TransactionDefinition.PROPAGATION_NEVER:
以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
-
TransactionDefinition.PROPAGATION_NESTED:
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。
(3) 事务超时属性
(一个事务允许执行的最长时间)
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
(4) 事务只读属性
&#x