该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-08-06
to mysaga
如果不用乐观锁,数据库也有自己的锁机制呀.为什么你测的这种多个线程操作同一帐户会出现死锁的情况呢?能把这种情况出现的具体说下吗? |
|
返回顶楼 | |
发表时间:2009-08-06
最后修改:2009-08-06
先确认一下数据库死锁的定义:A B两个并发事务分别持有了对方需要的一行记录的更新锁,因此无论等待多久AB都无法得到需要的锁。
因此,楼主的测试可能会出现死锁,比如事务A B都要更新账户1和账户2的余额,只不过转账方向不同(对于企业账户,这种情况很正常), 如果A获得了账户1的锁,B获得了账户2的锁,于是出现互相等待。 现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。 前面说的都是数据库本身的特性,但它对于保证交易的一致性还不够,因为可能出现“更新丢失”现象(比如转账前后,帐不平),这只能靠应用(通过乐观锁或悲观锁)来解决,其中乐观锁不依赖数据库(靠version、时间戳等),而悲观锁依赖数据库的select for update特性。 |
|
返回顶楼 | |
发表时间:2009-08-06
最后修改:2009-08-06
Spring的事务处理绝对是线程安全的,否则也不要出来混了。
它把事务上下文、数据库连接、hibernate session等等这些非线程安全的东西都用threadlocal绑定到了当前线程,根本不存在线程安全的问题。 至于数据库连接池、hibernate session工厂等,本来就是线程安全的,不需要spring来保证。 |
|
返回顶楼 | |
发表时间: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 的绿色条:因为数据完全保持了一致。我想这就是事务处理、锁、。。。。等等的意义所在。 而当然,在实际系统中,处理异常也是很重要的:至少,必须告诉相关的用户:“你刚刚的操作失败,请稍后再试。” |
|
返回顶楼 | |
发表时间:2009-08-06
daquan198163 写道 先确认一下数据库死锁的定义:A B两个并发事务分别持有了对方需要的一行记录的更新锁,因此无论等待多久AB都无法得到需要的锁。
因此,楼主的测试可能会出现死锁,比如事务A B都要更新账户1和账户2的余额,只不过转账方向不同(对于企业账户,这种情况很正常), 如果A获得了账户1的锁,B获得了账户2的锁,于是出现互相等待。 现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。 前面说的都是数据库本身的特性,但它对于保证交易的一致性还不够,因为可能出现“更新丢失”现象(比如转账前后,帐不平),这只能靠应用(通过乐观锁或悲观锁)来解决,其中乐观锁不依赖数据库(靠version、时间戳等),而悲观锁依赖数据库的select for update特性。 赞成你的观点。特别是这一句:“现在的数据库可以帮我们自动解决绝大部分死锁,这里的解决指的应该是避免出现无限等待,一旦发现死锁就抛异常,强行终止一个事务。”这也是我现在的看法:在高并发的情形下,操作失败难以避免,系统的责任就是(1)正确处理异常(包括正确的提示用户),(2)严格保证数据一致。 关于锁,在我的实际测试中,也发现spring端的事务处理和锁(乐观或悲观),是二者缺一不可的。去掉任何一个,都会造成户头总额前后不一致。 通过发这个帖子以及后来的讨论,我确实学到许多东西。感谢各位热心的兄弟。 |
|
返回顶楼 | |
发表时间:2009-08-06
最后修改:2009-08-06
非常感谢 daquan198163 mysaga 的解释
也就是说在高并发的情况下,数据库会出现一些死锁,或者其它错误,数据库能解决这些事情,但是他的解决方案不能保证数据的正确性.我们通过程序使用事务和锁去保证我们数据的正确性.使用 乐观或悲观 锁都可以解决是吧~~~ LZ最开始的时候测试是没有加任何锁是吗?加悲观锁是不是只能在SQL语句上加,有没有其它可配置的地方? |
|
返回顶楼 | |
发表时间: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》 |
|
返回顶楼 | |
发表时间:2009-08-06
haoxichuan 写道 非常感谢 daquan198163 mysaga 的解释
也就是说在高并发的情况下,数据库会出现一些死锁,或者其它错误,数据库能解决这些事情,但是他的解决方案不能保证数据的正确性.我们通过程序使用事务和锁去保证我们数据的正确性.使用 乐观或悲观 锁都可以解决是吧~~~ daquan198163在你楼下解释得很清楚了。。。。 haoxichuan 写道 LZ最开始的时候测试是没有加任何锁是吗?加悲观锁是不是只能在SQL语句上加,有没有其它可配置的地方?
这个问题有点复杂。 一句话答案:是的,最开始的时候没有加,是我的疏漏。所以那时候测试不能通过,数据不一致。 后来加上了,但是又被那些死锁的异常给迷惑了。 当然现在明白,死锁并不可怕,只要处理好就行了。而正因为加上了锁,测试才能通过,数据才变得一致。 |
|
返回顶楼 | |
发表时间:2009-08-11
我想很多刚学习的人看懂这个帖子应该还是有点点难度的吧
反正我是了2遍才看明白: 原来楼主的有2个问题: 1,数据库本身的死锁,多线程操作2条记录以上时,出现相互等待update现象. 2,JVM对象的并发问题.当然是由于 int from = randomGenerator.nextInt(accounts.length); 这条代码引起的,可能上千个线程共用了一个accounts对象. 如上有错误的地方,还请大家指正. |
|
返回顶楼 | |
发表时间:2009-08-14
C_J 写道 我想很多刚学习的人看懂这个帖子应该还是有点点难度的吧
反正我是了2遍才看明白: 原来楼主的有2个问题: 1,数据库本身的死锁,多线程操作2条记录以上时,出现相互等待update现象. 2,JVM对象的并发问题.当然是由于 int from = randomGenerator.nextInt(accounts.length); 这条代码引起的,可能上千个线程共用了一个accounts对象. 如上有错误的地方,还请大家指正. 基本上是这样的。 1,是真正困扰我的问题。 2,“JVM对象的并发”,纯粹是不小心,出的低级错误。有人指出后,我马上就改了。 现在回想起来,平常编程的时候,确实习惯了从 hibernate (无2级缓存)里面拿数据,处理,然后更新——那种情形下,很难出现这种多线程访问同一对象的错误,所以在写多线程测试用例的时候,就大意了。。。。。 |
|
返回顶楼 | |