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

Mag*_*ier 16 sql-server execution-plan sql-server-2008-r2 prepared-statement plan-cache

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

样本:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();
Run Code Online (Sandbox Code Playgroud)

我玩了一点并进行了追踪。调用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
Run Code Online (Sandbox Code Playgroud)

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

exec sp_execute 1,@id=20
Run Code Online (Sandbox Code Playgroud)

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

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

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

Sol*_*zky 22

准备 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上的任何命令)中,ExecuteNonQueryExecuteReader遵循完全执行,如果是的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(). 我在测试中注意到的一件事,以及运行该测试控制台应用程序的任何人都应该注意的事情:从来没有明确调用 made 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())的“标准”数据库接口仍然足够容易,因为这样做是基本的 Object您可能已经在您的应用程序代码中进行了定向编程;-)。


Dan*_*man 5

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

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

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