今天系统里一个业务流程莫名其妙的执行了6个小时没有结束,正常流程大概3分钟。定位原因,发现是ExecutingacommandsynchronouslyontheOracleclientdoesnotrespond。今天把这个问题分享出来,让更多的人避开这个坑。1业务场景我们需要将一个csv文件(文件名biz.csv)中的数据读取到Oracle数据库表(表名t_biz,t_biz)中。数据库表t_biz表结构如下:biz.csv文件内容如下:id,a,b,c1,a1,b1,c12,a2,b2,c23,a3,b3,c3读取内容将biz.csv文件导入到表t_biz中,为了提高效率,这里使用了sqlldr命令,命令如下:sqlldrtest/test123@bizcontrol=/home/jinjunzhu/biz/T_BIZ.ctllog=/home/jinjunzhu/biz/T_BIZ.logbad=/home/jinjunzhu/biz/T_BIZ.bad解释一下这个命令,test/test123是要访问的数据库实例用户名/密码,biz是数据库实例名。T_BIZ.ctl为控制文件,内容如下:options(skip=1,rows=10000,errors=0,parallel=true,bindsize=1048576,readsize=1048576)loaddatainfile'/home/jinjunzhu/biz/biz.csv'fieldsterminatedby','truncateintotableday_datatrailingnullcols(id,a,b,c)这条命令在业务代码中被调用,代码如下:privateintexecute(Stringcmd)throwsException{Processprocess=Runtime.getRuntime().exec(newString[]{"/bin/bash","-c",cmd});process.waitFor(10,TimeUnit.SECONDS);Integerstatus=process.waitFor();returnstatus==null?-1:status;}2问题现场程序执行到上面第4行时,程序挂了,再也没有返回。这段代码之前一直没出过问题,最近也没上线。今天唯一不同的是,今天的文件数据量越来越大,比昨天大了几万行。数据库情况:看不到sqlldr命令等待。CPU可以正常手动执行上面的命令,但是会打印很多日志,如下图:3原因分析网上有很多问题,有3个原因:3.1Oracle版本低Oracle版本是low,建议升级到10.2.0.2以上,这个解决方法忽略,因为我们的数据库版本是Oracle11.2.0.4.0。3.2数据记录情况我以为sqlldr命令执行失败,但是所有的文件数据已经记录到t_biz表中了。这表示命令已成功执行,但Oracle没有向应用程序返回结果。会不会是Oracle数据库挂了?但是上面的问题已经在现场确认过了,Oracle并没有挂在sqlldr命令上。3.3最终答案看了很多博客,终于发现不是Oracle的原因。根本原因是在使用java执行shell时,如果不读取标准输出,就会将输出输出到默认缓冲区。如果输出流太大,缓冲区将被填满,程序将挂起。从上面问题现场手动执行可以看出,由于加载的数据量非常大,导致的输出流也非常大,很容易超过默认的buffersize。4解决方案问题已经很清楚了,解决方案也有了,可以通过处理sqlldr的输出来解决。有三种解决方法。4.1添加参数在sqlldr命令后添加一个参数,silent=(ALL),最终命令如下:sqlldrtest/test123@bizcontrol=/home/jinjunzhu/biz/T_BIZ.ctllog=/home/jinjunzhu/biz/T_BIZ.logbad=/home/jinjunzhu/biz/T_BIZ.badsilent=(ALL)4.2程序读取标准输出程序中sqlldr命令返回的输出。修改后的代码如下:privateintexecute(Stringcmd)throwsException{Processprocess=Runtime.getRuntime().exec(newString[]{"/bin/bash","-c",cmd});process.waitFor(10,TimeUnit.SECONDS);Integerstatus;BufferedReaderbr=newBufferedReader(newInputStreamReader(process.getInputStream()));Stringline;while((line=br.readLine())!=null){System.out.println(line);}return(status=process.waitFor())==null?-1:status;}4.3文件接收条件输出您可以在sqlldr命令中添加文件参数以接收命令的标准输出。最后,我采用了这个方法。命令如下:sqlldrtest/test123@bizcontrol=/home/jinjunzhu/biz/T_BIZ.ctllog=/home/jinjunzhu/biz/T_BIZ.logbad=/home/jinjunzhu/biz/T_BIZ.bad1>/home/jinjunzhu/biz/std.log2>/home/jinjunzhu/biz/err.log5总结刚开始出现这个问题的时候,我一直以为是Oracle的问题,但是后来研究发现这个锅真的不能怪Oracle。sqlldr命令的详细参数介绍比较成熟,大家可以自行上网搜索。【编辑推荐】鸿蒙官方战略合作共建——鸿蒙技术社区任何Ubuntu用户都应该安装的四大Linux应用变废为宝MySQL的三大JOIN子句使用指南AppleiOS15再次迎来更新,此外到直播文,有五个新发现
