【问题标题】:C#-SQL: How to execute a batch of StoredProcedure?C#-SQL:如何执行一批StoredProcedure?
【发布时间】:2010-09-17 02:10:36
【问题描述】:

编辑:
我的问题不再是问题了:我已经重做我的性能测试并且我犯了一个致命的愚蠢错误:我忘记了 x1000 从毫秒获得秒数:/ 对不起那些家伙。
信息:
- 我每秒从我的 PC 到本地网络上的数据库服务器进行大约 1900 次更新。
- 如果程序与 DB 在同一台机器上,则每秒更新 3.200 次。
- 每秒 3.500 次从我的 PC 上的数据库服务器更新我没有重新创建和重新打开新的 SQLConnection。
- 每秒 5.800 次更新,带有批处理文本。 对于我的 10.000 行,如果需要 5 秒,我的程序就可以了。很抱歉让您担心了。

实际上,我使用 SQL 存储过程在我的数据库中创建一行以避免 SQL 注入。在 C# 中,我有以下方法:

public void InsertUser(string userCode)
{
   using (SqlConnection sqlConnection = new SqlConnection(this.connectionString))
   {
      SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection);
      sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
      sqlCommand.Parameters.Add(new SqlParameter("@UserCode", userCode));
      sqlConnection.Open();
      sqlCommand.ExecuteNonQuery();///0.2 seconds !ERROR HERE! 0.2ms here,NOT 0.2sec!!!
   }
} 

当我要插入一两行时,它会很棒。但如果我需要创建 1.000 个用户、10.000 个产品和 5000 个宠物,这不是最好的解决方案:我将在网络传输中浪费大量时间。

我相信,不检查它,我可以只使用有限数量的回调。所以我不想调用 10.000 次:

sqlCommand.BeginExecuteNonQuery()

另一种方法是创建批处理文本,但存在 SQL 注入风险(而且很难看)。

.Net 中是否有一个“SqlCommandList”对象来管理它?如何在数据库中进行大量写入?那有什么好的模式?

【问题讨论】:

  • 我建议使用“sqlCommand.Prepare();”参数类型应该至少可以提高第二次查询的执行时间。

标签: c# performance stored-procedures batch-file sqlcommand


【解决方案1】:

这应该运行得更快一点:

public void InsertUser(IEnumerable<string> userCodes)
{
   using (SqlConnection sqlConnection = new SqlConnection(this.connectionString), 
             SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection))
   {
      sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
      SqlParameter param = sqlCommand.Parameters.Add("@UserCode", SqlDbTypes.VarChar);
      sqlConnection.Open();

      foreach(string code in userCodes)
      {
          param.Value = code;
          sqlCommand.ExecuteNonQuery();///0.2 seconds
      }
   }
}

即使你传递了 1000 个用户,它也只会打开一个连接并且只创建一个命令。不过,它仍然会单独执行每个插入操作。当然,如果 userCode 不是字符串,您需要适当地重新考虑它。您可能还想查看 SQL Server 的 BULK INSERT 命令。

【讨论】:

    【解决方案2】:

    SQLDataAdaptor 的 UpdateBatchSize 怎么样?

    我们的前端人员使用它来将几 10,000 个 proc 调用批处理成块

    Article

    MSDN

    我们的环境不允许“bulkadmin”权限,所以我们不能使用 BULKINSERT/bcp 等

    【讨论】:

      【解决方案3】:

      就个人而言,如果我经常希望进行相当大的插入(10,000 行肯定符合条件...),我可能会考虑为传入数据创建一个单独的表,并使用SqlBulkCopy 填充此表。然后,您只需执行一个将数据移动到真实表中的存储过程。

      另一种方法是将 xml 发送到数据库,并使用 sqlxml 来解析它(使用 SQL2005 及更高版本更容易)-但这会给 db 服务器带来额外的工作。

      【讨论】:

        【解决方案4】:

        如果你真的担心这个,你可以(就像你说的那样)将命令批处理成这样的字符串:

        var cmd = new SqlCommand();
        cmd.Connection = sqlConnection;
        for (int i = 0; i < batchSize; i++) {
            cmd.CommandText += String.Format("EXEC InsertUser @UserCode{0};", i);
            cmd.Parameters.AddWithValue("@UserCode" + i.ToString(), XXXXX);
            //... etc ...
        }
        

        因为在此方案中,您将使用一个参数,因此与使用存储过程相比,您不会有更多的 SQL 注入风险。但我怀疑您是否真的会节省大量时间。 IMO,您应该保持简单,并按照您现在的方式进行操作。

        【讨论】:

        • 重新创建命令并不断打开和关闭新连接会产生大量开销。
        • 另外,如果你要那样做,请使用字符串生成器来组装命令。
        • Joel,我想您会发现连接池大大加快了速度。至于StringBuilder,它确实会加快速度并减少内存使用量,但这完全取决于你想要在批处理中使用多少命令......
        【解决方案5】:

        根据 Joel 的回答,这是不使用 SqlBulkCopy 或创建大量凌乱的 SQL 并执行的最快解决方案。 (我添加了一个事务,这将大大提高性能)

        public void InsertUser(IEnumerabler<string> userCodes)
        {
           using (SqlConnection sqlConnection = new SqlConnection(this.connectionString))
           {
              sqlConnection.Open();
              SqlTransaction transaction = connection.BeginTransaction();
              SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection);
              sqlCommand.Transaction = transaction;
              sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
              SqlParameter param = sqlCommand.Parameters.Add("@UserCode", SqlDbTypes.VarChar);
        
              foreach(string code in userCodes)
              {
                  param.Value = code;
                  sqlCommand.ExecuteNonQuery();
              }      
              transaction.Commit();
           }
        }
        

        【讨论】:

        • 事务涉及锁定...如果单独运行它会更快。但是,如果服务器上有其他活动,它实际上会减慢速度,非常糟糕。虽然我承认只做插入不太可能导致冲突。
        【解决方案6】:

        我猜这是一个很老的问题。

        对于 SQL Server 2008,现在的答案是使用表值参数。简而言之,将所有变量传入一个使用过的定义类型(表)。

        在 SQL 中,您现在可以将所有记录作为单个项目处理...实际上使用集合逻辑并获得真正的性能。

        【讨论】:

          【解决方案7】:

          您是否考虑过将 XML 文档传递给存储过程,然后遍历该存储过程以查找要插入的数据?

          【讨论】:

            【解决方案8】:

            “这不是最好的解决方案:我将在网络传输中浪费大量时间” 你能承受损失吗?

            如果这是你不经常做的事情,那有关系吗? 首先测量它,如果它是一个问题然后修复它,我个人可能会使用 Marc Gravells 表进行传入插入。 另一种选择是异步触发插入,然后在开始下一个之前不必等待每个完成。

            我花了好几年的时间,但最后我发现我不应该浪费时间优化不需要它的代码。

            希望这会有所帮助(尽管我认为不会,抱歉)。

            【讨论】:

              【解决方案9】:

              根据上面的一些答案,以最少的努力实现最显着的性能提升涉及对现有代码的 2 处更改:

              1. 将更新包装在事务中
              2. 只打开一个连接并使用不同的参数多次调用该过程。

              BULK INSERT 是一种选择,但对于您想要做的事情来说可能有点矫枉过正。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-05-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多