事务 Transation

事务的 ACID 特性

Atomicity(原子性)

一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

Consistency(一致性)

事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

Isolation(隔离性)

一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

Duration(持久性)

持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

由事务并发引起的问题

Dirty Read(脏读)

脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。

例如:事务T1修改了一行数据,但是还没有提交,
这时候事务T2读取了被事务T1修改后的数据,
之后事务T1因为某种原因Rollback了,
那么事务T2读取的数据就是脏的。

解决办法:把数据库的事务隔离级别调整到 READ_COMMITTED

过程如下图:

脏读

Unrepeatable Read(不可重复读)

不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果。 

例如:事务T1读取某一数据,事务T2读取并修改了该数据,
T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。 

解决办法:把数据库的事务隔离级别调整到REPEATABLE_READ

过程如下图:

不可重复读

Phantom Read(幻读)

幻读是指当事务不是独立执行时发生的一种现象。

例如:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,
但是系统管理员B就在这个时候插入了一条具体分数的记录,
当系统管理员A改结束后发现还有一条记录没有改过来,
就好像发生了幻觉一样。这就叫幻读。

解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ

过程如下图:

幻读

不可重复读和幻读的区别

不可重复读

不可重复读的重点是修改
同样的条件, 你读取过的数据, 再次读取出来发现值不一样了

例子:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成

--在postresql中的例子,可能和其他的数据库略有区别
begin transaction;
select salary from employee
where id = MaryID;

在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.

begin transaction;
update employee
set salary = 2000
where id = MaryID;
commit;

在事务1中,Mary 再次读取自己的工资时,工资变为了2000

select salary 
from employee
where id = MaryID;

在一个事务中前后两次读取的结果并不致,导致了不可重复读。

幻读

幻读的重点在于新增或者删除
同样的条件, 第1次和第2次读出来的记录数不一样

例子:目前工资为1000的员工有10人。
事务1,读取所有工资为1000的员工。

begin transaction;
select * from employee
where salary = 1000;
-- 共读取10条记录 

这时另一个事务向employee表插入了一条员工记录,工资也为1000

begin transaction;
insert into employee(,salary,)
values(,1000,);
commit;

事务1再次读取所有工资为1000的员工

--事务1
select * from employee
where salary = 1000;
--共读取到了11条记录

这就产生了幻像读。

总结

归纳一下,以上提到了事务并发所引起的跟读取数据有关的问题,各用一句话来描述一下:

  1. 脏读:事务 A 读取了事务 B 未提交的数据,并在这个基础上又做了其他操作。
  2. 不可重复读:事务 A 读取了事务 B 已提交的更改数据。
  3. 幻读:事务 A 读取了事务 B 已提交的新增数据。

第一条是坚决抵制的,后两条在大多数情况下可不作考虑。

这就是为什么必须要有事务隔离级别这个东西了,它就像一面墙一样,隔离不同的事务。看下面这个表格,您就清楚了不同的事务隔离级别能处理怎样的事务并发问题:

解决方案

根据您的实际需求,再参考这张表,最后确定事务隔离级别,应该不再是一件难事了。

JDBC 也提供了这四类事务隔离级别,但默认事务隔离级别对不同数据库产品而言,却是不一样的。我们熟知的 MySQL 数据库的默认事务隔离级别就是 READ_COMMITTED,Oracle、SQL Server、DB2等都有有自己的默认值。我认为 READ_COMMITTED 已经可以解决绝大多数问题了,其他的就具体情况具体分析吧。

若对其他数据库的默认事务隔离级别不太清楚,可以使用以下代码来获取:

DatabaseMetaData meta = DBUtil.getDataSource().getConnection().getMetaData();
int defaultIsolation = meta.getDefaultTransactionIsolation();

提示:在 java.sql.Connection 类中可查看所有的隔离级别。

我们知道 JDBC 只是连接 Java 程序与数据库的桥梁而已,那么数据库又是怎样隔离事务的呢?其实它就是“锁”这个东西。当插入数据时,就锁定表,这叫“锁表”;当更新数据时,就锁定行,这叫“锁行”。当然这个已经超出了我们今天讨论的范围。

JDBC 解决方案

事务隔离级别

  • READ_UNCOMMITTED
  • READ_COMMITTED
  • REPEATABLE_READ
  • SERIALIZABLE

Spring 解决方案

首先要明确的是,事务是从哪里来?传播到哪里去?答案是,从方法 A 传播到方法 B。Spring 解决的只是方法之间的事务传播,那情况就多了,比如:

  1. 方法 A 有事务,方法 B 也有事务。
  2. 方法 A 有事务,方法 B 没有事务。
  3. 方法 A 没有事务,方法 B 有事务。
  4. 方法 A 没有事务,方法 B 也没有事务。

这样就是 4 种了,还有 3 种特殊情况。还是用我的 Style 给大家做一个分析吧:

假设事务从方法 A 传播到方法 B,您需要面对方法 B,问自己一个问题:

方法 A 有事务吗?以下就是解答了。

事务传播特性

  • PROPAGATION_REQUIRED(default)

如果没有,就新建一个事务;如果有,就加入当前事务。这就是 PROPAGATION_REQUIRED,它也是 Spring 提供的默认事务传播行为,适合绝大多数情况。

  • RROPAGATION_REQUIRES_NEW

如果没有,就新建一个事务;如果有,就将当前事务挂起。这就是 RROPAGATION_REQUIRES_NEW,意思就是创建了一个新事务,它和原来的事务没有任何关系了。

  • PROPAGATION_NESTED

如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。这就是 PROPAGATION_NESTED,也就是传说中的“嵌套事务”了,所嵌套的子事务与主事务之间是有关联的(当主事务提交或回滚,子事务也会提交或回滚)。

  • PROPAGATION_SUPPORTS

如果没有,就以非事务方式执行;如果有,就使用当前事务。这就是 PROPAGATION_SUPPORTS,这种方式非常随意,没有就没有,有就有,有点无所谓的态度,反正我是支持你的。

  • PROPAGATION_NOT_SUPPORTED

如果没有,就以非事务方式执行;如果有,就将当前事务挂起。这就是 PROPAGATION_NOT_SUPPORTED,这种方式非常强硬,没有就没有,有我也不支持你,把你挂起来,不鸟你。

  • PROPAGATION_NEVER

如果没有,就以非事务方式执行;如果有,就抛出异常。这就是 PROPAGATION_NEVER,这种方式更猛,没有就没有,有了反而报错,确实够牛的,它说:我从不支持事务!

  • PROPAGATION_MANDATORY

如果没有,就抛出异常;如果有,就使用当前事务。这就是 PROPAGATION_MANDATORY,这种方式可以说是牛逼中的牛逼了,没有事务直接就报错,确实够狠的,它说:我必须要有事务!


需要注意的是 PROPAGATION_NESTED,不要被它的名字所欺骗,Nested(嵌套),所以凡是在类似方法 A 调用方法 B 的时候,在方法 B 上使用了这种事务传播行为,如果您真的那样做了,那您就错了。因为您错误地以为 PROPAGATION_NESTED 就是为方法嵌套调用而准备的,其实默认的 PROPAGATION_REQUIRED 就可以帮助您,做您想要做的事情了。

事务超时

事务超时(Transaction Timeout):为了解决事务时间太长,消耗太多的资源,所以故意给事务设置一个最大时常,如果超过了,就回滚事务。

只读事务

只读事务(Readonly Transaction):为了忽略那些不需要事务的方法,比如读取数据,这样可以有效地提高一些性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值