如何在C#.NET(SQL Server)中正确有效地重用预准备语句?

Jos*_*osh 14 .net c# sql sql-server prepared-statement

我看了很多问题,但显然我的SO-fu不能胜任这个任务,所以我在这里.我试图有效地使用预准备语句,我不仅仅意味着参数化单个语句,而是编译一个语句以便重复使用多次.我的问题在于参数和重用以及如何正确实现.

一般来说,我遵循这个程序(人为的例子):

SqlConnection db = new SqlConnection(...);
SqlCommand s = new SqlCommand("select * from foo where a=@a", db);
s.Parameters.Add("@a", SqlDbType.VarChar, 8);
s.Prepare();
...
s.Parameters["@a"] = "bozo";
s.Execute();
Run Code Online (Sandbox Code Playgroud)

超级,有效.但是,每次运行此查询时,我都不想执行所有这些步骤(或后四个步骤).这似乎抵消了准备陈述的整个想法.在我看来,我只需要更改参数并重新执行,但问题是如何做到这一点?

我试过s.Parameters.Clear(),但这实际上删除了参数本身,而不仅仅是值,所以我基本上需要重新Add参数并再次重复Prepare,这似乎也打破了整个点.不用了,谢谢.

在这一点上,我留下迭代s.Parameters并将它们全部设置为null或其他值.它是否正确?不幸的是,在我目前的项目中,我有大约15个参数的查询需要执行〜每次运行10,000次.我可以将这个迭代分流成一个方法但是想知道是否有更好的方法来做到这一点(没有存储过程).

我目前的解决方法是一个扩展方法,SqlParameterCollection.Nullify它将所有参数设置为null,这对我的情况很好.我只是在执行后运行它.


我发现了一些几乎完全相同但(恕我直言)尚未解答的问题:

准备语句和.NET中的内置连接池

SQLite/C#连接池和准备语句混淆(Serge非常接近回答!)

我能找到的最佳答案是(1)上面的常识和(2)这个页面:

http://msdn.microsoft.com/en-us/magazine/cc163799.aspx

sim*_*rcl 10

重新使用准备好的SqlCommand时,您当然需要将参数值设置为新值吗?使用后您无需清除它们.

对于我自己,我没有看到过去10年中生成的DBMS,它从准备语句中获得了明显的好处(我想如果数据库服务器可能处于其CPU的极限,但这不是典型的).你确定准备是必要的吗?

除非你从外部源上传,否则运行相同的命令"每次运行~10,000次"对我来说有点香味.在这种情况下,批量加载可能有帮助吗?每次跑步都在做什么?

干杯 -

  • 谢谢.您只需要将参数重置为新值(我刚刚测试了它并且它可以工作).您不需要将它们设置为null或任何其他内容.所以是的,保持连接打开,保持SqlCommands,并根据需要重置参数值. (2认同)

Stu*_*tLC 5

为了增加Simon的答案,在Sql 2005之前, Command.Prepare()将改进对ad-hoc查询的查询计划缓存(通常会编译SPROC).但是,在更新的Sql版本中,只要您的查询已参数化,也可以缓存也参数化的即席查询,从而减少对查询的需求Prepare().

下面是一个保留SqlParameters集合的示例,只更改那些变化的参数值的值,以防止重复创建参数(即保存参数对象的创建和收集):

using (var sqlConnection = new SqlConnection("connstring"))
 {
    sqlConnection.Open();
    using (var sqlCommand = new SqlCommand
       {
          Connection = sqlConnection,
          CommandText = "dbo.MyProc",
          CommandType = CommandType.StoredProcedure,
       })
    {
       // Once-off setup per connection
       // This parameter doesn't vary so is set just once
       sqlCommand.Parameters.Add("ConstantParam0", SqlDbType.Int).Value = 1234;
       // These parameters are defined once but set multiple times
       sqlCommand.Parameters.Add(new SqlParameter("VarParam1", SqlDbType.VarChar));
       sqlCommand.Parameters.Add(new SqlParameter("VarParam2", SqlDbType.DateTime));

       // Tight loop - performance critical
       foreach(var item in itemsToExec)
       {
         // No need to set ConstantParam0
         // Reuses variable parameters, by just mutating values
         sqlParameters["VarParam1"].Value = item.Param1Value; // Or sqlParameters[1].Value
         sqlParameters["VarParam2"].Value = item.Param2Date; // Or sqlParameters[2].Value
         sqlCommand.ExecuteNonQuery();
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 如果要插入大量行,并且与数据库的其他居民并发很重要,并且如果ACID事务边界不重要,您可以考虑批处理并提交更新,以便在表上保留少于5000个行锁一次,防止表锁升级.
  • 根据您的proc实际执行的工作,可能有机会并行化循环,例如使用TPL.显然连接和命令不是线程安全的每个任务都需要自己的连接和可重用命令 - Parallel.ForEachlocalInit重载是理想的.