2014年618前夕的某個晚上的某個系統(tǒng)的sql執(zhí)行時報錯了:
<!--添加同步數(shù)據(jù)--><insert id="insert" parameterClass="order"> INSERT INTO aa(ID,ORDERID,CREATEDATE) VALUES (seq.Nextval,#orderId#,#createDate#) <selectKey resultClass="java.lang.Long"> SELECT seq.CURRVAL FROM DUAL </selectKey></insert>會拋出800多條如下錯誤
Caused by: java.sql.SQLException: ORA-01013: 用戶請求取消當前的操作at Oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)at oracle.jdbc.driver.T4CTTIoer.PRocessError(T4CTTIoer.java:331)at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:745)at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:219)at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:970)
原因是sql執(zhí)行時間太長,jdbc驅(qū)動主動去取消了操作。
建議:
1、看一下該sql的平均執(zhí)行時間,對該sql設(shè)置一個超時時間。(執(zhí)行時間太長會對占用著連接,造成其他人拿不到連接)
2、找DBA咨詢下有沒有辦法優(yōu)化下該sql,比如能不能并行插入?;蛘吣懿荒茏龇謪^(qū)。
1、數(shù)據(jù)源配置
如果使用apache dbcp時,且可能遇到連接數(shù)瓶頸時,可以調(diào)整如下配置:
<!—建議以下值盡量一樣,沒必要頻繁的過期空閑連接(除非比如連接池資源緊缺,可以考慮) --><property name="maxIdle" value="80" /><property name="minIdle" value="80" /><property name="initialSize" value="80"/><property name="maxActive" value="80" />
<!—這個是等待獲取連接池連接時間,也不要太大,比如設(shè)置在500毫秒 --><property name="maxWait" value="500" />
<!-- 移除無引用連接(那些沒有close的連接)此處設(shè)置為false,需要保證程序中連接一定釋放 -->
<property name="removeAbandoned" value="false"></property><property name="removeAbandonedTimeout" value="300000"></property><!-- 一個連接空閑多久從池中移除,此處不做判斷--><property name="minEvictableIdleTimeMillis" value="-1" /><!-- 過期時循環(huán)測試多少次(0 就相當于關(guān)閉定時器) --><property name="numTestsPerEvictionRun" value="0" /><!-- expire connection 定時器周期 --><property name="timeBetweenEvictionRunsMillis" value="120000" />
<!-- 當連接空閑時是否測試,即保持連接一直存活,配合expire connection 定時器使用 -->
<property name="testWhileIdle" value="false"></property>
如果是MySQL庫,可能存在8小時問題,可以考慮開啟過期定時器(numTestsPerEvictionRun=1),定期過期一下連接,timeBetweenEvictionRunsMillis時間可以設(shè)置在8小時左右.
另外可以通過如下配置來配置socket連接/讀超時:
<property name="connectionProperties"
value="oracle.net.CONNECT_TIMEOUT=2000;oracle.jdbc.ReadTimeout=2000"></property>
(此處的連接和讀取超時時間,請根據(jù)自己業(yè)務(wù)來考慮大小)
更多配置可參考http://www.importnew.com/2466.html
2、ibatis配置
**項目使用的是ibatis-sqlmap-2.3.4.726.jar版本,而從2.3.1起:
o Removed maxTransactions, maxRequests, maxsessions from configuration, all are now controlled by the resource providers。(即已經(jīng)移除了maxTransactions, maxRequests, maxSessions配置)
因此我們只需要如下配置:
<settings cacheModelsEnabled="false" enhancementEnabled="true"
lazyLoadingEnabled="false" errorTracingEnabled="true" maxRequests="32"
defaultStatementTimeout="2"/>
defaultStatementTimeout單位是秒;根據(jù)業(yè)務(wù)配置。
如果想只設(shè)置某個Statement的超時時間,可以考慮:<insert ……timeout="2">
之前線上報如下錯誤,原因就是statement執(zhí)行超時了。
Cause:java.sql.SQLException:ORA-01013:用戶請求取消當前的操作
3、spring事務(wù)管理器配置
提供全局的事務(wù)級別的超時時間:
<bean id="oracleTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="oracleDataSource" />
<property name="defaultTimeout" value="2"/>
</bean>
總結(jié):
超時設(shè)置主要有以下幾個:
1、連接超時
2、讀數(shù)據(jù)超時
3、Statement超時
4、事務(wù)級別的超時=N* Statement超時 + GC 暫停time
之前總結(jié)過事務(wù)超時的一些問題,有興趣可以參考下:
http://jinnianshilongnian.iteye.com/blog/1986023
http://www.importnew.com/2466.html
另一個數(shù)據(jù)庫連接池需要注意的點:
<bean id="msqlDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
如果沒有加destroy-method ,而且重啟次數(shù)太頻繁,造成重啟tomcat 舊的數(shù)據(jù)庫連接池的連接不釋放,這樣會有好多數(shù)據(jù)庫連接占著一段時間不釋放;所以最好加上destroy-method。
//////////////////////亂七八糟///////////////////////
當超過最大的連接數(shù)目的時候,會刪除連接。
if (config != null && config.getRemoveAbandoned() && (getNumIdle() < 2) && (getNumActive() > getMaxActive() - 3) ) {
removeAbandoned();
}
這段代碼的作用是失效孤兒連接,即有人拿到連接但是沒有close的。
1、網(wǎng)絡(luò)阻塞/不穩(wěn)定時的級聯(lián)效應(比如我現(xiàn)在寫的ssdb-client 在網(wǎng)絡(luò)出現(xiàn)故障(網(wǎng)絡(luò)不可用)時 我會設(shè)置一個時間,在這個時間內(nèi)的請求全部tiemout)
連接池內(nèi)部應該根據(jù)當前網(wǎng)絡(luò)的狀態(tài)(比如超時次數(shù)太多),對于一定時間內(nèi)的(如100ms)全部timeout,根本不進行await(maxWait)。
還有一個就是當前等待連接池的人數(shù),比如現(xiàn)在等待1000個,那么接下來的等待是沒有意義的,這樣還會造成滾雪球(ssdb-client采用了這種策略)。
2、等待超時應該盡可能小點(除非很必要),即使返回錯誤頁,也比等待強。
dbcp的比較容易出問題的地就是 設(shè)置的超時時間太長,造成大量的TIMED_WAIT,線程阻塞,而且是滾雪球,一旦出問題很難立即回復,而且這個可以通過[1]說的解決。
大部分數(shù)據(jù)庫client都會有一個取消statement執(zhí)行的功能(即假設(shè)我們設(shè)置QueryTimeout=2秒,如果2秒內(nèi)沒返回信息,那么有個任務(wù)會主動發(fā)送一個取消的sql去取消當前statement的執(zhí)行)
1、mysql每個連接會創(chuàng)建一個Timer(每個Timer會創(chuàng)建一個Thread)
2、每創(chuàng)建一個Statement會提交一個TimerTask(每個Task在執(zhí)行時會創(chuàng)建一個Thread)
也就是說假設(shè)我們500個連接池,每個連接執(zhí)行1個statement,最壞的情況下會創(chuàng)建:
500*1+500*1=1000個線程。
假設(shè)一個應用中有三個mysql庫,那么最壞情況下有:
1000*3=3000個線程創(chuàng)建。
如果我們數(shù)據(jù)庫采用了分庫分表或者讀寫分離,可想而知。在壓力大的時候。
假設(shè)os對線程釋放不是特別快的話,cancel掉的線程可能并不是立即可用(我不確定,熟悉的同學可解釋下)。
而oracle采用不同的策略:
1、每個ClassLoader一個watchdog 線程(類似于mysql的timer);
2、每個Statement一個Task,而線程是在watchdog需要取消時去觸發(fā)的,即watchdog發(fā)現(xiàn)該Statement需要cancel時,調(diào)用其某個方法,該方法快速創(chuàng)建線程并運行;
也就是說假設(shè)我們500個連接池,每個連接執(zhí)行1個statement,最壞的情況下會創(chuàng)建:
1+500*1=501個線程。
假設(shè)一個應用中有三個mysql庫,那么最壞情況下有:
1 + 500*3=1501個線程創(chuàng)建。
解決方案:
1、最好的方案是改mysql實現(xiàn)。
2、修改底層系統(tǒng)支持的線程數(shù)。
//////////////////////亂七八糟///////////////////////
另外dbcp 1.x使用的是commons-pool 1.x,高并發(fā)下性能不是很好;考慮升級2.x或者如果新項目可以考慮使用druid或proxool,老項目還是慎重遷移(之前我遷移過是沒有問題的,不過還是慎重)。
===========================
新聞熱點
疑難解答