这个问题我实在是为整个 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控制事务,写了如下的事务控制配置:

< bean  id ="transactionInterceptor"  class ="org.springframework.transaction.interceptor.TransactionInterceptor" >
         < 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若干次,否则?死锁。

 

这个问题存在了若干年,已经成了一个传说,今天就先说到这里,有空再和大家详细解 释问题的缘起、解决和展望。