遇到一个多用户并发的有关问题,哪位高手说查询操作不用做事务处理或者不用加锁

遇到一个多用户并发的问题,谁说查询操作不用做事务处理或者不用加锁?
查询操作不用做事务处理或者不用加锁,以前一直比较同意,今天遇到一个实际的问题,原来这种想法是错误的.

项目中遇到一个用户资金扣除出错的问题:
系统采用了quartz来做定时任务的处理,在某个时间点,自动对用户购买方案进行扣款,通过对日志的跟踪发现:当用户购买了多个方案时,有时用户可用资金会出错。

要实现的功能:
查询用户的可用资金,然后扣款。
假设用户w当前可用资金为1000元,应该执行两次扣款,第一次100,第二次50,用户w正确的余额应该是850。

出错原因:
扣款前要先查询用户当前的可用金额,然后再扣款.
通过日志发现,线程a查询完用户w的可用金额为1000元后,暂停了执行扣款的操作。
然后线程b开始执行,线程b查询用户w的可用金额为1000元,然后扣款50,更新DB,这时用户w余额是950。
然后,线程a在1000的基础上扣款100,更新DB,这时用户w的最终余额是900元。显而易见,用户w的最终余额是错误的。

结论:查询操作不用做事务处理或者说不用加锁是错误的。

其实这是一个丢失更新的典型的场景,但在实际中真实地遇到它,还是让人很兴奋啊.

欢迎大家批评指正。
1 楼 daquan198163 2007-12-25  
乐观锁是用来解决这个问题的
2 楼 sorphi 2007-12-25  
你这里的场景:查询操作只是一个完整事务的一部分

可别从一个极端到另外一个极端
3 楼 WorkingHard?! 2007-12-25  
关键是你更新的策略问题,你在进行更新的时候逻辑是怎么写的~

如果是查询出来余额判断后执行更新操作,那么你这个逻辑的本身也是属于更新的一部分,方法类似

public void updateAccount(***){
   double account  =  queryAccout();
   XXDAO.update(XXX);
}

事务的设置可以用声明式事务对该方法进行设定 或者 自己手动控制事务
4 楼 抛出异常的爱 2007-12-25  
脏读...锁机制存在的主要原因之一
5 楼 charles751 2007-12-25  
WorkingHard?! 写道
关键是你更新的策略问题,你在进行更新的时候逻辑是怎么写的~

如果是查询出来余额判断后执行更新操作,那么你这个逻辑的本身也是属于更新的一部分,方法类似

public void updateAccount(***){
   double account  =  queryAccout();
   XXDAO.update(XXX);
}

事务的设置可以用声明式事务对该方法进行设定 或者 自己手动控制事务



同意.

投入门贴的朋友们,你们都遇到过这种场景吗?表示怀疑
6 楼 ddandyy 2007-12-25  
所谓的查询  是指整个事务里只有一个查询
7 楼 charles751 2007-12-25  
ddandyy 写道
所谓的查询  是指整个事务里只有一个查询


看来大家的看法比较一致啊.
我这个场景中,查询只是事务的一部分而已.

这个查询方法在dao层中,事务由service方法做处理
8 楼 ideafrog 2007-12-25  
你的数据库是oracle吗?

不同的数据库有不同的锁机制。你这样说,根本就是在乱扯。虽然有那么点道理,但是与问题本质没有关系
9 楼 charles751 2007-12-25  
ideafrog 写道
你的数据库是oracle吗?

不同的数据库有不同的锁机制。你这样说,根本就是在乱扯。虽然有那么点道理,但是与问题本质没有关系


能解释下您说的本质吗
10 楼 movingboy 2007-12-25  
最基本的事务处理问题啊~~~
“查询操作不用做事务处理或者不用加锁”是指不需要对查询出来的数据进行更新(包括删除),自然不需要进行事务管理了。楼主的情况很明显不同,当然需要事务管理了。建议楼主再读点数据库基础方面的书
11 楼 hocus 2007-12-25  
查询和更新应该做到一个事务控制的方法里。
该方法会被多个线程调用,所以该方法需要同步控制。
12 楼 soci 2007-12-25  
多线程共享资源啊 和数据库没啥关系  和俩线程访问同一个实例是一回事
13 楼 xiangzhouwang 2007-12-26  
控制下加锁级别
14 楼 charles751 2007-12-26  
第一贴就成新手贴,可悲啊.
看来我水平有限啊,还是到serverside去混吧