大型SQL查询的问题

时间:2013-07-09 12:49:47

标签: c# sql

我必须更新包含数百万条记录的表格。现在我存储要在List中更新的所有记录的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。

7 个答案:

答案 0 :(得分:2)

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

或者,你也可以创建一个#temp表(或一个临时表),用SqlBulkCopysee here)填充它,然后用JOIN填充它UPDATE来执行你的{{ 1}}。

答案 1 :(得分: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();
   }
}

答案 2 :(得分:0)

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

  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;

答案 3 :(得分: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个,直到所有这些处理完毕。

答案 4 :(得分: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中设置您的新值,最后您只需重命名表格。

答案 5 :(得分: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。如果您要对同一组数字进行多次测试,那么在加载表后对其进行索引可能是有意义的。

答案 6 :(得分: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的速度比String

快100到1000倍