本文参考以下文章编写:https://www.cnblogs.com/tiancai/p/9353897.html;https://blog.youkuaiyun.com/lyc_liyanchao/article/details/85136247;https://www.jianshu.com/p/aa76625d3715
数据库事务的四个特性
- 原子性:原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响
- 一致性:也就是说一个事务执行之前和执行之后都必须处于一致性状态。 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
- 隔离性:隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
- 持久性:事物提交成功之后,事物中的所有数据都必须持久化到数据库中。即使事物提交之后数据库立刻崩溃,也需要保证数据能能够被恢复。
事务隔离性可能出现的问题
- 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一 个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的
- 不可重复读:指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。 那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读
- 幻觉读:事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及 到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,就会发生操作第一个事务的用户发现表中还有 没有修改的数据行,也就是说幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读,就好象发生了幻觉一样。
事务隔离级别
- read uncommitted 读未提交:对应Spring中的ISOLATION_READ_UNCOMMITTED;这是事务最低的隔离级别,它允许令外一个事务可以看到这个事务未提交的数据,比如:公司发工资了,把50000元打到我的账号上,但是该事务并未提交,而我正好去查看账户,发现工资已经到账,是50000元整,非常高兴。可是不幸的是,领导发现发给的工资金额不对,是2000元,于是迅速回滚了事务,修改金额后,将事务提交,最后我实际的工资只有2000元,空欢喜一场。 脏读是两个并发的事务,“事务A:领导发工资”、“事务B:我查询工资账户”,事务B读取了事务A尚未提交的数据。当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。
- read commited 读已提交:对应Spring中的ISOLATION_READ_COMMITTED;保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据;我拿着工资卡去消费,系统读取到卡里确实有2000元,而此时老婆也正好在网上转账,把工资卡的2000元转到她账户,并在我之前提交了事务,当我扣款时,系统检查到工资卡已经没有钱,扣款失败,十分纳闷,明明卡里有钱,为何......不可重复读是两个并发的事务,“事务A:消费”、“事务B:老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读。
- repeatable read:对应Spring中的ISOLATION_REPEATABLE_READ;这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读);当隔离级别设置为Repeatable read时,可以避免不可重复读。当我拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),我老婆就不可能对该记录进行修改,也就是不能在此时转账。虽然repeatable read避免了不可重复读,但还有可能出现幻读。例如:老婆工作在银行部门,她时常通过银行内部系统查看我的信用卡消费记录。有一天,她正查询到我当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而我此时正好在外面吃完大餐后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction ... ),并提交了事务,随后老婆将我的当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,老婆很诧异,以为出现了幻觉,幻读就这样产生了。
- serializable:对应Spring的ISOLATION_SERIALIZABLE ;这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读
Spring默认的事务隔离级别同我们的数据库是一致的,而mysql默认的事务隔离级别为repeatable read;所以我们开发中有可能出现幻觉读的情况。
TransactionDefinition.java
public interface TransactionDefinition {
// 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。
int PROPAGATION_REQUIRED = 0;
// 支持当前事物,如果当前没有事物,则以非事物方式执行。
int PROPAGATION_SUPPORTS = 1;
// 使用当前事物,如果当前没有事物,则抛出异常。
int PROPAGATION_MANDATORY = 2;
// 新建事物,如果当前已经存在事物,则挂起当前事物。
int PROPAGATION_REQUIRES_NEW = 3;
// 以非事物方式执行,如果当前存在事物,则挂起当前事物。
int PROPAGATION_NOT_SUPPORTED = 4;
// 以非事物方式执行,如果当前存在事物,则抛出异常。
int PROPAGATION_NEVER = 5;
// 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同
int PROPAGATION_NESTED = 6;
// 使用后端数据库默认的隔离级别。
int ISOLATION_DEFAULT = -1;
// READ_UNCOMMITTED 隔离级别
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
// READ_COMMITTED 隔离级别
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
// REPEATABLE_READ 隔离级别
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
// SERIALIZABLE 隔离级别
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
// 默认超时时间
int TIMEOUT_DEFAULT = -1;
// 获取事物传播特性
int getPropagationBehavior();
// 获取事物隔离级别
int getIsolationLevel();
// 获取事物超时时间
int getTimeout();
// 判断事物是否可读
boolean isReadOnly();
// 获取事物名称
@Nullable
String getName();
}
Spring事务的传播特性表
传播特性 | 说明 |
PROPAGATION_REQUIRED | 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中 |
PROPAGATION_SUPPORTS | 支持当前事物,如果当前没有事物,则以非事物方式执行 |
PROPAGATION_MANDATORY | 使用当前事物,如果当前没有事物,则抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事物,如果当前已经存在事物,则挂起当前事物 |
PROPAGATION_NOT_SUPPORTED | 以非事物方式执行,如果当前存在事物,则挂起当前事物 |
PROPAGATION_NEVER | 以非事物方式执行,如果当前存在事物,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与PROPAGATION_REQUIRED传播特性相同 |