由事物隔离级别引发的血案

今天公司的系统发现一个bug:主表记录的已还款总额和还款记录表里面的偿还金额之和不一致。看到这个问题,我的第一反应是怀疑还款的时候离线锁没生效,导致并发修改主表记录。可是经过查看日志和代码,排除了这个可能性。然后又怀疑可能是由于还款之后,修改已还款总额和还款状态时只调用了jpa的save,没有flush,导致没及时写入数据库,别的线程更新的时候不是最新数据。但再一想,发现不对,因为还款的操作是在事务之中进行的,事务结束,jpa会自动把修改写入数据库,应该不会出现这个问题。后来请来大牛帮忙分析,终于发现了这个巨坑。。。。

先说一下代码结构

 1 @Transactional(value = Transactional.TxType.REQUIRES_NEW)
 2     public void repayInTranaction() {
 3         repay();
 4     }
 5     // 调用返回的时候,主表的修改才flush到数据库
 6
 7     private void repay() {
 8         try {
 9             lock.lock();
10             // 查询主表记录
11             // 插入还款记录,状态为待还款
12             // 还款
13             // 修改还款记录,状态为已成功
14             // 将该次还款金额加到主表的已还款总额,这个地方修改过后调用了save, 没有flush
15         } catch (Exception e) {
16             // error handling
17         } finally {
18             lock.unlock();
19         }
20     }

其中,repayInTransaction方法会被并发调用。

也许经验比较丰富的大牛看到这段示例代码立刻就能看出问题所在。是的,正如本文的标题所说,问题的关键就在于事务隔离。spring transaction的默认隔离级别(关于隔离级别,可参考http://blog.sina.com.cn/s/blog_539d361e0100ncf6.html)是“已提交读”,也就是说,如果一个事务修改了某一行数据,但尚未提交,此时另外一个事务来读取同一行是不能读取到第一个事务的修改的。

在上面的代码中,如果第一个线程执行完repay方法,但repayInTranaction方法尚未返回,这个时候lock已经释放,但事务尚未提交,新的线程创建了新的事务,要来修改同一条主表记录。由于锁已经释放,新线程可以执行到第10行的部分,读出第一个线程已经修改但尚未提交的数据。然后第一个线程提交第一个事务,接着第二个线程提交第二个事务,悲剧就发生了。。。

补救方法比较简单,修改一下代码结构,让锁的作用范围比事务的范围更大就ok了。修改后代码如下:

 1 public void repay() {
 2         try {
 3             lock.lock();
 4             repayInTranaction();
 5         } catch (Exception e) {
 6             // error handling
 7         } finally {
 8             lock.unlock();
 9         }
10     }
11
12     @Transactional(value = Transactional.TxType.REQUIRES_NEW)
13     public void repayInTranaction() {
14             // 查询主表记录
15             // 插入还款记录,状态为待还款
16             // 还款
17             // 修改还款记录,状态为已成功
18             // 将该次还款金额加到主表的已还款总额,这个地方修改过后调用了save, 没有flush
19     }
时间: 2025-01-12 10:58:03

由事物隔离级别引发的血案的相关文章

事务隔离级别引发的"血案"

事务引发的"血案"见的多了也麻木了,这回遇到个事务隔离级别的"案子",坑了我小半天的时间...也怪自己细节不牢.. 敲着代码遇到这么一个怪事情: class XXXService{ @Transactional public void demo(){ //一堆业务逻辑 rpc.insertOne(); //dubbo调用远程服务插入一条数据 getOne(); //获取刚才插入的数据 } } 其中getOne()的事务的传播属性是required, 因为dubbo是

事物及事物隔离级别

什么是事物 事物是访问数据库的一个操作序列,数据库应用系统通过事物集来完成对数据库的存取.事物的正确执行使得数据库从一种状态转换为另一种状态. 事物必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性(isolation).持久性(durability)的缩写,这四种状态的意思是: 1.原子性 即不可分割,事物要么全部被执行,要么全部不执行.如果事物的所有子事物全部提交成功,则所有的数据库操作被提交,数据库状态发生变化:如果

Oracle 事物隔离级别

1.事务介绍 Oracle11g中的事务是隐式自动开始的,不需要用户显示的执行开始事务语句.但对于事务的结束处理,则需要用户进行指定的操作.通常在以下情况下,Oracle认为一个事务结束了. (1)  执行Commit语句提交事务 (2)  执行Rollback语句撤销事务 (3)  执行一条数据定义语句.如果该语句执行成功,那么oracle系统会自动执行commit命令:否则,系统会自动执行Rollback命令. (4)  执行一个数据控制命令,当语句执行完毕,oracle系统会自动执行com

数据库的事物隔离级别以及锁的一些个人理解

数据库的 基本分为 共享锁和排它锁 排它锁顾名思义,不能和其他任何所共存. 以SqlServer中某一行数据为例, 特殊的,WithNoLock 这个是不给数据加上任何锁,所以根本和锁没关系 再说update,update的过程是给这条数据加上排它锁,所以当另外事物过来要求修改这条数据的时候,会由于排它锁的互斥,导致无法申请到排它锁,从而实现同一时间只有一个事物对同一条数据进行修改.同样当该条数据正在修改中但其所属的事物还未提交的时候,查询需要在这条数据上加上共享锁的过程也由于排它锁的存在导致被

事物隔离级别_悲观与乐观锁

  1.我印象中的事物事务是一种机制,它确保多个SQL语句被当作完整的操作单元来处理,所有的操作都完成时统一提交. 2.关系数据库的事务特性(ACID) ACID:原子性: 事务是数据库的逻辑工作单位,而且必须是原子工作单位,对于修改,要么全部执行,要么全部不执行        一致性:事务在完成时,必须是所有的数据都保持一致状态. 隔离型: 一个事务的执行不能被其他事务所影响.事务必须是互相隔离的,防止并发读写同一个数据的情况发生 持久性: 一个事务一旦提交,事物的操作便永久性的保存在DB中.

Hibernate 事物隔离级别 深入探究

目录 一.数据库事务的定义 二.数据库事务并发可能带来的问题 三.数据库事务隔离级别 四.使用Hibernate设置数据库隔离级别 五.使用悲观锁解决事务并发问题 六.使用乐观锁解决事务并发问题 Hibernate事务与并发问题处理(乐观锁与悲观锁) 一.数据库事务的定义 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作.事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源.通过将一组相关操作组合为一个要么全部成功

数据库事物隔离级别通俗理解

转自:http://www.oschina.net/question/258230_134502 总的说,数据库事物无非就两种:读取事物(select).修改事物(update,insert).在没有事物隔离控制的时候,多个事物在同一时刻对同一(数据的操作可能就会影响到最终期望的结果,通常有四种情况 (1) 两个更新事物同时修改一条数据时,很显然这种情况是最严重的了,程序中无论如何也不能出现这种情况,因为它会造成更新的丢失!通俗的讲,我更新时,你丫也更新这不就出问题了吗,艹,不行! (2) 一个

事物隔离级别

为了模拟并发环境,SQL SERVER中打开两个查询窗口(分别表示事务1.事务2)即可,并发用户用事务1,事务2简称 测试表脚本:CREATE TABLE [Customer](        [CustID] [int] NOT NULL,        [Fname] [nvarchar](20),        [Lname] [nvarchar](20),        [Address] [nvarchar](50),        [City] [nvarchar](20),    

Mysql 查看及设置事物隔离级别

1.查看 SELECT @@tx_isolation 2.设置 2.1所有级别 1)read uncommitted : 读取尚未提交的数据 :哪个问题都不能解决 2)read committed:读取已经提交的数据 :可以解决脏读 ---- oracle默认的 3)repeatable read:重读读取:可以解决脏读 和 不可重复读 ---mysql默认的 4)serializable:串行化:可以解决 脏读 不可重复读 和 虚读---相当于锁表 2.2 设置 设置mysql的隔离级别:se