為什麼要做batch處理
這個問題我就不解釋了,因為我想你們肯定能比
我解釋的更好!如果你真的不知道,那就到雅虎上去搜
索一下吧;
Oracle回滾段
這個問題偶也不很明白,只是大概有個了解,如
果你是這方面的專家,或者對這方面有比較深的理解,
別忘了跟偶分享哦;
在JDBC中如何做batch處理
JDBC提供了數據庫batch處理的能力,在數據大批量操作(新增、刪除等)的情況下可以大幅度提升系統的性能。我以前接觸的一個項目,在沒有采用batch處理時,刪除5萬條數據大概要半個小時左右,後來對系統進行改造,采用了batch處理的方式,刪除5萬條數據基本上不會超過1分鐘。看一段JDBC代碼:
// 關閉自動執行
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, 260)");
// 提交一批要執行的更新命令
int[] updateCounts = stmt.executeBatch();
本例中禁用了自動執行模式,從而在調用 Statement.executeBatch() 時可以防止 JDBC 執行事務處理。禁用自動執行使得應用程序能夠在發生錯誤及批處理中的某些命令不能執行時決定是否執行事務處理。因此,當進行批處理更新時,通常應該關閉自動執行。
在JDBC 2.0 中,Statement 對象能夠記住可以一起提交執行的命令列表。創建語句時,與它關聯的命令列表為空。Statement.addBatch() 方法為調用語句的命令列表添加一個元素。如果批處理中包含有試圖返回結果集的命令,則當調用 Statement. executeBatch() 時,將拋出 SQLException。只有 DDL 和 DML 命令(它們只返回簡單的更新計數)才能作為批處理的一部分來執行。如果應用程序決定不提交已經為某語句構
造的命令批處理,則可以調用方法 Statement.clearBatch()(以上沒有顯示)來重新設置批處理。
Statement.executeBatch() 方法將把命令批處理提交給基本 DBMS 來執行。命令的執行將依照在批處理中的添加順序來進行。ExecuteBatch() 為執行的命令返回更新計數數組。數組中對應於批處理中的每個命令都包含了一項,而數組中各元素依據命令的執行順序(這還是和命令的最初添加順序相同)來排序。調用executeBatch() 將關閉發出調用的 Statement 對象的當前結果集(如果有一個結果集是打開的)。executeBatch() 返回後,將重新將語句的內部批處理命令列表設置為空。
如果批處理中的某個命令無法正確執行,則 ExecuteBatch() 將拋出BatchUpdateException。可以調用BatchUpdateException.getUpdateCounts() 方法來為批處理中成功執行的命令返回更新計數的整型數組。因為當有第一個命令返回錯誤時,Statement.executeBatch() 就中止,而且這些命令是依據它們在批處理中的添加順序而執行的。所以如果 BatchUpdateException.getUpdateCounts() 所返回的數組包含 N 個元素,這就意味著在調用 executeBatch() 時批處理中的前 N 個命令被成功執行。用PreparedStatement 可以象下面這樣寫代碼:
// 關閉自動執行
con.setAutoCommit(false);
PreparedStatement stmt = con.prepareStatement("INSERT INTO employees VALUES (?, ?)");
stmt.setInt(1, 2000);
stmt.setString(2, "Kelly Kaufmann");
stmt.addBatch();
???
// 提交要執行的批處理
int[] updateCounts = stmt.executeBatch();
iBatis框架對batch處理的支持
iBatis框架對batch處理提供了很好的支持,底層的實現方式就是JDBC。下面看一段示例代碼:
private void execute(SqlMapClient clIEnt){
if(log.isDebugEnabled()){
log.debug("execute start...");
}
clIEnt.startBatch();
for(int i=0;i<2000;i++){
clIEnt.delete("delete from order where id=?",i);
}
clIEnt.executeBatch();
if(log.isDebugEnabled()){
log.debug("execute end...");
}
}
iBatis框架做batch處理的問題
在一個batch中只能對一個表進行操作,例如插入或刪除。當有多個表需要處理時,只能放在多個batch中進行處理。看下面的一段代碼:
private void execute(int from,int to,List list){
if(log.isDebugEnabled()){
log.debug("STRGHousekeepTask execute start...");
}
HKSqlMapWrapper sqlWrapper = HKSqlMapWrapper.newInstance();
sqlWrapper.startBatch();
for(int i=from;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("STRGHousekeepTask execute end...");
}
}
代碼1
這段代碼的目的就是要刪除數據庫中3個表的數據,sqlWrapper是iBatis的SqlMapClIEnt的一個包裝器,主要是封狀對事物的控制。當批次(既to-from的值)很小的時候,這樣寫是沒有問題的。盡管這段代碼的本意是要享受batch處理帶來的好處,但是事實上這段代碼並不會真正達到預期的效果,至於原因,我們一會在進行分析;。我們先來看下面一段代碼:
private void execute(int from,int to,List list){
if(log.isDebugEnabled()){
log.debug("STRGHousekeepTask execute start...");
}
HKSqlMapWrapper sqlWrapper = HKSqlMapWrapper.newInstance();
sqlWrapper.startBatch();
for(int i=from;i
sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
}
for(int i=from;i
sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
}
for(int i=from;i
sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
}
sqlWrapper.execBatch();
if(log.isDebugEnabled()){
log.debug("STRGHousekeepTask execute end...");
}
}
代碼2
正如你所看到的,和代碼1相比它只是做了3次循環,每個循環執行一個表的操作。雖然麻煩,但是卻真正的享受到了batch處理的好處!下面是時候解釋一下這兩段代碼幕後的秘密了;。
在前面的章節裡已經解釋了JDBC如何做batch處理,如果還不清楚的話請查看前面的章節。要解釋這兩段代碼裡面的玄機,還得看一段代碼;下面的代碼是從iBatis源碼中提取的:
public void addBatch(RequestScope request, Connection conn, String sql, Object[] parameters ) {
PreparedStatement ps = null;
if (currentSql != null
&& sql.hashCode() == currentSql.hashCode()
&& sql.length() == currentSql.length()) {
int last = 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的源碼,我們只關心它如何對一條語句進行處理。參數sql是要進行batch處理的語句,parameters是sql的參數列表,如果sql和實例變量currentSql相等,則從statementList列表裡面得到一個PreparedStatement,然後進行batch處理,如果不等就新生成一個PreparedStatement對象,並把它加到statementList列表裡面,並把當前sql的值附給currentSql,下次傳遞來sql的時候就會和這個新的currentSql比較。這就是為什麼在一個循環裡面只對一個表進行處理的原因了。如果在一個循環裡面對多個表進行處理,每次傳給addBatch方法的sql都是新的,都會生成一個新的PreparedStatement,所以也就享受不到batch處理帶來的好處了!
按照代碼1的方式執行程序,當batch size很小的時候盡管享受不到batch處理帶來的好處,但是也不至於會出什麼大問題,但是當batch size值很大的時候(我在程序中試驗過1000-5000范圍),數據庫就會報錯了!錯誤是"too many courses",原因是每生成一個PreparedStatement實例,就會相應的生成一個course。假設batch size是5000,要刪除10個表的數據,那麼產生的course的數目就是5000*10=50000,這對數據庫來說是不能接受
的,所以就會報錯。
如果按照代碼2的的方式寫程序肯定是沒有問題的,只會生成10個PreparedStatement實例,相應的也只會生成10個course,這樣就真正的享受到了batch處理帶來的好處。但是,作為一名“挑剔”的程序員,我們怎麼能容忍這樣的寫法呢?明明一個循環就可以搞定,現在要分成10個循環來做,非但效率上存在問題,大量重復的代碼也讓我們的程序顯得很沒“水准”。
既然第一種方式不能享受batch處理帶來的好處,並且還會出錯,第二種方式代碼又非常的丑陋,那麼我們就得想個辦法來解決這個問題了。請記住:解決問題的過程就是一種享受;。
修改底層代碼,支持多表batch處理
既然出問題的地方找到了,那麼解決它就很容易了。什麼,你說還不知道問題出在哪?My God! Kill me ,pleale;!
在這裡分享一下我的思路,把每次傳近來的sql作為key、把生成的PreparedStatement實例作為value放在一個Map裡以後每次傳來sql時先判斷在Map裡有沒有這個key,如果有就直接拿到它的value作為PreparedStatement實例,如果沒有就新生成一個PreparedStatement實例並把它放到Map裡。這樣有幾個sql就有幾個PreparedStatement
實例,和寫多個循環效果是一樣的。但寫一個循環會更爽;!