【问题标题】:issues with a large sql query大型 sql 查询的问题
【发布时间】:2013-07-09 12:49:46
【问题描述】:

我必须更新一个包含数百万条记录的表。现在我将要更新的所有记录的 id 存储在列表中。查询的实际生成如下:

string queryPart="";
foreach (int id in transactionsToUpdate.ToList())
{
    queryPart+="TransactionID="+id;
    queryPart+=" OR ";
}

queryPart += "1=0";
string query = @"UPDATE dbo.OutgoingQueue SET Status='C' WHERE "+queryPart;

目前即使列表中有 100,000 个值,也会出现两个问题。首先,上面的代码需要很长时间才能执行(查询形成部分)。其次,当我在 DB 上执行查询时,它会给出 Timeout Expired 异常。有没有更好的方法来实现我想要的?

更新: 使用 stringbuilder 解决了第一个查询需要很长时间形成的问题。但第二个问题仍然存在。如果我增加超时,那么我会得到 sql 的资源异常。

【问题讨论】:

  • 在我的项目中没有使用 EF。
  • 创建一个接受逗号分隔的 id 列表的存储过程。然后在存储过程中使用带有“IN”子句而不是“OR”的 id 的更新语句。从 .Net 将逗号分隔的列表传递给存储的过程。
  • ID 是连续的吗?您能否对它们进行排序并使用 `where TransactionID > x AND TransactionID
  • 将 ID 转储到临时表中,然后根据连接或 IN(x) 查询进行更新。它会更快。
  • @KamranShahid :是的,这就是我最终所做的。

标签: c# sql


【解决方案1】:

这是表值参数的理想用例。见这里:http://msdn.microsoft.com/en-us/library/bb675163.aspx

或者,您也可以创建一个#temp 表(或临时表),用SqlBulkCopy (see here) 填充它,然后用JOIN 对它做你的UPDATE

【讨论】:

  • 我以前没用过这些。知道这如何转化为 100K+ 行的更新吗? “...表值参数在插入少于 1000 行时表现良好。” msdn.microsoft.com/en-us/library/bb510489.aspx#BulkInsert
  • @DaveZiegler SqlBulkCopy 在高端可能更快,但由于#temp 表设置和管理而稍微复杂一些。我认为 TVP 在高端并不会慢得多(无论如何,根据 SQLCAT 团队的说法),并且如果您将其批量为 1000 个(无论如何您可能都想这样做)它可能一样快。
  • @DaveZiegler 我添加了SqlBulkCopy 作为替代方案。 (这其实是我自己常用的。)
  • 我认为比 SqlBulkcopy 更好的表值参数
【解决方案2】:

您可以找到一种方法将 100,000 个值传递到数据库中,尽管如果您使用的参数会很快遇到限制。

更新附加在事务中

另外,这就是准备好的查询的用途

using (var conn = <GETCONNETIONMETHOD>)
{
  conn.Open();
  using (var tran = conn.BeginTransaction()) 
  {
    using (var cmd = conn.CreateCommand(
        @"update dbo.outgoingqueue set status = 'C' where transactionID = @id"))
    {
       cmd.Transaction = conn.BeginTransaction();
       var param = cmd.Parameters.Add("@id", typeof(int));
       cmd.Prepare();
       foreach (int id in transactionsToUpdate.ToList())
       {
         param.Value = id;
         cmd.ExecuteNonQuery();
       }
       tran.Commit();
     }
  }
}

如果您有足够的权限来执行批量复制,那么最好的方法是

using (var conn = <GETCONNECTIONMETHOD>)
{
   var dt = new DataTable;
   dt.BeginLoadData();
   dt.Columns.Add("id");
   foreach (int id in transactionsToUpdate.ToList() {
     dt.Rows.Add(id);
   }
   dt.EndLoadData();

   using (var cmdSetup = conn.CreateCommand(@"create table #tempUpdate(int id)")) {
      cmdSetup.ExecuteNonQuery();
   }
   var bcp = new SqlBulkCopy(conn);
   bcp.DestinationTableName = "#tempUpdate";
   bcp.WriteToServer(dt);
   using (var cmdUpdate = conn.CreateCommand(
      @"update o set status = 'C' from dbo.outgoingQueue o " +
      @"inner join #tempUpdate t on o.transactionId = t.id"))
   {
      cmd.ExecuteNonQuery();
   }
}

【讨论】:

  • 但我没有将其标记为答案,因为在此代码中 cmd.ExecuteNonQuery() 将被调用 100,000 次,我猜这在性能方面并不好。还是这样?
  • @HarshMaurya 你说得对,快速调用 ExecuteNonQuery() 100000 次是不好的。
  • @AzharKhorasany 我可以从概念上理解为什么向ExecutenOnQuery 拨打 100,000 次电话可能很糟糕,但是您是否有任何真实世界的数字来支持该声明。为什么具体不好。我不是在争论,我只是想知道。
  • @HarshMaurya 我现在更新了答案,还包括批量复制操作,如果您有足够的权限,这应该是最好的方法。
【解决方案3】:

假设 transactionsToUpdate 是一个 int 列表,

在此处获取逗号分隔的列表:

string queryPart = String.Join(",", transactionsToUpdate.ToArray());

然后像这样在查询中传递它:

string query = @"UPDATE dbo.OutgoingQueue SET Status='C' WHERE TransactionID IN(" + queryPart + ")";

或者,您可以创建一个接受逗号分隔值列表的存储过程,并将 queryPart 传递给存储过程。

更新:

然后您可以通过 .Net 进行批量操作,例如:

int count = 0;
int bulkCount = 1000;

while (count < transactionsToUpdate.Count)
{
    string queryPart = String.Join(",", transactionsToUpdate.ToArray().Skip(count).Take(bulkCount));
    string query = @"UPDATE dbo.OutgoingQueue SET Status='C' WHERE TransactionID IN(" + queryPart + ")";

    //execute the sql here by doing the ExecuteNonQuery call.

    count += bulkCount;
}

此查询将从列表中取出前 1000 个,处理它们,然后再取出 1000 个,直到全部处理完毕。

【讨论】:

  • 还是一样的异常。可能即使对于 IN 子句也很复杂
  • 最好使用表值参数
  • @KamranShahid 无需对答案投反对票,因为这不是错误的答案。而且由于没有其他答案可以告诉实现表值函数,所以这是最好的答案。
【解决方案4】:

关于更新大小的答案:

如果要更新的 id 值较小,则可以创建批处理语句并在单个事务中发送。

update dbo.outgoingqueue out set status = 'C' where out.transactionID = %1;
update dbo.outgoingqueue out set status = 'C' where out.transactionID = %2;
update dbo.outgoingqueue out set status = 'C' where out.transactionID = %3;
update dbo.outgoingqueue out set status = 'C' where out.transactionID = %4;
update dbo.outgoingqueue out set status = 'C' where out.transactionID = %5;
update dbo.outgoingqueue out set status = 'C' where out.transactionID = %6;

如果 id 不是那么小而不能在单个事务中执行,您可以创建一个临时表并执行更新查询,如下所示:

update dbo.outgoingqueue out set status = 'C' where 
 exists (select null from tmp_tab where tmp_tab.transactionID = out.transactionID);

如果您打算更新所有记录,最好的方法是根本不更新。

您应该使用以下新名称创建一个新表:

select <column list> into <table name> from <source>;

然后在select 你设置你的新值,最后你只是重命名表。

【讨论】:

    【解决方案5】:

    我会采取的策略:

    cmd.CommandText = "CREATE TABLE #Values( id  Int )";
    cmd.ExecuteNonQuery();
    
    
    foreach (int id in transactionsToUpdate.ToList())
    {
        cmd.CommandText = "INSERT INTO #Values VALUES( " + id.ToString() + ");" 
        cmd.ExecuteNonQuery();
    }
    

    现在,您可以通过加入这个 TEMP 表(这是 SQL 的自然域)来测试值,而不是 100,000 个 IF。如果您要针对同一组数字进行多次测试,那么在加载表格后对其进行索引可能是有意义的。

    【讨论】:

      【解决方案6】:

      你可以做以下事情来解决你的问题。

      1. 速度问题 为此,让我知道 transactionsToUpdate.ToList() 这个列表中有哪些项目?如果你 从数据库中填写此列表,然后我建议修改您的更新查询,以便您不需要运行 for 循环。它将提高您的应用程序的性能。您可以执行以下查询。无需每次都使用循环。它将比您当前的代码运行得快很多,我认为如果您使用此查询,您将不会遇到超时问题。

        UPDATE dbo.OutgoingQueue 
        SET Status='C' 
        FROM dbo.OutgoingQueuE AS A
        INNER JOIN 
        (
        QUERY BY WHICH YOU FILL UP CURRENT LIST
        ) AS B ON A.TransactionID = B.ID
        
      2. 超时问题 您可以在执行 sql 命令时设置命令超时。

        SqlCommand myCommand = new SqlCommand(); myCommand.CommandTimeout = 15;

      【讨论】:

      • 关于第二个问题,如果我增加超时,我会得到“sql out of resource exception”,它说发生在极其复杂的查询中。但我不明白为什么我的查询如此复杂。
      • 您的查询很复杂,因为它在查询的 where 子句中包含 100,000 个 OR 的组合。这将花费大量时间在 sql server 中运行。
      • 告诉我 transactionsToUpdate 列表中有什么?我有一个解决方案。
      • transactionsToUpdate 列表包含整数
      • 整数表示 1 到 100,000 ??它是固定的还是你从数据库中填充的?
      【解决方案7】:

      对您的代码的一个建议

      使用StringBuilder 而不是String 它可以加快您的进程

      StringBuilder queryPart = new StringBuilder("");
      foreach (int id in transactionsToUpdate.ToList())
      {
      queryPart.Append("TransactionID=");
      queryPart.Append(id);
      queryPart.Append(" OR ");
      }
      queryPart.Append("1=0");
      string query = @"UPDATE dbo.OutgoingQueue SET Status='C' WHERE "+queryPart.toString();
      

      如果您进行大型操作,您应该始终使用StringBuileder

      编辑 1 您可以使用 StopWatch 类检查您的执行性能

      这将显示 StringBuilder Ever 比 String 快 100 到 1000 倍的速度

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        • 2012-09-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多