关于使用Spring导致c3p0数据库死锁问题

这个问题我实在是为整个 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控制事务,那么对不起,无解。最好的结果是让人直接自杀,而不是等待吃饭一直等到饿死。

 

哑谜打到现在,有兄弟说了,我骂了Spring半天,不就是不能用OpenSessionInView模式吗?但,凡是个像样的牛牛写的web数据库访问指南,都说要用这个模式呢,和Spring有啥关系。

 

是的,你说对了一部分,请在Google上搜索一下“opensessioninviewfilter”这个字符串,返回280,000条结果,对吧。这个类是谁写的?是Spring写的。

 

再访问下这个链接:
http://www.google.cn/search?q=opensessioninviewfilter+site:springsource.org
有11300条结果,是spring的官网的搜索结果。

 

对不起,我今天实在没时间详细讲解这个filter与Spring事务控制无间配合之后的邪恶之处,我只能说,如果你用了,你基本上死定了。

 

这个模式没太大问题,但你必须精细的控制Transaction begin的时机,如果你在request上来不久就由于Spring的事务配置开始了一个事务,就基本上会导致死锁、连接池耗尽的一系列问题。

 

注意到了哈,我说基本上,嗯,这么用了,还有得救。如果你能够及时在每次更改数据库之后commit,并且在下一次更改之前重新start事务,就不会死锁。

 

但这么做,毫无疑问代码量会增大很多,可维护性会下降很多,不划算对吧。

 

是的,在filter里边 Open Session 或者 new 一个 EntityManager 没有错,

 

常见错误之一是没有在正确的时候开启事务,没有在正确的时候关闭事务,导致事务持续的时间无谓的过长。
常见错误之二是你如果用了ORM,那么就不应当写insert update delete语句,否则你会被迫无谓的在同一个request之内commit若干次,否则?死锁。

 

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


P.S. 不用Spring也同样可能导致这个问题,那是另一码事了。本篇文章的主旨,是说我们被人卖了还帮人数钱的事情;而不是讨论我们被卖了到底怎么赎身(另文附上)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值