為什么要做batch處理
Oracle回滾段
在JDBC中如何做batch處理
iBatis框架對batch處理的支持
iBatis框架做batch處理的問題
修改底層代碼,支持多表batch處理
為什么要做batch處理????
????這個(gè)問題我就不解釋了,因?yàn)槲蚁肽銈兛隙鼙?br />
我解釋的更好!如果你真的不知道,那就到Google上去搜
索一下吧?
Oracle回滾段????這個(gè)問題偶也不很明白,只是大概有個(gè)了解,如
果你是這方面的專家,或者對這方面有比較深的理解,
別忘了跟偶分享哦?
在JDBC中如何做batch處理????JDBC提供了數(shù)據(jù)庫batch處理的能力,在數(shù)據(jù)大批量操作(新增、刪除等)的情況下可以大幅度提升系統(tǒng)的性能。我以前接觸的一個(gè)項(xiàng)目,在沒有采用batch處理時(shí),刪除5萬條數(shù)據(jù)大概要半個(gè)小時(shí)左右,后來對系統(tǒng)進(jìn)行改造,采用了batch處理的方式,刪除5萬條數(shù)據(jù)基本上不會(huì)超過1分鐘。看一段JDBC代碼:
- //?關(guān)閉自動(dòng)執(zhí)行
- 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)");
- //?提交一批要執(zhí)行的更新命令
- int[]?updateCounts?=?stmt.executeBatch();
????本例中禁用了自動(dòng)執(zhí)行模式,從而在調(diào)用?Statement.executeBatch()?時(shí)可以防止?JDBC?執(zhí)行事務(wù)處理。禁用自動(dòng)執(zhí)行使得應(yīng)用程序能夠在發(fā)生錯(cuò)誤及批處理中的某些命令不能執(zhí)行時(shí)決定是否執(zhí)行事務(wù)處理。因此,當(dāng)進(jìn)行批處理更新時(shí),通常應(yīng)該關(guān)閉自動(dòng)執(zhí)行。
????在JDBC?2.0?中,Statement?對象能夠記住可以一起提交執(zhí)行的命令列表。創(chuàng)建語句時(shí),與它關(guān)聯(lián)的命令列表為空。Statement.addBatch()?方法為調(diào)用語句的命令列表添加一個(gè)元素。如果批處理中包含有試圖返回結(jié)果集的命令,則當(dāng)調(diào)用?Statement.?executeBatch()?時(shí),將拋出?SQLException。只有?DDL?和?DML?命令(它們只返回簡單的更新計(jì)數(shù))才能作為批處理的一部分來執(zhí)行。如果應(yīng)用程序決定不提交已經(jīng)為某語句構(gòu)
造的命令批處理,則可以調(diào)用方法?Statement.clearBatch()(以上沒有顯示)來重新設(shè)置批處理。
????Statement.executeBatch()?方法將把命令批處理提交給基本?DBMS?來執(zhí)行。命令的執(zhí)行將依照在批處理中的添加順序來進(jìn)行。ExecuteBatch()?為執(zhí)行的命令返回更新計(jì)數(shù)數(shù)組。數(shù)組中對應(yīng)于批處理中的每個(gè)命令都包含了一項(xiàng),而數(shù)組中各元素依據(jù)命令的執(zhí)行順序(這還是和命令的最初添加順序相同)來排序。調(diào)用executeBatch()?將關(guān)閉發(fā)出調(diào)用的?Statement?對象的當(dāng)前結(jié)果集(如果有一個(gè)結(jié)果集是打開的)。executeBatch()?返回后,將重新將語句的內(nèi)部批處理命令列表設(shè)置為空。
????如果批處理中的某個(gè)命令無法正確執(zhí)行,則?ExecuteBatch()?將拋出BatchUpdateException。可以調(diào)用BatchUpdateException.getUpdateCounts()?方法來為批處理中成功執(zhí)行的命令返回更新計(jì)數(shù)的整型數(shù)組。因?yàn)楫?dāng)有第一個(gè)命令返回錯(cuò)誤時(shí),Statement.executeBatch()?就中止,而且這些命令是依據(jù)它們在批處理中的添加順序而執(zhí)行的。所以如果?BatchUpdateException.getUpdateCounts()?所返回的數(shù)組包含?N?個(gè)元素,這就意味著在調(diào)用?executeBatch()?時(shí)批處理中的前?N?個(gè)命令被成功執(zhí)行。用PreparedStatement?可以象下面這樣寫代碼:
- //?關(guān)閉自動(dòng)執(zhí)行
- con.setAutoCommit(false);
- PreparedStatement?stmt?=?con.prepareStatement("INSERT?INTO?employees?VALUES?(?,??)");
- stmt.setInt(1,?2000);
- stmt.setString(2,?"Kelly?Kaufmann");
- stmt.addBatch();
- ???
- //?提交要執(zhí)行的批處理
- int[]?updateCounts?=?stmt.executeBatch();
iBatis框架對batch處理的支持????iBatis框架對batch處理提供了很好的支持,底層的實(shí)現(xiàn)方式就是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處理的問題????在一個(gè)batch中只能對一個(gè)表進(jìn)行操作,例如插入或刪除。當(dāng)有多個(gè)表需要處理時(shí),只能放在多個(gè)batch中進(jìn)行處理。看下面的一段代碼:
- ????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<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("STRGHousekeepTask?execute?end...");
- ????????}
- ????}
????????????????????????????????????????????代碼1
????這段代碼的目的就是要?jiǎng)h除數(shù)據(jù)庫中3個(gè)表的數(shù)據(jù),sqlWrapper是iBatis的SqlMapClient的一個(gè)包裝器,主要是封狀對事物的控制。當(dāng)批次(既to-from的值)很小的時(shí)候,這樣寫是沒有問題的。盡管這段代碼的本意是要享受batch處理帶來的好處,但是事實(shí)上這段代碼并不會(huì)真正達(dá)到預(yù)期的效果,至于原因,我們一會(huì)在進(jìn)行分析?。我們先來看下面一段代碼:
- ????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<to;i++){
- ????????????sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
- ????????}
- ????????for(int?i=from;i<to;i++){
- ????????????sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
- ????????}
- ????????for(int?i=from;i<to;i++){
- ????????????sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
- ????????}
- ????????sqlWrapper.execBatch();
- ????????if(log.isDebugEnabled()){
- ????????????log.debug("STRGHousekeepTask?execute?end...");
- ????????}
- ????}
????????????????????????????????????????????代碼2
????正如你所看到的,和代碼1相比它只是做了3次循環(huán),每個(gè)循環(huán)執(zhí)行一個(gè)表的操作。雖然麻煩,但是卻真正的享受到了batch處理的好處!下面是時(shí)候解釋一下這兩段代碼幕后的秘密了?。
????在前面的章節(jié)里已經(jīng)解釋了JDBC如何做batch處理,如果還不清楚的話請查看前面的章節(jié)。要解釋這兩段代碼里面的玄機(jī),還得看一段代碼?下面的代碼是從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的源碼,我們只關(guān)心它如何對一條語句進(jìn)行處理。參數(shù)sql是要進(jìn)行batch處理的語句,parameters是sql的參數(shù)列表,如果sql和實(shí)例變量currentSql相等,則從statementList列表里面得到一個(gè)PreparedStatement,然后進(jìn)行batch處理,如果不等就新生成一個(gè)PreparedStatement對象,并把它加到statementList列表里面,并把當(dāng)前sql的值附給currentSql,下次傳遞來sql的時(shí)候就會(huì)和這個(gè)新的currentSql比較。這就是為什么在一個(gè)循環(huán)里面只對一個(gè)表進(jìn)行處理的原因了。如果在一個(gè)循環(huán)里面對多個(gè)表進(jìn)行處理,每次傳給addBatch方法的sql都是新的,都會(huì)生成一個(gè)新的PreparedStatement,所以也就享受不到batch處理帶來的好處了!????
???按照代碼1的方式執(zhí)行程序,當(dāng)batch?size很小的時(shí)候盡管享受不到batch處理帶來的好處,但是也不至于會(huì)出什么大問題,但是當(dāng)batch?size值很大的時(shí)候(我在程序中試驗(yàn)過1000-5000范圍),數(shù)據(jù)庫就會(huì)報(bào)錯(cuò)了!錯(cuò)誤是"too?many?courses",原因是每生成一個(gè)PreparedStatement實(shí)例,就會(huì)相應(yīng)的生成一個(gè)course。假設(shè)batch?size是5000,要?jiǎng)h除10個(gè)表的數(shù)據(jù),那么產(chǎn)生的course的數(shù)目就是5000*10=50000,這對數(shù)據(jù)庫來說是不能接受
的,所以就會(huì)報(bào)錯(cuò)。
????如果按照代碼2的的方式寫程序肯定是沒有問題的,只會(huì)生成10個(gè)PreparedStatement實(shí)例,相應(yīng)的也只會(huì)生成10個(gè)course,這樣就真正的享受到了batch處理帶來的好處。但是,作為一名“挑剔”的程序員,我們怎么能容忍這樣的寫法呢?明明一個(gè)循環(huán)就可以搞定,現(xiàn)在要分成10個(gè)循環(huán)來做,非但效率上存在問題,大量重復(fù)的代碼也讓我們的程序顯得很沒“水準(zhǔn)”。
????既然第一種方式不能享受batch處理帶來的好處,并且還會(huì)出錯(cuò),第二種方式代碼又非常的丑陋,那么我們就得想個(gè)辦法來解決這個(gè)問題了。請記住:解決問題的過程就是一種享受?。
修改底層代碼,支持多表batch處理????既然出問題的地方找到了,那么解決它就很容易了。什么,你說還不知道問題出在哪?My?God!?Kill?me?,pleale?!?
????在這里分享一下我的思路,把每次傳近來的sql作為key、把生成的PreparedStatement實(shí)例作為value放在一個(gè)Map里以后每次傳來sql時(shí)先判斷在Map里有沒有這個(gè)key,如果有就直接拿到它的value作為PreparedStatement實(shí)例,如果沒有就新生成一個(gè)PreparedStatement實(shí)例并把它放到Map里。這樣有幾個(gè)sql就有幾個(gè)PreparedStatement
實(shí)例,和寫多個(gè)循環(huán)效果是一樣的。但寫一個(gè)循環(huán)會(huì)更爽?!?
后記:
??????在一般的項(xiàng)目中做batch處理的地方似乎都是先取得一個(gè)條件列表list,然后直接根據(jù)這個(gè)list的大小作為batch size做一個(gè)循環(huán)。如果你在這個(gè)循環(huán)里同時(shí)進(jìn)行多個(gè)表的CUD操作,那么這里就有一個(gè)安全隱患存在。當(dāng)你的list不太大的時(shí)候,你怎么測試程序它都不會(huì)出問題,盡管可能會(huì)有執(zhí)行效率上的問題,但是當(dāng)突然有一天這個(gè)list變的很大的時(shí)候,你的程序可能就突然“罷工”了。?
對于這個(gè)問題,我在上面的文檔里提出了改進(jìn)batch處理的方法,另外還有需要注意的一個(gè)問題就是這個(gè)list的大小的問題。如果這個(gè)list的size有可能會(huì)很大,那么我們應(yīng)該考慮根據(jù)這個(gè)list的大小“分批”執(zhí)行。因?yàn)椴⒉皇莃atch size越大效果就越好,如果batch的size很大的話很可能產(chǎn)生效率和性能上的問題。至于這個(gè)batch size的值為多少比較合適就沒有一個(gè)固定的說法,這個(gè)可能要取決于你所使用的服務(wù)器和數(shù)據(jù)庫的性能了,另外不同廠商的JDBC驅(qū)動(dòng)也會(huì)有不同的性能表現(xiàn),你可以向DBA咨詢相關(guān)的問題。?
我們應(yīng)該盡可能把問題扼殺在搖籃之中。除了改進(jìn)iBatis的batch處理機(jī)智外,還應(yīng)該適當(dāng)?shù)囊?guī)劃batch size大小,以避免發(fā)生問題,提高執(zhí)行效率。?
上述是我個(gè)人的觀點(diǎn),有些地方可能不是很準(zhǔn)確。如果你的程序中存在類似的問題,可以適當(dāng)參考一下我的意見,最好還是向?qū)I(yè)人士咨詢。