【问题标题】:C# + Sql Server - Execute a stored procedure large number of times. Best way?C# + Sql Server - 多次执行存储过程。最好的办法?
【发布时间】:2009-06-30 03:17:09
【问题描述】:

我有一个将数据插入 3 个表的存储过程(使用 UPSERTS),并且有一些基本的逻辑。 (如果-那么-否则)

我需要使用不同的参数执行这个 Sproc 数百万次(来自 C# 应用程序),并且我需要它是 FAST。

最好的方法是什么?

有人知道除了 Lucene 或 Sql Server FTS 之外的开源(或不)现成文档索引器吗?

*我正在尝试建立一个文档单词索引。对于文档中的每个单词,我将单词、docID 和单词位置插入到数据库中。

例如,对于 100 个文档,这种情况会发生 100000 次。

Sproc:有 3 个表要插入,每一个我都会做一个 UPSERT。

C# 应用程序

using (SqlConnection con = new SqlConnection(_connectionString))
            {
                con.Open();
                SqlTransaction trans = con.BeginTransaction();
                SqlCommand command = new SqlCommand("add_word", con, trans);
                command.CommandType = System.Data.CommandType.StoredProcedure;
                string[] TextArray;
                for (int i = 0; i < Document.NumberOfFields; i++)
                {
                  ...
                 Addword(..., command);  <---- this updates parameters with new values and ExecuteNonQuery.
                }

            }

我忘了提,这段代码会在 Sql Server 中产生死锁。我不知道为什么会这样。

【问题讨论】:

  • 你现在的执行方式是不是很慢?
  • 它实际上是做什么的?是否可以将多个 UPSERTS 表示为一个 upsert(例如,不是将 1 项的计数增加 5 次,而是更新一次以将其计数增加 5。
  • 我正在建立一个文档词索引。所以我要添加一个词,它是文档和该文档中的位置。当我有数百个文档时,它有数十万个单词,而且速度很慢......
  • SUYC(向我们展示您的代码)
  • 您是否考虑过使用内置的全文索引功能?或者甚至寻找第三方选项来做到这一点?

标签: c# sql-server sql-server-2005 stored-procedures


【解决方案1】:
  1. 删除正在加载的表上的所有索引,然后在加载完成后将它们重新添加。这将防止每次更改都发生大量抖动/重新索引。

  2. 确保数据库在加载之前分配了足够的物理文件空间,这样它就不必在加载时花时间不断地从文件系统中获取它。通常数据库设置为在满时增长大约 10%,此时 sql server 会阻止查询,直到分配更多空间。在加载你所说的数据量的时候,sql会做很多阻塞。

  3. 如果可能,请查看批量加载/批量复制。

  4. 在代码中执行所有 IF THEN ELSE 逻辑。准备好后,只需将您想要存储的实际值发送到 s'proc。你甚至可以运行两个线程。一个用于评估数据并将其排队,另一个用于将队列写入数据库服务器。

  5. 查看现成的程序,这些程序完全按照您所说的对文档进行索引。他们很可能已经解决了这些问题。

  6. 尽可能摆脱事务要求。尽量让 s'proc 调用尽可能简单。

  7. 看看你是否可以限制你存储的单词。例如,如果您不关心“it”、“as”、“I”等词,那么在调用 s'proc 之前将它们过滤掉。

【讨论】:

  • 我的 If-Then-Else 逻辑依赖于检查行是否出现在表中,因此将它们移动到 C# 代码意味着循环中执行更多的 Sproc...
  • 嗯.. 那我猜你不能做那部分。不过其他人应该会有所帮助。
【解决方案2】:

如果您想从 C# 快速批量插入数据,请查看 SqlBulkCopy 类(.NET 2.0 及更高版本)。

【讨论】:

    【解决方案3】:

    这似乎是一种初步的方法,但它应该可以工作并且应该很快。您可以只生成一个包含 SQL 语句列表的巨大文本文件,然后从命令行运行它。如果我没记错的话,应该可以使用 GO 语句来批处理命令。或者,您可以直接从应用程序中将多个 SQL 命令连接为字符串并批量执行它们。似乎您正在尝试做的是一次性任务,并且数据不会直接作为用户输入。所以你应该能够自己处理逃跑。

    我确信有更复杂的方法可以做到这一点(SqlBulkCopy 看起来是一个好的开始),所以请将此视为一个建议。我会先花一些时间调查是否有更优雅的方法更好的方法。

    另外,我会确保存储过程中的逻辑尽可能简单,并且表没有任何索引。它们应该稍后添加。

    【讨论】:

    • +1,如果我的数据已经在 C# 中,我会这样做。请注意,某些索引可能有用/重要(因为这是 UPSERT 而不是 INSERT)。
    • GO 关键字仅对 Query Analyzer(在 Sql2K 领域中)等 Sql Server 实用程序有意义,而 .NET SqlClient 类无法识别:因此您应该删除它们并 - 如有必要 - 执行一次一个的声明。仅供参考,看看我对这篇文章的回答:stackoverflow.com/questions/784953/…
    【解决方案4】:

    这可能作为一个要求太笼统了 - 为了使过程本身快速,我们需要查看它并了解您的 db-schema。

    另一方面,如果您想知道尽可能快地执行相同(未优化或优化)过程的最佳方法,通常最好的方法是进行某种缓存 在客户端上并尽可能少地调用该过程来批处理您的操作。

    如果这是在一个循环中,人们通常做的是——而不是每次迭代都调用该过程——构建/填充一些缓存数据结构,当循环退出(或任何给定数量的循环)时将调用存储过程如果您需要更频繁地发生这种情况)批处理您缓存的操作(即,您可以将一个 xml 字符串传递给您的 sp,然后它将解析它,将这些东西放在临时表中,然后从那里开始 - 您可以保存整个很多这样的开销)。

    另一个常见的解决方案是使用 SqlServer Bulk 操作。

    回到存储过程 - 请记住,优化您的 T-SQL 和 db-schema(使用索引等)会对您的性能产​​生巨大的影响。

    【讨论】:

      【解决方案5】:

      尝试使用 XML 来做到这一点。

      你只需要执行 1 次:

      例子:

      DECLARE @XMLDoc XML
      SET @XMLDoc = '<words><word>test</word><word>test2</word></words>'
      
      CREATE PROCEDURE add_words
      (
          @XMLDoc XML
      )
      AS
      
      DECLARE @handle INT
      
      EXEC sp_xml_preparedocument @handle OUTPUT, @XMLDoc
      
      INSERT INTO TestTable
      SELECT * FROM OPENXML (@handle, '/words', 2) WITH 
        (
          word varchar(100)
        )
      EXEC sp_xml_removedocument @handle
      

      【讨论】:

        【解决方案6】:

        如果您尝试优化速度,请考虑简单地升级您的 SQL Server 硬件。在您的服务器中放置一些 RAM 和超快的 RAID 可能是加快查询速度的最具成本效益的长期解决方案。与开发时间相比,硬件相对便宜。

        注意杰夫·阿特伍德的话:

        Coding Horror: Hardware is Cheap, Programmers are Expensive

        【讨论】:

        • 很遗憾,许多经理/会计师将硬件视为额外费用,但无论优化查询需要多长时间,您的薪水都是一样的。
        【解决方案7】:

        在这种情况下,与数据库的通信可能会成为瓶颈,尤其是当数据库位于另一台机器上时。我建议将整个文档发送到数据库并编写一个将其拆分为单词的存储过程,或者使用 sql-server 托管的托管代码。

        【讨论】:

          【解决方案8】:

          假设这是一个不会在多个用户之间发生争用的应用,请尝试以下方法:

          • 将您的参数插入为此目的设置的表中
          • 更改您的 SP 以循环遍历该表并在每一行上执行其工作
          • 调用 SP 一次
          • 当输入表完成时让 SP 截断输入表

          这样就消除了调用SP数百万次的开销,参数插入表中可以串联("INSERT INTO foo(v) VALUE('bar'); INSERT INTO foo(v) VALUE ('bar2'); INSERT INTO foo(v) VALUE('bar3');").

          缺点:SP执行时间长,而且不会有进度反馈,用户体验不是很友好。

          【讨论】:

            【解决方案9】:

            要将大量数据移动到服务器,如果您使用的是 2008,请使用 SqlBulkCopy 或表值参数。如果您需要速度,请不要每行执行一次存储过程,开发一个基于集合的处理所有(或大量)行。

            【讨论】:

              【解决方案10】:

              --自编辑问题以来已编辑。

              最大的问题是确保正确调整存储的过程。您的 C# 代码与您获得它的速度差不多。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2019-12-03
                • 2020-11-19
                • 2010-12-28
                • 2016-03-25
                • 2011-11-07
                • 1970-01-01
                相关资源
                最近更新 更多