1

我必须更新一个包含数百万条记录的表。现在我将要更新的所有记录的 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 的资源异常。

4

7 回答 7

2

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

或者,您也可以制作一个#temp 表(或临时表),用SqlBulkCopy参见此处)填充它,然后JOIN对它执行您的UPDATE.

于 2013-07-09T15:03:56.240 回答
1

您可以找到一种方法将 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();
   }
}
于 2013-07-09T14:37:02.580 回答
0

假设 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 个,直到所有这些都处理完毕。

于 2013-07-09T14:29:04.690 回答
0

我会采取的策略:

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。如果您要针对同一组数字进行多次测试,则在加载表后对其进行索引可能是有意义的。

于 2013-07-09T15:14:55.873 回答
0

对您的代码的一项建议

使用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 倍的速度有多快

于 2013-07-10T09:24:33.660 回答
0

关于更新大小的答案:

如果要更新的 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您设置中设置新值,最后您只需重命名表。

于 2013-07-09T14:49:25.377 回答
0

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

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

    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;

于 2013-07-09T12:59:05.567 回答