使用SqlCommand.Prepare()有什么意义和好处?


14

我遇到了开发人员代码,其中在执行SQL查询之前广泛使用了SqlCommand.Prepare()(请参见MSDN)方法。我想知道这样做的好处是什么?

样品:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

我玩了一些,并进行了追踪。调用Prepare()方法后,Command的执行使Sql Server执行以下语句:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

之后,当获取参数的值并SqlCommand.ExecuteNonQuery()调用该参数时,将在Sql-Server上执行以下操作:

exec sp_execute 1,@id=20

对我来说,这看起来像该语句在Prepare()执行后立即得到了编译。我想知道这样做有什么好处?这是否意味着将其放入计划缓存中,并且可以在使用所需参数值执行最终查询后立即重新使用它?

我发现(并记录在另一个问题中)用SqlParameters执行的SqlCommands总是包装在sp_executesql过程调用中。这使Sql Server能够独立于参数值存储和重用计划。

关于这一点,我想知道该prepare()方法是无用的还是过时的,或者我是否在这里遗漏了什么?


我一直认为这与防止SQL注入有关,因此您要准备查询以接受某些类型的某些变量。这样,如果您不传递这些类型的变量,则查询将失败
Mark Sinkinson

尽管这样说,这种PHP解释对于.NET php.net/manual/en/pdo.prepared-statements.php
Mark Sinkinson

“是的,先生。司机,准备搬出去。” “你在准备什么?!你一直在准备!走吧!”
所有行业的乔恩,2016年

Answers:


19

与执行准备好的SQL批处理分开准备SQL批处理是有效的**鉴于执行计划的缓存方式,对于SQL Server来说毫无用处。仅在没有缓存的情况下,分开准备步骤(解析,绑定任何参数和编译)和执行才有意义。目的是通过重用现有计划来节省解析和编译所花费的时间,这就是内部计划缓存所要做的。但是并不是所有的RDBMS都执行这种级别的缓存(显然,这些功能的存在),因此由客户端代码来请求“缓存”计划,然后保存该缓存ID,然后重新使用它。这是不必要的客户端代码负担(程序员必须记住这样做并正确执行)。实际上,IDbCommand.Prepare()方法的MSDN页面指出:

服务器会自动缓存计划以在需要时进行重用;因此,无需在客户端应用程序中直接调用此方法。

应当指出的是,据我的测试显示(这符合你的问题显示什么),调用SqlCommand.Prepare() ,并没有做“准备” -只操作:调用sp_prepexec其准备执行SQL ; 它不会调用仅解析和编译的sp_prepare(并且不接受任何参数值,仅接受其名称和数据类型)。因此,调用不会有任何“预编译”的好处,SqlCommand.Prepare因为它会立即执行(假设目标是推迟执行)。但是SqlCommand.Prepare()即使调用sp_prepexec而不是,也会有一个有趣的潜在次要好处sp_prepare。请参阅说明(**)以了解详细信息。

我的测试表明,即使SqlCommand.Prepare()调用时(并且请注意,此调用在SQL Server上不会发出任何命令),后面的ExecuteNonQueryor ExecuteReader也会完全执行,并且如果是ExecuteReader,它会返回行。尽管如此,SQL Server Profiler仍显示该SqlCommand.Execute______()调用之后的第一个调用SqlCommand.Prepare()注册为“ Prepare SQL”事件,随后的.Execute___()调用注册为“ Exec Prepared SQL”事件。


测试码

它已被上传到PasteBin,网址为:http ://pastebin.com/Yc2Tfvup 。该代码创建一个.NET / C#控制台应用程序,该应用程序旨在在SQL Server Profiler跟踪运行(或扩展事件会话)时运行。它在每个步骤之后都会暂停,因此可以清楚地知道哪些语句具有特殊效果。


更新

找到了更多信息,还有一些避免致电的潜在原因 SqlCommand.Prepare()。我在测试中注意到的一件事,以及运行该测试控制台应用程序的任何人应该注意的一点:从来没有进行过明确的调用sp_unprepare。我进行了一些挖掘,并在MSDN论坛中找到了以下文章:

SqlCommand-准备还是不准备?

接受的答案包含大量信息,但要点是:

  • **准备实际上为您节省的时间主要是通过网络传输查询字符串所花费的时间。
  • 准备好的查询不能保证已保存缓存的计划,并且一旦到达服务器就不会比临时查询快。
  • RPC的(CommandType.StoredProcedure)从准备中得不到任何好处。
  • 在服务器上,准备好的句柄基本上是指向包含要执行的TSQL的内存映射的索引。
  • 该映射是基于每个连接存储的,无法使用交叉连接。
  • 客户端重新使用池中的连接时发送的重置清除了地图。

我还从SQLCAT小组找到了以下帖子,该帖子似乎相关,但可能只是特定于ODBC的问题,而SqlClient在处理SqlConnection时确实可以正确清理。很难说,因为SQLCAT帖子中没有提及任何其他有助于证明这是原因的测试,例如清除连接池等。

小心那些准备好的SQL语句


结论

鉴于:

  • 调用的唯一真正好处SqlCommand.Prepare()似乎是您不需要通过网络再次提交查询文本,
  • 调用sp_prepare并将sp_prepexec查询文本存储为连接内存(即SQL Server内存)的一部分

我建议不要拨打电话,SqlCommand.Prepare()因为唯一的潜在好处是节省了网络数据包,而缺点是占用了更多的服务器内存。尽管在大多数情况下消耗的内存量可能很小,但是网络带宽几乎不会成为问题,因为大多数数据库服务器直接以至少10兆位(如今更可能是100兆位或千兆位)的连接直接连接到应用程序服务器(有些甚至在同一盒子上;-)。与网络带宽相比,内存和内存几乎总是更稀缺。

我想,对于任何编写可以连接到多个数据库的软件的人来说,标准接口的便利性可能会改变成本/收益分析。但是即使在那种情况下,我也必须相信,提取一个“标准”数据库接口仍然足够容易,因为该接口是基本的对象,所以该接口允许使用不同的提供程序(因此它们之间也存在差异,例如不需要调用Prepare())。您可能已经在应用程序代码中进行过的面向对象的编程;-)。


感谢您的广泛回答。我测试了您的步骤,并从您的期望/结果中得到了两个偏差:在步骤4中,我得到了另一个结果。在步骤10中我得到了另一个plan_handle比在第2步
Magier

1
@Magier不确定第4步...也许您使用了不同的SET选项,或者两者之间的查询文本有所不同?即使是单个字符(可见或不可见)的差异也将是一个不同的计划。从此网页进行复制和粘贴可能无济于事,因此请确保将查询(单引号之间的所有内容)从复制sp_prepare到的调用中sp_executesql,以确保安全。是的,对于第10步,是的,我昨天做了更多测试,确实看到了某些场合使用不同的手柄sp_prepare,但是对于而言却从未如此sp_executesql。我将再测试一件事并更新答案。
所罗门·鲁茨基

1
@Magier实际上,我之前进行的测试涵盖了它,我认为该计划没有真正的重用,尤其是考虑到MSDN论坛上的帖子说它仅缓存SQL。我仍然很好奇它如何能够重用plan_handle,而sp_executesql不能或不能。但是无论如何,sp_prepare如果计划已从缓存中删除,解析时间仍然存在,因此我将删除答案的这一部分(有趣但无关紧要)。无论如何,该部分还是一个有趣的旁注,因为它没有解决您的直接问题。其他所有内容仍然适用。
所罗门·鲁茨基

1
这就是我一直在寻找的解释。
CSharpie '16

5

关于这一点,我想知道prepare()方法是没有用的还是过时的,或者我是否在这里遗漏了什么?

我要说的是Prepare方法在SqlCommand世界中的价值是有限的,并不是说它完全没有用。好处之一是Prepare是SqlCommand实现的IDbCommand接口的一部分。这允许相同的代码针对其他DBMS提供程序(可能需要Prepare)运行,而无需使用条件逻辑来确定是否应调用Prepare。

除此用例AFAIK之外,Prepare对于SQL Server的.NET Provider没有任何价值。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.