驚呆,Oracle的這個坑竟然讓我踩上了
今天,系統(tǒng)中的一個業(yè)務(wù)處理莫名地執(zhí)行了6個小時都沒有結(jié)束,正常處理也就是3分鐘左右,對原因進行定位,發(fā)現(xiàn)是在Oracle客戶端上同步執(zhí)行一個命令沒有響應(yīng)。今天來分享一下這個問題,讓更多的人避開這個坑。
1 業(yè)務(wù)場景
我們要把一個csv文件(文件名biz.csv)中的數(shù)據(jù)讀取到Oracle數(shù)據(jù)庫表(表名t_biz,t_biz)中,數(shù)據(jù)庫表t_biz表結(jié)構(gòu)如下:
biz.csv文件內(nèi)容如下:
- id,a,b,c
- 1,a1,b1,c1
- 2,a2,b2,c2
- 3,a3,b3,c3
把biz.csv文件的內(nèi)容讀入到表t_biz,為了提高效率,這里使用了sqlldr 命令,命令如下:
- sqlldr test/test123@biz control=/home/jinjunzhu/biz/T_BIZ.ctl log=/home/jinjunzhu/biz/T_BIZ.log bad=/home/jinjunzhu/biz/T_BIZ.bad
解釋一下這個命令,test/test123 是要訪問的數(shù)據(jù)庫實例的用戶名/密碼,biz 是數(shù)據(jù)庫實例名稱。T_BIZ.ctl是控制文件,內(nèi)容如下:
- options(skip=1,rows=10000,errors=0,parallel=true,bindsize=1048576,readsize=1048576)
- load data
- infile '/home/jinjunzhu/biz/biz.csv'
- fields terminated by ','
- truncate into table day_data
- trailing nullcols
- (id,a,b,c)
業(yè)務(wù)代碼中調(diào)用這個命令,代碼如下:
- private int execute(String cmd) throws Exception{
- Process process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
- process.waitFor(10, TimeUnit.SECONDS);
- Integer status = process.waitFor();
- return status == null ? -1 : status;
- }
2 問題現(xiàn)場
程序執(zhí)行到上面第4行的時候,程序hang住了,一直沒有返回。這個代碼之前從來沒有出過問題,最近也沒有上過線,今天唯一的不同就是文件數(shù)據(jù)量越來越大,今天比昨天大了幾萬行。
數(shù)據(jù)庫情況:
- 看不到有sqlldr命令等待的情況
- CPU正常
- 手工執(zhí)行上面命令可以成功,但是打印的日志非常多,如下圖:
3 原因分析
網(wǎng)上搜這個問題竟然很多,原因有下面三類:
3.1 Oracle版本低
Oracle版本低,建議升級到10.2.0.2或以上,這個方案忽略,因為我們的數(shù)據(jù)庫版本是Oracle 11.2.0.4.0。
3.2 數(shù)據(jù)落庫情況
本以為sqlldr命令執(zhí)行失敗了,但是文件數(shù)據(jù)已經(jīng)全部落到t_biz表。這說明命令執(zhí)行成功了,只是Oracle沒有給應(yīng)用返回結(jié)果。難道是Oracle數(shù)據(jù)庫hang住了?但是上面的問題現(xiàn)場已經(jīng)確認(rèn),Oracle并沒有hang在sqlldr這個命令上。
3.3 最終答案
看了好多博客,最后發(fā)現(xiàn)竟然不是Oracle的原因。根本原因是使用java執(zhí)行shell時,如果不讀取標(biāo)準(zhǔn)輸出,這個輸出就會輸出到缺省緩沖區(qū),如果輸出流太大,必將打滿緩沖區(qū),導(dǎo)致程序hang住。
從上面問題現(xiàn)場的手工執(zhí)行中可以看到,因為加載的數(shù)據(jù)量很大大,結(jié)果輸出也流非常大,這很容易超出缺省緩沖區(qū)大小。
4 解決方案
問題已經(jīng)很明確了,解決方案也就有了,處理sqlldr的輸出就可以解決。解決方法有下面三種。
4.1 增加參數(shù)
在sqlldr命令后面增加一個參數(shù),silent=(ALL),最后命令如下:
- sqlldr test/test123@biz control=/home/jinjunzhu/biz/T_BIZ.ctl log=/home/jinjunzhu/biz/T_BIZ.log bad=/home/jinjunzhu/biz/T_BIZ.bad silent=(ALL)
4.2 程序讀取標(biāo)準(zhǔn)輸出
程序中讀取sqlldr命令返回的輸出,修改后的代碼如下:
- private int execute(String cmd) throws Exception{
- Process process = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
- process.waitFor(10, TimeUnit.SECONDS);
- Integer status;
- BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
- String line;
- while ((line = br.readLine()) != null) {
- System.out.println(line);
- }
- return (status = process.waitFor()) == null ? -1 : status;
- }
4.3 文件接收標(biāo)準(zhǔn)輸出
可以在sqlldr命令中增加文件參數(shù)來接收命令的標(biāo)準(zhǔn)輸出,最后我采用了這種方式,命令如下:
- sqlldr test/test123@biz control=/home/jinjunzhu/biz/T_BIZ.ctl log=/home/jinjunzhu/biz/T_BIZ.log bad=/home/jinjunzhu/biz/T_BIZ.bad 1>/home/jinjunzhu/biz/std.log 2>/home/jinjunzhu/biz/err.log
5 總結(jié)
這個問題剛出現(xiàn)的時候,一直以為是Oracle的問題,但是后來研究發(fā)現(xiàn),這個鍋真的不能讓Oracle來背。關(guān)于sqlldr命令的詳細參數(shù)介紹,已經(jīng)比較成熟,大家可以自行網(wǎng)絡(luò)查找。
【編輯推薦】