zoukankan      html  css  js  c++  java
  • 老系统优化同步导入10w+Excel数据 秒级

    1. 背景:老系统asp.net 2.0项目使用客户反应,某个业务每个月导入数据操作很慢,大致需要15-30分钟才会导入完毕;
    2. 分析:导入慢的原因:

     .数据量过大,且采用的是同步,单个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     }
    NPOI

                             去掉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_AssetBalanceQuiry_Insert]  
     @MainID int ,  
     @CloseDate datetime,  
     @ResultCount int=0 output  
    AS  
    BEGIN  
     INSERT INTO T_AssetBalanceQuiry   
     (  
      CloseDate,CompanyCode,AssetsCode,AssetsDescribtion,  
      AssetsNumber,CapitalizationDate,AssetsOldValue,AssetsDepreciation,  
      AssetsNetWorth,ServiceLife,AssetsType,Main_ID,AssetsCode_sub,Status  
     )   
     select @CloseDate,公司代码,CONVERT(decimal(18, 0), 资产编号),资产描述,  
     管理序列号,资本化日期,资产原值,资产累计折旧,  
     资产净值, datediff(mm,资本化日期,getdate()),AS_PrimaryNo,@MainID,资产次级编号,null   
     from  dbo.Temp_gdzc  
     inner join T_Asset on 资产管理序列号 = AS_MSNO;  
     select @ResultCount = count(1) from T_AssetBalanceQuiry where Main_ID = @MainID;  
     update T_AssetBalanceMain SET AssetsCount = @ResultCount where ID = @MainID;  
    END  
      
      
      
    批量插入存储过程

                          最后批量修改再使用 存储过程 执行关联修改;

    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秒;

                        

  • 相关阅读:
    day46 mysql进阶
    解决:ping github.com遇到“请求超时”
    修改hosts文件
    Python正课135 —— 基础扩展1
    Python正课136 —— 基础扩展2
    05 树莓派安装Python3.6
    一次可以面向百度的笔试
    作业23
    获取类名和方法名
    面向对象三大特性之继承
  • 原文地址:https://www.cnblogs.com/yanghucheng/p/10272443.html
Copyright © 2011-2022 走看看