码迷,mamicode.com
首页 > 数据库 > 详细

.net导入Oracle数据优化小记

时间:2015-11-17 12:38:35      阅读:237      评论:0      收藏:0      [点我收藏+]

标签:

.net导入Oracle数据优化小记

         工作中遇到一个项目需要每次部署时导入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类批量导入能够非常快速的将大量数据导入数据库,调节批尺大小可以达到更好的效果。

.net导入Oracle数据优化小记

标签:

原文地址:http://www.cnblogs.com/shelenty/p/4970912.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!