标签:
工作中遇到一个项目需要每次部署时导入Oracle数据库约4万条数据,原计划使用dmp格式导入,但是这种方式需要依赖数据库的imp.exe文件,环境影响度比较大,于是决定使用Excel进行导入。最初使用Excel导入时每次平均耗时7分钟,不符合项目要求,经过优化后导入时间缩短到30秒左右。以下为本次优化研究的优化小记,希望在以后遇到此类问题时有所帮助。欢迎各位大神评论指教!
数据库版本为Oracle11g,导入数据涉及数据库中18张表,约4万条数据。数据预先保存为Excel文档,一个sheet为一张表数据,每个sheet第一行为表字段名称,第二行为表字段类型,用于导入时进行数据格式转换。
1、NOPI,使用 NPOI 可以在没有安装 Office 或者相应环境的机器上对 Excel文档进行读写。本文的研究主要用到了Excel文档的读取,代码如下:
using (FileStream fs = File.OpenRead(@"d:/myMergeCell.xls")) //打开myxls.xls文件 { HSSFWorkbook wk = new HSSFWorkbook(fs); //把xls文件中的数据写入wk中 for (int i = 0; i < wk.NumberOfSheets; i++) //NumberOfSheets是myxls.xls中总共的表数 { ISheet sheet = wk.GetSheetAt(i); //读取当前表数据 IRow rowName = sheet.GetRow(0);//表字段名 IRow rowType = sheet.GetRow(1);//表字段类型 for (int j = 2; j <= sheet.LastRowNum; j++) //前两行为字段行,从第3行开始为数据行 { IRow row = sheet.GetRow(j); //读取当前行数据 if (row != null) { for (int k = 0; k < row.LastCellNum; k++) //LastCellNum 是当前行的总列数 { ICell cell = row.GetCell(k); //读取当前单元格数据 if (cell != null) { //对单元格进行操作...... //cell.DateCellValue获取日期格式的值 //cell.NumericCellValue获取数值格式的值 //cell.StringCellValue获取字符串格式的值 } } } } } }
2、Oracle.DataAccess,Oracle公司提供的驱动,相对.net中的System.Data. OracleClient来说,能够为Oracle数据库提供更多更好的支持,其中包含支持批量导入的OracleBulkCopy类,能够快速的向Oracle数据库中导入大量的数据。
OracleBulkCopy类批量导入的代码如下:
using (var conn = new OracleConnection(connStr)) { conn.Open();//先打开数据库连接 using (OracleBulkCopy bcp = new OracleBulkCopy(conn)) { bcp.BatchSize = 400;//这是批尺寸可以调整 bcp.BulkCopyTimeout = 1000; //设置超时 bcp.DestinationTableName = table;//要导入的数据库表名 bcp.ColumnMappings.Clear(); for (int k = 0; k < rowName.LastCellNum; k++) {//设置表字段映射,不设置的话可能会导致导入的数据列错乱 bcp.ColumnMappings.Add(rowName.GetCell(k).StringCellValue, rowName.GetCell(k).StringCellValue); } bcp.WriteToServer(dataTable);//将表数据转化为DataTable再进行批量导入 } }
1、逐条导入。首先遍历Excel中的sheet来获取每个表的数据,并根据sheetName和sheet的前两行数据拼接插入语句,注意在此之前需检查表是否存在,并先删除表中全部数据再进行导入。代码如下:
string table = sheet.SheetName;//表名 command.CommandText = "select * from all_tables where table_name=:table_name"; command.Parameters.Clear(); command.Parameters.Add(":table_name", table); var reader = command.ExecuteReader(); if (!reader.HasRows) { reader.Close(); System.Console.WriteLine(table+"表不存在" ); continue; } //删除表数据 command.CommandText = "delete from " + table; command.Parameters.Clear(); command.ExecuteNonQuery(); string field = "";//字段名 string fieldValue = "";//字段值 for (int k = 0; k < rowName.LastCellNum; k++) { field += rowName.GetCell(k).StringCellValue + ",";//拼接字段名 fieldValue += ":" + rowName.GetCell(k).StringCellValue + ",";//拼接绑定字段值 } command.CommandText = "insert into " + table + "(" + field.TrimEnd(‘,‘) + ") values(" + fieldValue.TrimEnd(‘,‘) + ")";
之后遍历数据行,根据字段类型获取不同类型的单元格数据进行绑定,代码如下:
if (cell != null) { if (rowType.GetCell(k).StringCellValue == "DATE") command.Parameters.Add(":" + rowName.GetCell(k).StringCellValue, cell.DateCellValue); else if (rowType.GetCell(k).StringCellValue == "NUMBER") command.Parameters.Add(":" + rowName.GetCell(k).StringCellValue, cell.NumericCellValue); else command.Parameters.Add(":" + rowName.GetCell(k).StringCellValue, cell.StringCellValue); } else command.Parameters.Add(":" + rowName.GetCell(k).StringCellValue, DBNull.Value);
最终执行sql语句并记录影响行数,代码如下:
try { sumCount+=command.ExecuteNonQuery(); } catch (Exception ex) { Console.WriteLine(ex.Message); }
程序运行多次记录导入所有数据所需时间,平均耗时7min ,而如果让网页端等待后台数据导入,等待时间7min显然是不合理的,所以需要对数据导入方法进行优化。
2、多线程逐条导入。每一张表创建一个线程,用第1点的方法进行导入,代码如下:
for (int i = 0; i < wk.NumberOfSheets; i++) //NumberOfSheets是myxls.xls中总共的表数 { var t = new Thread(new ParameterizedThreadStart(impTheard));// impTheard方法中执行第1点的步骤 t.Start(i); }
运行多次平均耗时缩短了1分钟左右,导入时间依然太长。由于不同的表数据量相差较大,数据量最大的一张表数据有2万多条,所以又将数据量较大的表拆分为多个线程进行导入,通过控制调整线程导入数据量来控制线程数量,因为线程运行需传入较多参数,所以创建impTable类来保存参数,代码如下:
int c = 2;//sheet行号 int yczxl = 4000;//每个线程导入行数 for (; c <= sheet.LastRowNum; c += yczxl+1) { var t = new Thread(new ThreadStart(new impTable(table, c, ((c + yczxl) > sheet.LastRowNum ? sheet.LastRowNum : (c + yczxl)), field, fieldValue, sheet, rowName, rowType).imp)); t.Start(); }
多次运行程序导入数据平均耗时6min,和未拆分前相差不大,所以还需进一步优化。
3、分别使用Oracle.DataAccess和System.Data.OracleClient进行导入,Oracle.DataAccess中的OracleCommand在进行变量绑定时可以使用command.Parameters.Add(string name,object val)方法,但是在4.0版本的System.Data.OracleClient中Parameters.Add(string name,object val) 方法已被否决,需替换为Parameters.AddWithValue(string name,object val)。运行程序对比发现两者导入耗时并没有明显变化。
4、使用OracleBulkCopy类批量导入,导入时需先将Excel文档中的sheet转为DataTable对象,注意必须设置DataTable字段类型,否则导入时会因数据类型不匹配而出现很多错误。OracleBulkCopy类批量导入的代码上一节已贴,这里就不再贴出来了。Sheet转DataTable代码如下:
DataTable dt = new DataTable(); //第一行是字段 IRow headRow = sheet.GetRow(0); IRow rowType = sheet.GetRow(1);//表字段类型
//设置datatable字段 for (int i = headRow.FirstCellNum, len = headRow.LastCellNum; i < len; i++) { if (rowType.Cells[i].StringCellValue == "DATE")//设置DataTable字段为DATE类型 dt.Columns.Add(headRow.Cells[i].StringCellValue, typeof(DateTime)); else if (rowType.Cells[i].StringCellValue == "NUMBER")//设置DataTable字段为NUMBER类型 dt.Columns.Add(headRow.Cells[i].StringCellValue, typeof(Int32)); else dt.Columns.Add(headRow.Cells[i].StringCellValue); } //遍历数据行 for (int i = 2, len = sheet.LastRowNum + 1; i < len; i++) { IRow tempRow = sheet.GetRow(i); DataRow dataRow = dt.NewRow(); //遍历一行的每一个单元格 for (int r = 0, j = tempRow.FirstCellNum, len2 = tempRow.LastCellNum; j < len2; j++, r++) { ICell cell = tempRow.GetCell(j); if (cell != null) { if (rowType.GetCell(j).StringCellValue == "DATE") dataRow[r] = cell.DateCellValue; else if (rowType.GetCell(j).StringCellValue == "NUMBER") dataRow[r] = cell.NumericCellValue; else dataRow[r] = cell.StringCellValue; } else dataRow[r] = DBNull.Value; } dt.Rows.Add(dataRow); } return dt;
OracleBulkCopy类批量导入无法得知导入受影响行数,所以在导入完成后,需再次访问数据库查询表中的数据总数作为数据导入受影响行数。
使用Stopwatch计时,代码如下:
Stopwatch MyStopWatch = new Stopwatch(); MyStopWatch.Start(); //...... MyStopWatch.Stop(); decimal t = MyStopWatch.ElapsedTicks; System.Console.WriteLine(t / 10000000);
调节批尺为不同大小,运行多次耗时20s~70s,已达到网页端可接受的范围。当批尺为400时,导入所需时间为32秒。
5、分别使用release和debug方式进行编译运行。Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。但运行多次对比发现两者导入耗时相差不大。
多线程可以加快导入速度,但是也要注意线程数的控制。
使用OracleBulkCopy类批量导入能够非常快速的将大量数据导入数据库,调节批尺大小可以达到更好的效果。
标签:
原文地址:http://www.cnblogs.com/shelenty/p/4970912.html