【问题标题】:Multiple threads for optimal performance problem多线程优化性能问题
【发布时间】:2011-08-18 16:55:23
【问题描述】:

我有一个巨大的批处理操作,每隔几个月就会运行一次,解析文本文件并将其导入 Sql Server 数据库。该过程需要几天时间才能完成,我正在寻找加快速度的方法。大约 1/3 的时间用于解析文本,2/3 的时间用于数据库 I/O。

我认为一个简单的解决方案是将这些拆分成单独的线程。因此,当一个线程写入数据库时​​,另一个线程可以解析文本。我更改了代码以构建需要执行的 SqlCommand 对象列表,然后在解析完成后将这些对象传递给新线程执行。

在一个小示例中,在单个线程中执行一批 SqlCommand 对象需要 37 秒,然后当我切换到在单独的线程中执行这些对象时,我感到惊讶的是,该过程大大减慢了,总共需要 63.34 秒。我做了一些探索,最终决定在 Visual Studio 中运行一些性能分析。我运行 Instrumentation 来测量多线程版本的时间,当它在 31.04 秒内运行时我感到很惊讶。我多次重新运行所有测试,结果或多或少相同。因此,似乎在运行性能分析时,对工作负载的拆分会提高性能,但在不运行性能分析时会减慢。

如果有人可以帮助指出可能导致此问题的原因以及我应该在哪里解决它,那就太棒了!

测试在四核 VMware 虚拟机中运行,该虚拟机在 6 核主机上运行。

编辑:进一步研究后,违规行似乎是与解析相关的行,与数据库无关,主要是 fileText.Trim()。为什么这些在附加调试器的情况下运行速度要慢得多我不知道。

编码启动新线程

        while (sqlWriterThread != null && sqlWriterThread.ThreadState == ThreadState.Running)
            Thread.Sleep(0);
        if (sqlWriterThread == null || sqlWriterThread.ThreadState == ThreadState.Stopped)
        {
            sqlWriterThread = new Thread(new ParameterizedThreadStart(SqlWriterThread));
            sqlWriterThread.Name = "SqlWriterThread";
            sqlWriterThread.Priority = ThreadPriority.Highest;
        }
        sqlWriterThread.Start(commandBatch);
        Thread.Sleep(0);

查询执行代码

    public void SqlWriterThread(object commandBatch)
    {
        List<SqlCommand> batch = (commandBatch as List<SqlCommand>);
        using (SqlConnection connection = new SqlConnection(HelperDatabase.ConnectionString))
        {
            connection.Open();
            SqlTransaction transaction = connection.BeginTransaction();
            try
            {
                foreach (SqlCommand cmd in batch)
                {
                    cmd.Connection = connection;
                    cmd.Transaction = transaction;
                    cmd.ExecuteNonQuery();
                    cmd.Dispose();
                }

                transaction.Commit();
            }
            catch
            {
                transaction.Rollback();
            }
        }
    }

【问题讨论】:

  • 您使用的 .net 框架版本是什么?
  • 当您计时 63.34 秒时,您是否在附加调试器的情况下运行?点击 ctrl+f5 以在没有调试器的情况下运行,而只需按 f5 将在附加调试器的情况下运行,这会降低性能
  • @BrandonAGr - 这似乎解决了这个问题。如果您将其发布为答案,那么我会将其标记为已回答。这并不能解释为什么在附加调试器的情况下运行多个线程应该以仅运行单个线程的一半速度运行。这才是真正的奥秘。

标签: c# sql-server multithreading performance


【解决方案1】:

与任何 SQL Server 性能问题一样,我建议使用Waits and Queues 方法。这会将问题缩小到实际等待/争用/瓶颈发生的位置。

没有任何进一步的数据,并且在您的帖子中缺乏任何特定的 SQL 信息,人们无法说太多:您的批处理中的那些 sqlCommands 是什么?是堆吗?是btree吗?多少个二级索引?架构的准确定义、准确的数据库文件位置和主轴分布,你知道的,基本信息。

【讨论】:

  • 数据库位于 SSD 上,因此没有主轴。我会弄清楚我可以以有意义的方式包含哪些其他信息。
【解决方案2】:

您将同步操作的执行与异步模式分开,而其他线程可能同时运行,导致操作执行时间更长。

但是,如果您将其他部分与线程分开,则情况并非如此,因此在这种情况下您将获得多线程的好处。即:一个线程中的“解析文本”,“另一个线程中的数据库 I/O”,如果适用的话,还将线程内的工作分成更多的块“线程”。

如果您正在运行4.0,我建议您使用Parallel.ForEach 来执行 DB 线程内的代码:

Parallel.ForEach(batch => cmd
{
    cmd.Connection = connection;
    cmd.Transaction = transaction;
    cmd.ExecuteNonQuery();
    cmd.Dispose();
});

【讨论】:

  • Parallel.ForEach 无法工作,因为每个事务和每个连接一次只能执行一个命令。
【解决方案3】:

如果需要几天时间,您的流程本质上就会中断。您是否一次处理一张记录?尝试对临时表进行批量插入,然后使用 SQl 来存储数据,然后使用基于集合的过程来插入数据(如果文件很大,您可能希望一次批量循环数千个)。

或者创建一个 SSIS 包来为您加载。

【讨论】:

  • 一次解析1块文本,这不是一个简单的解析过程。然后创建并插入与该文本块相关的所需记录。
  • 我一直在做复杂的转换,但以基于集合的方式进行。我在不到一个小时的时间内导入了超过 2000 万条记录。
  • 这不仅仅是导入数据,而是基于数千行代码创建各种规则来读取、写入和更新已有的数据。在此过程中创建了 2 亿多条记录。
【解决方案4】:

您将许多命令包装在客户端管理的批处理事务中。它们是什么类型的命令?

如果事务是简单的插入,我会想只写一个文件并使用 BCP/SSIS,但我想它比这要复杂得多。

如果它是多个父子插入(这就是您使用事务的原因 - 尽管我没有看到任何迹象,因为您似乎没有为创建子项保存父 ID),这可以用存储过程的表值参数,它在一次调用中完成整个事务 - 开始事务插入父级、插入子级、提交事务?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-21
    • 1970-01-01
    • 2016-03-24
    相关资源
    最近更新 更多