论坛首页 Java企业应用论坛

疑惑:有没有人真正用多线程工具(比如groboutils)测试过Spring的事务处理?

浏览 28066 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-08-06  
to mysaga
如果不用乐观锁,数据库也有自己的锁机制呀.为什么你测的这种多个线程操作同一帐户会出现死锁的情况呢?能把这种情况出现的具体说下吗?
0 请登录后投票
   发表时间:2009-08-06   最后修改:2009-08-06
先确认一下数据库死锁的定义:A B两个并发事务分别持有了对方需要的一行记录的更新锁,因此无论等待多久AB都无法得到需要的锁。

因此,楼主的测试可能会出现死锁,比如事务A B都要更新账户1和账户2的余额,只不过转账方向不同(对于企业账户,这种情况很正常),
如果A获得了账户1的锁,B获得了账户2的锁,于是出现互相等待。

现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。

前面说的都是数据库本身的特性,但它对于保证交易的一致性还不够,因为可能出现“更新丢失”现象(比如转账前后,帐不平),这只能靠应用(通过乐观锁或悲观锁)来解决,其中乐观锁不依赖数据库(靠version、时间戳等),而悲观锁依赖数据库的select for update特性。
0 请登录后投票
   发表时间:2009-08-06   最后修改:2009-08-06
Spring的事务处理绝对是线程安全的,否则也不要出来混了。
它把事务上下文、数据库连接、hibernate session等等这些非线程安全的东西都用threadlocal绑定到了当前线程,根本不存在线程安全的问题。
至于数据库连接池、hibernate session工厂等,本来就是线程安全的,不需要spring来保证。
0 请登录后投票
   发表时间:2009-08-06   最后修改:2009-08-06
haoxichuan 写道
to mysaga
如果不用乐观锁,数据库也有自己的锁机制呀.为什么你测的这种多个线程操作同一帐户会出现死锁的情况呢?能把这种情况出现的具体说下吗?


我的看法是:数据库锁是保证数据一致的基本手段。而就是因为有了数据库锁,才会出现“死锁”——当然,没有数据库锁就没有死锁(我试过,不用任何一种锁,确实是没有异常的),但那样的话也就无法保证数据一致,那样的系统也就没什么人敢用了。

在我的测试里,如果使用乐观锁(hibernate 的 version 版本),出现的异常是

ERROR AbstractFlushingEventListener:324 - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)


如果使用悲观锁(LockMode.UPGRADE,其实也就是“select ... for update”),出现的异常是

ERROR JDBCExceptionReporter:101 - Deadlock found when trying to get lock; try restarting transaction



然而无论哪种锁哪种异常,在正确的捕捉异常后,我总可以看见 junit 的绿色条:因为数据完全保持了一致。我想这就是事务处理、锁、。。。。等等的意义所在。

而当然,在实际系统中,处理异常也是很重要的:至少,必须告诉相关的用户:“你刚刚的操作失败,请稍后再试。”
0 请登录后投票
   发表时间:2009-08-06  
daquan198163 写道
先确认一下数据库死锁的定义:A B两个并发事务分别持有了对方需要的一行记录的更新锁,因此无论等待多久AB都无法得到需要的锁。

因此,楼主的测试可能会出现死锁,比如事务A B都要更新账户1和账户2的余额,只不过转账方向不同(对于企业账户,这种情况很正常),
如果A获得了账户1的锁,B获得了账户2的锁,于是出现互相等待。

现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。

前面说的都是数据库本身的特性,但它对于保证交易的一致性还不够,因为可能出现“更新丢失”现象(比如转账前后,帐不平),这只能靠应用(通过乐观锁或悲观锁)来解决,其中乐观锁不依赖数据库(靠version、时间戳等),而悲观锁依赖数据库的select for update特性。


赞成你的观点。特别是这一句:“现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。”这也是我现在的看法:在高并发的情形下,操作失败难以避免,系统的责任就是(1)正确处理异常(包括正确的提示用户),(2)严格保证数据一致。

关于锁,在我的实际测试中,也发现spring端的事务处理和锁(乐观或悲观),是二者缺一不可的。去掉任何一个,都会造成户头总额前后不一致。

通过发这个帖子以及后来的讨论,我确实学到许多东西。感谢各位热心的兄弟。
0 请登录后投票
   发表时间:2009-08-06   最后修改:2009-08-06
非常感谢 daquan198163 mysaga 的解释
也就是说在高并发的情况下,数据库会出现一些死锁,或者其它错误,数据库能解决这些事情,但是他的解决方案不能保证数据的正确性.我们通过程序使用事务和锁去保证我们数据的正确性.使用 乐观或悲观 锁都可以解决是吧~~~

LZ最开始的时候测试是没有加任何锁是吗?加悲观锁是不是只能在SQL语句上加,有没有其它可配置的地方?
0 请登录后投票
   发表时间:2009-08-06   最后修改:2009-08-06
haoxichuan 写道
非常感谢 daquan198163 mysaga 的解释
也就是说在高并发的情况下,数据库会出现一些死锁,或者其它错误,数据库能解决这些事情,但是他的解决方案不能保证数据的正确性.我们通过程序使用事务和锁去保证我们数据的正确性.使用 乐观或悲观 锁都可以解决是吧~~~

你先看一下数据库的隔离级别的概念。
如果把数据库隔离级别设成serialize或repeatable read,也能保证数据的正确性,但性能差,也不是所有数据库都支持。所以,通常情况下,数据库都采用read commited,既保证了性能又保证了绝大多数(80%)情况下的一致性,
但时还有那20%——并发更新同一条记录——会出现更新丢失的问题,
于是就需要针对这20%的操作采用锁,如果预计很少出现并发冲突并且允许操作失败,就采用乐观锁,反之采用悲观锁。
引用
LZ最开始的时候测试是没有加任何锁是吗?加悲观锁是不是只能在SQL语句上加,有没有其它可配置的地方?

最开始没有加。 悲观锁最终都要体现到select for update,但hibernate提供了api,jdo可以配置。
参考《POJO in action》
1 请登录后投票
   发表时间:2009-08-06  
haoxichuan 写道
非常感谢 daquan198163 mysaga 的解释
也就是说在高并发的情况下,数据库会出现一些死锁,或者其它错误,数据库能解决这些事情,但是他的解决方案不能保证数据的正确性.我们通过程序使用事务和锁去保证我们数据的正确性.使用 乐观或悲观 锁都可以解决是吧~~~


daquan198163在你楼下解释得很清楚了。。。。

haoxichuan 写道
LZ最开始的时候测试是没有加任何锁是吗?加悲观锁是不是只能在SQL语句上加,有没有其它可配置的地方?



这个问题有点复杂。

一句话答案:是的,最开始的时候没有加,是我的疏漏。所以那时候测试不能通过,数据不一致。

后来加上了,但是又被那些死锁的异常给迷惑了。

当然现在明白,死锁并不可怕,只要处理好就行了。而正因为加上了锁,测试才能通过,数据才变得一致。
11 请登录后投票
   发表时间:2009-08-11  
我想很多刚学习的人看懂这个帖子应该还是有点点难度的吧

反正我是了2遍才看明白:

原来楼主的有2个问题:

1,数据库本身的死锁,多线程操作2条记录以上时,出现相互等待update现象.
2,JVM对象的并发问题.当然是由于
int from = randomGenerator.nextInt(accounts.length);
这条代码引起的,可能上千个线程共用了一个accounts对象.

如上有错误的地方,还请大家指正.
0 请登录后投票
   发表时间:2009-08-14  
C_J 写道
我想很多刚学习的人看懂这个帖子应该还是有点点难度的吧

反正我是了2遍才看明白:

原来楼主的有2个问题:

1,数据库本身的死锁,多线程操作2条记录以上时,出现相互等待update现象.
2,JVM对象的并发问题.当然是由于
int from = randomGenerator.nextInt(accounts.length);
这条代码引起的,可能上千个线程共用了一个accounts对象.

如上有错误的地方,还请大家指正.


基本上是这样的。

1,是真正困扰我的问题。

2,“JVM对象的并发”,纯粹是不小心,出的低级错误。有人指出后,我马上就改了。

现在回想起来,平常编程的时候,确实习惯了从 hibernate (无2级缓存)里面拿数据,处理,然后更新——那种情形下,很难出现这种多线程访问同一对象的错误,所以在写多线程测试用例的时候,就大意了。。。。。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics