数据导入功能在报表项目中是经常可见的,因为它是报表数据展示的基础,但对于大量数据的导入,真正从性能、效率等方面兼顾的方案却很少有。近在南航广西数据服务平台的项目开发中,我需要设计一个能快速将40多万条数据导入Oracle数据库的方案,为了实现导入的高效,我通过在网上收集资料以及动手实践测试,得出了一些分析总结与大家分享探讨。

  谈到数据导入功能的实现,无可厚非应该包括两个过程,首先是数据文件的上传,其次是数据的导入。

  一、数据文件上传

  文件上传本应该是与导入无关,但它处于数据导入功能的一个环节,其效率也显得有些重要。对于传统项目,文件上传通常采用Struts等框架实现的文件上传机制以及一些开源的文件上传组件,比如SmartUpload等,通过Html中类型为File的Input标签将数据文件获取,通过流的形式发送服务端,后由服务端获取流并写入文件,如此实现了文件从客户端到服务器的上传过程,这些方式我们都可以将其统一称为Web文件上传。

  Web文件上传是通过Http协议实现的文件流传输,其受限于Web数据传输的瓶颈,基于Http协议传输的数据在传输的速度上有一定影响,首先可能出现数据文件请求超时需要数据重传;其次在每秒能传输的字节数在Web方式里受到限制;后,由于Web在传输数据文件之前需要将数据文件转化为数据流,通过Web的File标签实现的文件流化效率很低,通过对比试验发现,同样对一个50M的文件流化,用Web的File标签流化的时间要大于采用IO方式流化所消耗的时间,也是说,对于大数据文件,要将其通过Web标签流化再传输,可能会有很长一段时间是处于发送请求状态,甚至会因为这个过程的时间持续较长而造成网络请求超时。这可能是优酷等类似的网站上传视频不采用Web方式的一个原因。

  除了Web方式,可以考虑使用Applet,作为一个客户端小程序嵌入到网页中,以IO的方式读取本地数据文件,然后通过Socket将文件流发送到服务端。这种方式从效率上比Web方式有明显的改进,首先是通过IO将文件转化为文件流的效率提升,其次数据通过Socket方传输式,是一种基于TCP协议的网络传输,去除了Web下Http协议对文件传输的限制,通过TCP协议直接从网络的传输层进行数据通信,传输速度上必然会更快。综上,采用Applet加Socket实现网络文件上传性能优于Web方式。

  然而,Applet实现的网络文件上传又并非优,原因在于Applet在运行时受到沙箱的限制。出于对客户机和服务器的保护,web中的applet程序只能运行在限制的沙箱中,其受到很多安全策略的限制,在applet中不能直接访问客户端本地文件系统,除非使用applet授权,采用数字签名的方式使applet能确认该客户端系统是可信的。这样一来,要使用该功能的客户机系统都需要安装安全证书,在项目的部署上显得十分繁琐。

  以上方式都各有弊端,终,通过和用户协商,我们决定借助外部的FTP工具,使用开源的文件传输工具让用户将数据文件直接传到服务器指定目录下,在网站系统上只执行数据文件列表加载。另外,目前也有在web上嵌入FTP功能的插件,其通过activeObject的形式嵌入web,实现类似ftp的文件上传功能,打算抽空继续研究。

  二、数据导入

  大数据导入Oracle数据库是功能实现的重点。

  大数据导入的特点在于数据记录多,数据插入需要批量执行,分批量commit,以此来减少对数据库的交互访问次数,减轻数据库压力。在此,我主要探索了两种导入方式,一种是基于多线程的并发插入,另一种是利用SqlLoader实现的大数据导入。

  (一)基于多线程的并发插入

  该方案是在批量到插入的基础之上采用多线程来执行的方式实现的。

  该方案第一步是加载所需的数据文件到内存,生成一个Sql的数组。对于导入的数据文件,一般是EXCEL格式的,对于此种类型数据文件,我们需要借助POI来实现EXCEL文件的加载,并通过POI读取EXCEL中行数据来生成数据插入Sql,文件代码如下:

private XSSFSheet get07SheetForExcel(File file, String sheetName) {
if (file != null) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
// 创建对Excel工作簿文件的引用
XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
// 创建对工作表的引用?1?7?1?7
XSSFSheet sheet = workbook.getSheetAt(0);
// 也可用getSheetAt(int index)按索引引用,
// 在Excel文档中,第一张工作表的缺省索引是0$1?7
// 其语句为:HSSFSheet sheet = workbook.getSheetAt(0);
return sheet;
} catch (Exception e) {
}
}
return null;
}
                                 ……
for (int i = begin; null != sheet
&& i < sheet.getLastRowNum() - sheet.getFirstRowNum()
+ 1; i++) {
HSSFRow row = sheet.getRow(i);
System.out.println(i);
String[] valuesPerRow = getHSSFRowValues(row);
if (null == valuesPerRow || valuesPerRow.length < 1) {
continue;
}
rows.add(valuesPerRow);
if (rows.size() == CommonParas.PER_IMPORT_SIZE) {
v.importToDB(rows);
rows.clear();
}
}
v.importToDB(rows);