iBATIS框架batch處理優(yōu)化淺析
iBATIS框架batch處理優(yōu)化的情況。為什么要做batch處理這個問題我就不解釋了,因為我想你們肯定能比我解釋的更好!如果你真的不知道,那就到雅虎上去搜索一下吧
Oracle回滾段
這個問題偶也不很明白,只是大概有個了解,如果你是這方面的專家,或者對這方面有比較深的理解,別忘了跟偶分享哦☻
在JDBC中如何做batch處理
JDBC提供了數(shù)據(jù)庫batch處理的能力,在數(shù)據(jù)大批量操作(新增、刪除等)的情況下可以大幅度提升系統(tǒng)的性能。我以前接觸的一個項目,在沒有采用batch處理時,刪除5萬條數(shù)據(jù)大概要半個小時左右,后來對系統(tǒng)進行改造,采用了batch處理的方式,刪除5萬條數(shù)據(jù)基本上不會超過1分鐘。看一段JDBC代碼:
- //關(guān)閉自動執(zhí)行
- con.setAutoCommit(false);
- Statementstmt=con.createStatement();
- stmt.addBatch("INSERTINTOemployeesVALUES(1000,'JoeJones')");
- stmt.addBatch("INSERTINTOdepartmentsVALUES(260,'Shoe')");
- stmt.addBatch("INSERTINTOemp_deptVALUES(1000,260)");
- //提交一批要執(zhí)行的更新命令
- int[]updateCounts=stmt.executeBatch();
本例中禁用了自動執(zhí)行模式,從而在調(diào)用Statement.executeBatch()時可以防止JDBC執(zhí)行事務(wù)處理。禁用自動執(zhí)行使得應(yīng)用程序能夠在發(fā)生錯誤及批處理中的某些命令不能執(zhí)行時決定是否執(zhí)行事務(wù)處理。因此,當(dāng)進行批處理更新時,通常應(yīng)該關(guān)閉自動執(zhí)行。
在JDBC2.0中,Statement對象能夠記住可以一起提交執(zhí)行的命令列表。創(chuàng)建語句時,與它關(guān)聯(lián)的命令列表為空。Statement.addBatch()方法為調(diào)用語句的命令列表添加一個元素。如果批處理中包含有試圖返回結(jié)果集的命令,則當(dāng)調(diào)用Statement.executeBatch()時,將拋出SQLException。只有DDL和DML命令(它們只返回簡單的更新計數(shù))才能作為批處理的一部分來執(zhí)行。如果應(yīng)用程序決定不提交已經(jīng)為某語句構(gòu)
造的命令批處理,則可以調(diào)用方法Statement.clearBatch()(以上沒有顯示)來重新設(shè)置批處理。
Statement.executeBatch()方法將把命令批處理提交給基本DBMS來執(zhí)行。命令的執(zhí)行將依照在批處理中的添加順序來進行。ExecuteBatch()為執(zhí)行的命令返回更新計數(shù)數(shù)組。數(shù)組中對應(yīng)于批處理中的每個命令都包含了一項,而數(shù)組中各元素依據(jù)命令的執(zhí)行順序(這還是和命令的最初添加順序相同)來排序。調(diào)用executeBatch()將關(guān)閉發(fā)出調(diào)用的Statement對象的當(dāng)前結(jié)果集(如果有一個結(jié)果集是打開的)。executeBatch()返回后,將重新將語句的內(nèi)部批處理命令列表設(shè)置為空。
如果批處理中的某個命令無法正確執(zhí)行,則ExecuteBatch()將拋出BatchUpdateException。可以調(diào)用BatchUpdateException.getUpdateCounts()方法來為批處理中成功執(zhí)行的命令返回更新計數(shù)的整型數(shù)組。因為當(dāng)有第一個命令返回錯誤時,Statement.executeBatch()就中止,而且這些命令是依據(jù)它們在批處理中的添加順序而執(zhí)行的。所以如果BatchUpdateException.getUpdateCounts()所返回的數(shù)組包含N個元素,這就意味著在調(diào)用executeBatch()時批處理中的前N個命令被成功執(zhí)行。用PreparedStatement可以象下面這樣寫代碼:
- //關(guān)閉自動執(zhí)行
- con.setAutoCommit(false);
- PreparedStatementstmt=con.prepareStatement("INSERTINTOemployeesVALUES(?,?)");
- stmt.setInt(1,2000);
- stmt.setString(2,"KellyKaufmann");
- stmt.addBatch();
- ???
- //提交要執(zhí)行的批處理
- int[]updateCounts=stmt.executeBatch();
iBATIS框架對batch處理優(yōu)化的支持
iBATIS框架對batch處理提供了很好的支持,底層的實現(xiàn)方式就是JDBC。下面看一段示例代碼:
- privatevoidexecute(SqlMapClientclient){
- if(log.isDebugEnabled()){
- log.debug("executestart...");
- }
- client.startBatch();
- for(inti=0;i<2000;i++){
- client.delete("deletefromorderwhereid=?",i);
- }
- client.executeBatch();
- if(log.isDebugEnabled()){
- log.debug("executeend...");
- }
- }
iBATIS框架做batch處理的問題
在一個batch中只能對一個表進行操作,例如插入或刪除。當(dāng)有多個表需要處理時,只能放在多個batch中進行處理。看下面的一段代碼:
代碼1:
- privatevoidexecute(intfrom,intto,Listlist){
- if(log.isDebugEnabled()){
- log.debug("STRGHousekeepTaskexecutestart...");
- }
- HKSqlMapWrappersqlWrapper=HKSqlMapWrapper.newInstance();
- sqlWrapper.startBatch();
- for(inti=from;i<to;i++){
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
- }
- sqlWrapper.execBatch();
- if(log.isDebugEnabled()){
- log.debug("STRGHousekeepTaskexecuteend...");
- }
- }
這段代碼的目的就是要刪除數(shù)據(jù)庫中3個表的數(shù)據(jù),sqlWrapper是iBATIS的SqlMapClient的一個包裝器,主要是封狀對事物的控制。當(dāng)批次(既to-from的值)很小的時候,這樣寫是沒有問題的。盡管這段代碼的本意是要享受batch處理帶來的好處,但是事實上這段代碼并不會真正達到預(yù)期的效果,至于原因,我們一會在進行分析☻。我們先來看下面一段代碼:
- privatevoidexecute(intfrom,intto,Listlist){
- if(log.isDebugEnabled()){
- log.debug("STRGHousekeepTaskexecutestart...");
- }
- HKSqlMapWrappersqlWrapper=HKSqlMapWrapper.newInstance();
- sqlWrapper.startBatch();
- for(inti=from;i<to;i++){
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
- }
- for(inti=from;i<to;i++){
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
- }
- for(inti=from;i<to;i++){
- sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
- }
- sqlWrapper.execBatch();
- if(log.isDebugEnabled()){
- log.debug("STRGHousekeepTaskexecuteend...");
- }
- }
代碼2
正如你所看到的,和代碼1相比它只是做了3次循環(huán),每個循環(huán)執(zhí)行一個表的操作。雖然麻煩,但是卻真正的享受到了batch處理的好處!下面是時候解釋一下這兩段代碼幕后的秘密了☻。
在前面的章節(jié)里已經(jīng)解釋了JDBC如何做batch處理,如果還不清楚的話請查看前面的章節(jié)。要解釋這兩段代碼里面的玄機,還得看一段代碼☻下面的代碼是從iBATIS源碼中提取的:
- publicvoidaddBatch(RequestScoperequest,Connectionconn,Stringsql,Object[]parameters){
- PreparedStatementps=null;
- if(currentSql!=null
- &&sql.hashCode()==currentSql.hashCode()
- &&sql.length()==currentSql.length()){
- intlast=statementList.size()-1;
- ps=(PreparedStatement)statementList.get(last);
- }else{
- ps=conn.prepareStatement(sql);
- currentSql=sql;
- statementList.add(ps);
- }
- request.getParameterMap().setParameters(request,ps,parameters);
- ps.addBatch();
- size++;
- }
這就是iBATIS中batch處理的做法,在這里不想對這段代碼做一一解釋,有興趣的可以自己查看一下iBATIS的源碼,我們只關(guān)心它如何對一條語句進行處理。參數(shù)sql是要進行batch處理的語句,parameters是sql的參數(shù)列表,如果sql和實例變量currentSql相等,則從statementList列表里面得到一個PreparedStatement,然后進行batch處理,如果不等就新生成一個PreparedStatement對象,并把它加到statementList列表里面,并把當(dāng)前sql的值附給currentSql,下次傳遞來sql的時候就會和這個新的currentSql比較。這就是為什么在一個循環(huán)里面只對一個表進行處理的原因了。如果在一個循環(huán)里面對多個表進行處理,每次傳給addBatch方法的sql都是新的,都會生成一個新的PreparedStatement,所以也就享受不到batch處理帶來的好處了!
按照代碼1的方式執(zhí)行程序,當(dāng)batchsize很小的時候盡管享受不到batch處理帶來的好處,但是也不至于會出什么大問題,但是當(dāng)batchsize值很大的時候(我在程序中試驗過1000-5000范圍),數(shù)據(jù)庫就會報錯了!錯誤是"toomanycourses",原因是每生成一個PreparedStatement實例,就會相應(yīng)的生成一個course。假設(shè)batchsize是5000,要刪除10個表的數(shù)據(jù),那么產(chǎn)生的course的數(shù)目就是5000*10=50000,這對數(shù)據(jù)庫來說是不能接受
的,所以就會報錯。
如果按照代碼2的的方式寫程序肯定是沒有問題的,只會生成10個PreparedStatement實例,相應(yīng)的也只會生成10個course,這樣就真正的享受到了batch處理帶來的好處。但是,作為一名“挑剔”的程序員,我們怎么能容忍這樣的寫法呢?明明一個循環(huán)就可以搞定,現(xiàn)在要分成10個循環(huán)來做,非但效率上存在問題,大量重復(fù)的代碼也讓我們的程序顯得很沒“水準(zhǔn)”。
既然第一種方式不能享受batch處理帶來的好處,并且還會出錯,第二種方式代碼又非常的丑陋,那么我們就得想個辦法來解決這個問題了。請記住:解決問題的過程就是一種享受☻。
修改底層代碼,支持多表batch處理.
既然出問題的地方找到了,那么解決它就很容易了。什么,你說還不知道問題出在哪?MyGod!Killme,pleale☻!
在這里分享一下我的思路,把每次傳近來的sql作為key、把生成的PreparedStatement實例作為value放在一個Map里以后每次傳來sql時先判斷在Map里有沒有這個key,如果有就直接拿到它的value作為PreparedStatement實例,如果沒有就新生成一個PreparedStatement實例并把它放到Map里。這樣有幾個sql就有幾個PreparedStatement實例,和寫多個循環(huán)效果是一樣的。但寫一個循環(huán)會更爽!
【編輯推薦】