老系统Excel数据导入优化10w数据
Posted yanghucheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了老系统Excel数据导入优化10w数据相关的知识,希望对你有一定的参考价值。
- 背景:老系统asp.net 2.0项目使用客户反应,某个业务每个月导入数据操作很慢,大致需要15-30分钟才会导入完毕;
- 分析:导入慢的原因:
.数据量过大,且采用的是同步,单个excel sheet 13万+数据;
.导入前验证,每行某列 验证,频繁和数据库交互
.使用很老的微软企业库进行批量插入操作,效率低下 ,插入后,又批量进行执行sql修改操作 ;
3 解决方案:
修改excel转table 的方案由OpenXMLHelper 转换变为 NPOI;
1 public class NPOIHelper 2 { 3 /// <summary> 4 /// 将excel导入到datatable 5 /// </summary> 6 /// <param name="filePath">excel路径</param> 7 /// <param name="isColumnName">第一行是否是列名</param> 8 /// <returns>返回datatable</returns> 9 public DataTable ExcelToDataTable(string filePath, bool isColumnName) 10 { 11 DataTable dataTable = null; 12 FileStream fs = null; 13 DataColumn column = null; 14 DataRow dataRow = null; 15 IWorkbook workbook = null; 16 ISheet sheet = null; 17 IRow row = null; 18 ICell cell = null; 19 int startRow = 0; 20 try 21 { 22 using (fs = File.OpenRead(filePath)) 23 { 24 // 2007版本 25 if (filePath.IndexOf(".xlsx") > 0) 26 workbook = new XSSFWorkbook(fs); 27 // 2003版本 28 else if (filePath.IndexOf(".xls") > 0) 29 workbook = new HSSFWorkbook(fs); 30 31 if (workbook != null) 32 { 33 sheet = workbook.GetSheetAt(0);//读取第一个sheet,当然也可以循环读取每个sheet 34 dataTable = new DataTable(); 35 if (sheet != null) 36 { 37 int rowCount = sheet.LastRowNum;//总行数 38 if (rowCount > 0) 39 { 40 IRow firstRow = sheet.GetRow(0);//第一行 41 int cellCount = firstRow.LastCellNum;//列数 42 43 //构建datatable的列 44 if (isColumnName) 45 { 46 startRow = 1;//如果第一行是列名,则从第二行开始读取 47 for (int i = firstRow.FirstCellNum; i < cellCount; ++i) 48 { 49 cell = firstRow.GetCell(i); 50 if (cell != null) 51 { 52 if (cell.StringCellValue != null) 53 { 54 column = new DataColumn(cell.StringCellValue); 55 dataTable.Columns.Add(column); 56 } 57 } 58 } 59 } 60 else 61 { 62 for (int i = firstRow.FirstCellNum; i < cellCount; ++i) 63 { 64 column = new DataColumn("column" + (i + 1)); 65 dataTable.Columns.Add(column); 66 } 67 } 68 69 //填充行 70 for (int i = startRow; i <= rowCount; ++i) 71 { 72 row = sheet.GetRow(i); 73 if (row == null) continue; 74 75 dataRow = dataTable.NewRow(); 76 for (int j = row.FirstCellNum; j < cellCount; ++j) 77 { 78 cell = row.GetCell(j); 79 if (cell == null) 80 { 81 dataRow[j] = ""; 82 } 83 else 84 { 85 //CellType(Unknown = -1,Numeric = 0,String = 1,Formula = 2,Blank = 3,Boolean = 4,Error = 5,) 86 switch (cell.CellType) 87 { 88 case CellType.BLANK: 89 dataRow[j] = ""; 90 break; 91 case CellType.NUMERIC: 92 short format = cell.CellStyle.DataFormat; 93 //对时间格式(2015.12.5、2015/12/5、2015-12-5等)的处理 94 if (format == 14 || format == 31 || format == 57 || format == 58) 95 dataRow[j] = cell.DateCellValue; 96 else 97 dataRow[j] = cell.NumericCellValue; 98 break; 99 case CellType.STRING: 100 dataRow[j] = cell.StringCellValue; 101 break; 102 } 103 } 104 } 105 if (dataRow == null) Logger.Write(string.Format("转换行失败,行数为:{0}", i.ToString())); 106 dataTable.Rows.Add(dataRow); 107 } 108 } 109 else 110 { 111 Logger.Write(string.Format("转换datarow完毕,行数:{0}", rowCount.ToString())); 112 } 113 } 114 } 115 else 116 { 117 Logger.Write("转换workbook 为空"); 118 } 119 } 120 return dataTable; 121 } 122 catch (Exception ex) 123 { 124 Logger.Write(string.Format("转换失败,异常:{0}", ex.ToString())); 125 if (fs != null) 126 { 127 fs.Close(); 128 } 129 return null; 130 } 131 } 132 }
去掉excel的输入验证,由于只是验证数据库是否存在该编码,所以改为由存储过程内连接过滤
批量插入修改为使用 SqlBulkCopy,首先创建一张临时表存储需要插入的excel数据(未过滤)
public int CreateTempTable() { string createSql = @" IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N‘[dbo].[Temp_gdzc]‘) AND type in (N‘U‘)) delete FROM [dbo].[Temp_gdzc] IF Not EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N‘[dbo].[Temp_gdzc]‘) AND type in (N‘U‘)) BEGIN CREATE TABLE [dbo].[Temp_gdzc]( [公司代码] [nvarchar](100) NULL, [资产编号] [nvarchar](100) NULL, [资产次级编号] [nvarchar](100) NULL, [资产描述] [nvarchar](255) NULL, [资产管理序列号] [nvarchar](100) NULL, [资本化日期] [datetime] NULL, [资产原值] [float] NULL, [资产累计折旧] [float] NULL, [资产净值] [float] NULL ) end "; return BusinessRules.Common.SqlHelperBatch.ExecuteNonQuery(createSql, new SqlParameter[] { }); }
然后根据临时表 和 要插入的表的数据内关联过滤无效数据,调用存储过程使用 Insert into select 插入;
1 //调用存储过程插入明细表 2 var parameters = new List<SqlParameter>(); 3 parameters.Add(new SqlParameter("@MainID", SqlDbType.BigInt, 8) { Value = Mainid }); 4 parameters.Add(new SqlParameter("@CloseDate", SqlDbType.DateTime) { Value = closeTemp }); 5 SqlParameter outParameter = new SqlParameter("@ResultCount", SqlDbType.Int, 8); 6 outParameter.Direction = ParameterDirection.Output; 7 parameters.Add(outParameter); 8 9 var result = BusinessRules.Common.SqlHelperBatch.ExecuteNonQuery(tran, CommandType.Text, "exec P_AssetBalanceQuiry_Insert @MainID, @CloseDate,@ResultCount out", parameters.ToArray()); 10 count = Convert.ToInt32(outParameter.Value == DBNull.Value ? 0 : outParameter.Value);
存储过程如下:
最后批量修改再使用 存储过程 执行关联修改;
CREATE PROCEDURE [dbo].[P_T_UpdateA] @MainID int AS BEGIN UPDATE T1 SET A=C from T2 where A1= B1 AND Main_ID = @MainID END
需要注意的地方则是:批量插入时,主表ID需要记录,由于是一次操作,只会有一个主表ID,所以会先插入主表,得到主表ID,再批量插入从表;
最终优化操作时间由10分钟 到5-10秒;
以上是关于老系统Excel数据导入优化10w数据的主要内容,如果未能解决你的问题,请参考以下文章
用php导入10W条+ 级别的csv大文件数据到mysql。导出10W+级别数据到csv文件