這個(gè)問(wèn)題我實(shí)在是為整個(gè) springsource 的員工蒙羞

如果大家使用 spring 控制事務(wù),使用 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 報(bào)出的錯(cuò)誤,對(duì)于流量稍大一點(diǎn)的網(wǎng)站,一般都會(huì)出現(xiàn)

 

當(dāng)然,我確切的知道其原因是什么。


我只是想知道這個(gè)巨大的問(wèn)題為什么這么多年過(guò)去了,仍舊在反復(fù)的不斷地惱人的無(wú)解的一再 發(fā)生。


我花了些時(shí)間google了一下,發(fā)現(xiàn)搜索
"com.mchange.v2.resourcepool.TimeoutException"  這個(gè)字符串,前5頁(yè)都沒(méi)有給出正確答案。

 

有一些解決方案,我稱為workaround,并不是solution,例如
workaround1:
<!--當(dāng)連接池中的連接耗盡的時(shí)候c3p0一次同時(shí)獲取的連接數(shù)。Default: 3 --> 
<property name="acquireIncrement" value="5"/> 
workaround2:
是Spring中配置c3p0的時(shí)候,有一個(gè)配置屬性是checkoutTimeout,把這個(gè)配置屬性去掉就正常了。

 

好了,我來(lái)評(píng)價(jià)下這兩種 workaround

第一種:這么搞下去,你的數(shù)據(jù)庫(kù)連接數(shù)遲早會(huì)用光,到時(shí)結(jié)果是一樣的,好比得了癌 癥這樣做只是讓你晚死幾年。
第二種:很可笑,數(shù)據(jù)庫(kù)死鎖已經(jīng)發(fā)生了,只不過(guò)犧牲掉等待的線程罷了,好比說(shuō)有人在排隊(duì)等飯吃,但永遠(yuǎn)等不到,你跟他說(shuō)說(shuō)別等了,直接自裁算了。算是很善 良的做法,但是人終歸是死了,換句話說(shuō),那個(gè)線程終歸是未能給用戶返回正確的request。

 

另外,還有很多兄弟將這類問(wèn)題歸咎于無(wú)辜的c3p0,例如這樣的文章標(biāo)題:
"誰(shuí)來(lái)拯救C3P0的致命傷"

 

迄今為止,很少有人(我是沒(méi)發(fā)現(xiàn))了解,這個(gè)問(wèn)題實(shí)際上是Spring的致命傷, 我不清楚Spring的人曉不曉得這個(gè)問(wèn)題,反正我google了一下springsource.org的網(wǎng)站,僅有兩篇相關(guān)的論壇帖子,最終沒(méi)有適當(dāng)?shù)?回復(fù)。

 

兩篇?!

 

如果說(shuō)在座的各位曾經(jīng)run過(guò)流量較大的網(wǎng)站,也用Spring做事務(wù)控制,我想 有相當(dāng)一部分都遇到這個(gè)問(wèn)題,并且類似的問(wèn)題在google上用英文問(wèn)的人不計(jì)其數(shù),有正確回答嗎?沒(méi)有,至少我沒(méi)看到。

 

如何解釋如此多人遇到此類問(wèn)題,而spring的論壇上只有兩篇相關(guān)的論壇帖子 呢?
我不憚以最大的惡意來(lái)揣測(cè)SpringSource的諸位員工,即——被刪了。

 

反正我已經(jīng)以最大的惡意揣測(cè)過(guò) Rod Johnson 和他的嘍啰們了,不在乎多揣測(cè)一回。

 

因?yàn)椋琒pring知道,這是Spring的官方文檔中的巨大問(wèn)題,給無(wú)辜的用戶 們?cè)斐闪司薮髠Γ麄儫o(wú)法彌補(bǔ),至少暫時(shí)使用Spring當(dāng)前的架構(gòu),無(wú)法很輕松的彌補(bǔ)。

 

只要你用Spring控制事務(wù),寫了如下的事務(wù)控制配置:

<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>

 

那么,恭喜你,你成功掉進(jìn)了Spring給你挖好的大坑,義無(wú)反顧的跳進(jìn)去,還哭 著喊著這是c3p0或者ORM框架(例如Hibernate)給你挖的坑,誰(shuí)能救我?

 

答案是,沒(méi)有人能救你,只有你自己
珍惜生命,遠(yuǎn)離Spring。

 

真正原因是什么,如何解決,我今天沒(méi)空說(shuō)了,要說(shuō)的話,得畫n張圖,寫n行示例代 碼告訴你為什么。

 

今天就簡(jiǎn)單給幾個(gè)提示吧:
1、如果你不用spring,用jdbc,每次insert update delete 完了之后 commit 一下,問(wèn)題就會(huì)不見(jiàn)。
2、好吧,我承認(rèn)第一個(gè)提示是一種歷史的倒退,那么你不用Spring,使用你用的ORM框架在每次 insert update delete 完了之后 commit 一下,問(wèn)題也會(huì)不見(jiàn)。
3、好吧,我承認(rèn)第二個(gè)提示也是一種歷史的倒退,那么你不用Spring,用EJB3或者EJB3.1的@TransactionAttribute在你 每個(gè)涉及修改數(shù)據(jù)庫(kù)的方法上面標(biāo)記一下,并且聲明需要事務(wù),問(wèn)題也會(huì)不見(jiàn)。

 

總之,大家看到了,我都說(shuō)了不要用Spring,如果有兄弟非要較真,我非要用 Spring不可呢,沒(méi)問(wèn)題,我再給幾個(gè)提示:

1、如果你用Spring來(lái)如上的粗放型控制事務(wù),那么一定不敢用 OpenSessionInView模式,如果你不用,然后在每個(gè)會(huì)導(dǎo)致數(shù)據(jù)庫(kù)更新的方法上都標(biāo)注Spring的@Transactional并且聲明需 要事務(wù),問(wèn)題也可能會(huì)不見(jiàn)。
2、如果你非要用OpenSessionInView模式,還要用Spring控制事務(wù),那么對(duì)不起,無(wú)解。最好的結(jié)果是讓人直接自殺,而不是等待吃飯一 直等到餓死。

 

 

這個(gè)模式?jīng)]太大問(wèn)題,但你必須精細(xì)的控制Transaction begin的時(shí)機(jī),如果你在request上來(lái)不久就由于Spring的事務(wù)配置開(kāi)始了一個(gè)事務(wù),就基本上會(huì)導(dǎo)致死鎖、連接池耗盡的一系列問(wèn)題。

 

注意到了哈,我說(shuō)基本上,嗯,這么用了,還有得救。如果你能夠及時(shí)在每次更改數(shù)據(jù) 庫(kù)之后commit,并且在下一次更改之前重新start事務(wù),就不會(huì)死鎖。

 

但這么做,毫無(wú)疑問(wèn)代碼量會(huì)增大很多,可維護(hù)性會(huì)下降很多,不劃算對(duì)吧。

 

是的,在filter里邊 Open Session 或者 new 一個(gè) EntityManager 沒(méi)有錯(cuò),

 

常見(jiàn)錯(cuò)誤之一是沒(méi)有在正確的時(shí)候開(kāi)啟事務(wù),沒(méi)有在正確的時(shí)候關(guān)閉事務(wù),導(dǎo)致事務(wù)持 續(xù)的時(shí)間無(wú)謂的過(guò)長(zhǎng)。
常見(jiàn)錯(cuò)誤之二是你如果用了ORM,那么就不應(yīng)當(dāng)寫insert update delete語(yǔ)句,否則你會(huì)被迫無(wú)謂的在同一個(gè)request之內(nèi)commit若干次,否則?死鎖。

 

這個(gè)問(wèn)題存在了若干年,已經(jīng)成了一個(gè)傳說(shuō),今天就先說(shuō)到這里,有空再和大家詳細(xì)解 釋問(wèn)題的緣起、解決和展望。