2

我有一个 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);

        }
4

3 回答 3

4

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

于 2012-08-21T07:56:04.340 回答
0

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

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

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

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

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

于 2012-08-21T07:52:28.087 回答
0

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

  • 让 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
于 2012-08-21T08:08:30.540 回答