這個問題我實在是為整個 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若干次,否則?死鎖。
這個問題存在了若干年,已經成了一個傳說,今天就先說到這里,有空再和大家詳細解 釋問題的緣起、解決和展望。