【问题标题】:Any way to speed up this excel import?有什么方法可以加快这个excel导入速度?
【发布时间】:2012-08-16 12:31:51
【问题描述】:

我有一个 Excel 文档,它有大约 250000 行,需要永远导入。我对这个导入做了很多变体,但是有一些要求: - 需要验证每个单元格中的数据 - 必须检查数据库中是否存在重复项 - 如果存在重复,则更新条目 - 如果不存在条目,则插入一个新条目

我已经尽可能多地使用并行化,但是我确信必须有某种方法可以让这个导入运行得更快。任何帮助或想法将不胜感激。

请注意,数据库位于 LAN 上,是的,我知道我还没有使用参数化的 sql 命令。

        public string BulkUserInsertAndUpdate()
        {
            DateTime startTime = DateTime.Now;
            try
            {
                ProcessInParallel();
                Debug.WriteLine("Time taken: " + (DateTime.Now - startTime));
            }
            catch (Exception ex)
            {
                return ex.Message;
            }

            return "";
        }


       private IEnumerable<Row> ReadDocument()
        {
            using (SpreadsheetDocument spreadSheetDocument = SpreadsheetDocument.Open(_fileName, false))
            {
                WorkbookPart workbookPart = spreadSheetDocument.WorkbookPart;

                Sheet ss = workbookPart.Workbook.Descendants<Sheet>().SingleOrDefault(s => s.Name == "User");

                if (ss == null)
                    throw new Exception("There was a problem trying to import the file. Please insure that the Sheet's name is: User");

                WorksheetPart worksheetPart = (WorksheetPart)workbookPart.GetPartById(ss.Id);

                OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);
                StringTablePart = workbookPart.SharedStringTablePart;

                while (reader.Read())
                {
                    if (reader.ElementType == typeof(Row))
                    {
                        do
                        {
                            if (reader.HasAttributes)
                            {
                                var rowNum = int.Parse(reader.Attributes.First(a => a.LocalName == "r").Value);

                                if (rowNum == 1)
                                    continue;

                                var row = (Row)reader.LoadCurrentElement();
                                yield return row;
                            }

                        } while (reader.ReadNextSibling()); // Skip to the next row
                        break; // We just looped through all the rows so no need to continue reading the worksheet
                    }
                }
            }
        }

 private void ProcessInParallel()
        {
            // Use ConcurrentQueue to enable safe enqueueing from multiple threads. 
            var exceptions = new ConcurrentQueue<Exception>();


            Parallel.ForEach(ReadDocument(), (row, loopState) =>
                {

                    List<Cell> cells = row.Descendants<Cell>().ToList();

                    if (string.IsNullOrEmpty(GetCellValue(cells[0], StringTablePart)))
                        return;

                    // validation code goes here....


                    try
                    {
                        using (SqlConnection connection = new SqlConnection("user id=sa;password=D3vAdm!n@;server=196.30.181.143;database=TheUnlimitedUSSD;MultipleActiveResultSets=True"))
                        {
                            connection.Open();
                            SqlCommand command = new SqlCommand("SELECT count(*) FROM dbo.[User] WHERE MobileNumber = '" + mobileNumber + "'", connection);
                            var userCount = (int) command.ExecuteScalar();
                            if (userCount > 0)
                            {
                                // update
                                command = new SqlCommand("UPDATE [user] SET NewMenu = " + (newMenuIndicator ? "1" : "0") + ", PolicyNumber = '" + policyNumber + "', Status = '" + status + "' WHERE MobileNumber = '" + mobileNumber + "'", connection);
                                command.ExecuteScalar();
                                Debug.WriteLine("Update cmd");
                            }
                            else
                            {
                                // insert
                                command = new SqlCommand("INSERT INTO dbo.[User] ( MobileNumber , Status , PolicyNumber ,  NewMenu ) VALUES  ( '" + mobileNumber + "' , '" + status + "' ,  '" + policyNumber + "' ,  " + (newMenuIndicator ? "1" : "0") + " )", connection);
                                command.ExecuteScalar();
                                Debug.WriteLine("Insert cmd");
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        exceptions.Enqueue(ex);
                        Debug.WriteLine(ex.Message);
                        loopState.Break();
                    }
                });

            // Throw the exceptions here after the loop completes. 
            if (exceptions.Count > 0)
                throw new AggregateException(exceptions);

        }

【问题讨论】:

  • 是否需要从 .net 读取 excel 文件?我的意思是你为什么不在 SQL server 中使用 SSIS 或 Import 功能?
  • 文件是通过asp.net网站读入的。此外,托管数据库的服务器没有安装 excel,因此很难导入该文件。

标签: c# excel import parallel-processing


【解决方案1】:

我会建议您在不对中间表进行任何验证的情况下进行批量导入,然后才通过 SQL 进行所有验证。您的电子表格数据现在将采用与 SQL 表类似的结构。 这就是我从 Excel 和 CSV 导入 300 万行 + 工业强度并取得了巨大成功。

【讨论】:

    【解决方案2】:

    大多数情况下,我建议您检查您的并行性是否最佳。由于您的瓶颈可能是 Excel 文件上的磁盘 IO 和 Sql 服务器的 IO,我建议它可能不是。您已经跨这两个进程进行了并行化(因此每个进程都降低到最慢的速度);您的并行线程将争夺数据库并可能减慢彼此的速度。如果你的硬盘跟不上一个线程,那么拥有(比如说)八个线程是没有意义的——它只会产生开销。

    我建议两件事。首先:去掉所有的并行性,看看它是否真的有帮助。如果你单线程将整个文件解析到内存中的单个队列中,然后将整个东西运行到数据库中,你可能会发现它更快。

    然后,我会尝试将其拆分为两个线程:一个用于处理传入队列的文件,另一个用于从队列中获取项目并将它们推送到数据库中。这样,您处理的每个慢速资源都有一个线程 - 因此您可以最大限度地减少争用 - 每个线程仅被一个资源阻塞 - 因此您可以尽可能优化地处理该资源。

    这是多线程编程的真正诀窍。在问题上抛出额外的线程并不一定会提高性能。您要做的是尽量减少程序空闲等待外部(例如磁盘或网络 IO)完成的时间。如果一个线程 only 在 Excel 文件上等待,而一个线程 only 在 SQL 服务器上等待,并且它们之间的操作很少(在您的情况下,它是),您会发现您的代码运行速度与外部资源允许的速度一样快。

    此外,您自己也提到过,但使用参数化 Sql 不仅仅是一件很酷的事情要指出:它会提高您的性能。目前,您正在为每个插入创建一个新的SqlCommand,这会产生开销。如果您切换到参数化命令,则可以始终保持相同的命令,只需更改参数值,这将为您节省一些时间。我认为这在并行 ForEach 中是不可能的(我怀疑您是否可以跨线程重用 SqlCommand),但它适用于上述任何一种方法。

    【讨论】:

      【解决方案3】:

      增强处理的一些技巧(我相信这是您需要的,而不是真正的代码修复)。

      • 事先让 Excel 检查重复行。对于淘汰过时的工具,这是一个非常不错的工具。如果 A 和 B 重复,您将创建 A,然后使用 B 的数据进行更新。这样,您就可以淘汰 A,只创建 B。
      • 不要将其作为 .xls(x) 文件处理,将其转换为 CSV。 (如果您还没有)。
      • 在您的数据库上创建一些存储过程。在项目中用于简单数据检索时,我通常不喜欢存储过程,但对于需要高效运行的自动化脚本,它可以创造奇迹。只需添加一个 Create 函数(我假设在您清除重复项后不需要更新函数(在技巧 1 中)。+

      一些我不确定是否会对您的具体情况有所帮助的提示:

      • 使用 LINQ 而不是创建命令字符串。 LINQ 会自动微调您的查询。但是,突然切换到 LINQ 并不是一眨眼就能完成的事情,因此您需要付出的努力超过您的需要。
      • 我知道你说数据库服务器上没有 Excel,但是你可以让数据库处理 .csv 文件,而不需要安装 csv 文件的软件。您可以查看以下内容:http://dev.mysql.com/doc/refman/5.1/en/load-data.html

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-03
        • 2011-10-22
        • 2016-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多