这个问题我实在是为整个 springsource 的员工蒙羞
如果大家使用 spring 控制事务,使用 Open Session In View 模式,
com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool-- timeout at awaitAvailable()
com.mchange.v2.async.ThreadPoolAsynchronousRunner$DeadlockDetector -- APPARENT DEADLOCK!!!
还有诸如之类的若干 c3p0 报出的错误,对于流量稍大一点的网站,一般都会出现
当然,我确切的知道其原因是什么。
我只是想知道这个巨大的问题为什么这么多年过去了,仍旧在反复的不断地恼人的无解的一再 发生。
我花了些时间google了一下,发现搜索
"com.mchange.v2.resourcepool.TimeoutException" 这个字符串,前5页都没有给出正确答案。
有一些解决方案,我称为workaround,并不是solution,例如
workaround1:
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
<property name="acquireIncrement" value="5"/>
workaround2:
是Spring中配置c3p0的时候,有一个配置属性是checkoutTimeout,把这个配置属性去掉就正常了。
好了,我来评价下这两种 workaround
第一种:这么搞下去,你的数据库连接数迟早会用光,到时结果是一样的,好比得了癌 症这样做只是让你晚死几年。
第二种:很可笑,数据库死锁已经发生了,只不过牺牲掉等待的线程罢了,好比说有人在排队等饭吃,但永远等不到,你跟他说说别等了,直接自裁算了。算是很善 良的做法,但是人终归是死了,换句话说,那个线程终归是未能给用户返回正确的request。
另外,还有很多兄弟将这类问题归咎于无辜的c3p0,例如这样的文章标题:
"谁来拯救C3P0的致命伤"
迄今为止,很少有人(我是没发现)了解,这个问题实际上是Spring的致命伤, 我不清楚Spring的人晓不晓得这个问题,反正我google了一下springsource.org的网站,仅有两篇相关的论坛帖子,最终没有适当的 回复。
两篇?!
如果说在座的各位曾经run过流量较大的网站,也用Spring做事务控制,我想 有相当一部分都遇到这个问题,并且类似的问题在google上用英文问的人不计其数,有正确回答吗?没有,至少我没看到。
如何解释如此多人遇到此类问题,而spring的论坛上只有两篇相关的论坛帖子 呢?
我不惮以最大的恶意来揣测SpringSource的诸位员工,即——被删了。
反正我已经以最大的恶意揣测过 Rod Johnson 和他的喽啰们了,不在乎多揣测一回。
因为,Spring知道,这是Spring的官方文档中的巨大问题,给无辜的用户 们造成了巨大伤害,他们无法弥补,至少暂时使用Spring当前的架构,无法很轻松的弥补。
只要你用Spring控制事务,写了如下的事务控制配置:
< property name ="transactionManager" ref ="transactionManager" />
< property name ="transactionAttributes" >
< props >
< prop key ="save*" >PROPAGATION_REQUIRED </ prop >
< prop key ="add*" >PROPAGATION_REQUIRED </ prop >
< prop key ="set*" >PROPAGATION_REQUIRED </ prop >
< prop key ="delete*" >PROPAGATION_REQUIRED </ prop >
< prop key ="create*" >PROPAGATION_REQUIRED </ prop >
< prop key ="get*" >PROPAGATION_REQUIRED,readOnly </ prop >
< prop key ="*" >PROPAGATION_REQUIRED </ prop >
</ props >
</ property >
</ bean >
或者
< tx:advice id ="txAdvice" transaction-manager ="transactionManager" >
< tx:attributes >
< tx:method name ="update*" propagation ="REQUIRED" />
< tx:method name ="insert*" propagation ="REQUIRED" />
< tx:method name ="save*" propagation ="REQUIRED" />
< tx:method name ="delete*" propagation ="REQUIRED" />
< tx:method name ="add*" propagation ="REQUIRED" />
< tx:method name ="aud*" propagation ="REQUIRED" />
< tx:method name ="get*" propagation ="REQUIRED" read-only ="true" />
< tx:method name ="find*" propagation ="REQUIRED" read-only ="true" />
</ tx:attributes >
</ tx:advice >
那么,恭喜你,你成功掉进了Spring给你挖好的大坑,义无反顾的跳进去,还哭 着喊着这是c3p0或者ORM框架(例如Hibernate)给你挖的坑,谁能救我?
答案是,没有人能救你,只有你自己
珍惜生命,远离Spring。
真正原因是什么,如何解决,我今天没空说了,要说的话,得画n张图,写n行示例代 码告诉你为什么。
今天就简单给几个提示吧:
1、如果你不用spring,用jdbc,每次insert update delete 完了之后 commit 一下,问题就会不见。
2、好吧,我承认第一个提示是一种历史的倒退,那么你不用Spring,使用你用的ORM框架在每次 insert update delete 完了之后 commit 一下,问题也会不见。
3、好吧,我承认第二个提示也是一种历史的倒退,那么你不用Spring,用EJB3或者EJB3.1的@TransactionAttribute在你 每个涉及修改数据库的方法上面标记一下,并且声明需要事务,问题也会不见。
总之,大家看到了,我都说了不要用Spring,如果有兄弟非要较真,我非要用 Spring不可呢,没问题,我再给几个提示:
1、如果你用Spring来如上的粗放型控制事务,那么一定不敢用 OpenSessionInView模式,如果你不用,然后在每个会导致数据库更新的方法上都标注Spring的@Transactional并且声明需 要事务,问题也可能会不见。
2、如果你非要用OpenSessionInView模式,还要用Spring控制事务,那么对不起,无解。最好的结果是让人直接自杀,而不是等待吃饭一 直等到饿死。
这个模式没太大问题,但你必须精细的控制Transaction begin的时机,如果你在request上来不久就由于Spring的事务配置开始了一个事务,就基本上会导致死锁、连接池耗尽的一系列问题。
注意到了哈,我说基本上,嗯,这么用了,还有得救。如果你能够及时在每次更改数据 库之后commit,并且在下一次更改之前重新start事务,就不会死锁。
但这么做,毫无疑问代码量会增大很多,可维护性会下降很多,不划算对吧。
是的,在filter里边 Open Session 或者 new 一个 EntityManager 没有错,
常见错误之一是没有在正确的时候开启事务,没有在正确的时候关闭事务,导致事务持 续的时间无谓的过长。
常见错误之二是你如果用了ORM,那么就不应当写insert update delete语句,否则你会被迫无谓的在同一个request之内commit若干次,否则?死锁。
这个问题存在了若干年,已经成了一个传说,今天就先说到这里,有空再和大家详细解 释问题的缘起、解决和展望。