时间:2021-07-04 09:24:21 | 栏目:MsSql | 点击:次
有两种不同的方式来创建参数化查询。第一个方式是让查询优化器自动地参数化你的查询。另一个方式是通过以一个特定方式来编写你的T-SQL代码,并将它传递给sp_executesql系统存储过程,从而编程一个参数化查询。这篇文章的后面部分将介绍这个方法。
参数化查询的关键是查询优化器将创建一个可以重用的缓存计划。通过自动地或编程使用参数化查询,SQL Server可以优化类似T-SQL语句的处理。这个优化消除了对使用高贵资源为这些类似T-SQL语句的每一次执行创建一个缓存计划的需求。而且通过创建一个可重用计划,SQL Server还减少了存放过程缓存中类似的执行计划所需的内存使用。
现在让我们看看使得SQL Server创建参数化查询的不同方式。
参数化查询是怎样自动创建的?
微软编写查询优化器代码的人竭尽全力地优化SQL Server处理你的T-SQL命令的方式。我想这是查询优化器名称的由来。这些尽量减少资源和最大限度地提高查询优化器执行性能的方法之一是查看一个T-SQL语句并确定它们是否可以被参数化。要了解这是如何工作的,让我们看看下面的T-SQL语句:
SELECT * FROM AdventureWorks.Sales.SalesOrderHeader WHERE SalesOrderID = 56000; GO |
如果你使用下面的SELECT语句来查看一个只包含用于上面语句的缓存计划的、干净的缓冲池,那么你会看到查询优化器将T-SQL查询重写为一个参数化T-SQL语句:
SELECT stats.execution_count AS cnt, p.size_in_bytes AS [size], [sql].[text] AS [plan_text] FROM sys.dm_exec_cached_plans p OUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sql JOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handle; GO |
cnt size plan_text
--- ------- --------------------------------------------------------------
1 49152 (@1 int)SELECT * FROM [AdventureWorks].[Sales].[SalesOrderHeader]
WHERE [SalesOrderID]=@1
如果你看看上面输出中的plan_text字段,你会看到它不像原来的T-SQL文本。如前所述,查询优化器将这个查询重新编写为一个参数化T-SQL语句。在这里,你可以看到它现在有一个数据类型为(int)的变量(@1),它在之前的SELECT语句中被定义的。另外在plan_text的末尾, 值“56000”被替换为变量@1。既然这个T-SQL语句被重写了,而且被存储为一个缓存计划,那么如果未来一个T-SQL命令和它大致相同,只有SalesOrderID字段被赋的值不同的话,它就可以被用于重用。让我们在动作中看看它。
如果我在我的机器上运行下面的命令:DBCC FREEPROCCACHE; GO SELECT * FROM AdventureWorks.Sales.SalesOrderHeader WHERE SalesOrderID = 56000; GO SELECT * FROM AdventureWorks.Sales.SalesOrderHeader WHERE SalesOrderID = 56001; GO SELECT stats.execution_count AS cnt, p.size_in_bytes AS [size], [sql].[text] AS [plan_text] FROM sys.dm_exec_cached_plans p OUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sql JOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handle; GO 我从最后的SELECT语句得到下面的输出,(注意,输出被重新格式化以便它更易读): cnt size plan_text --- -------- -------------------------------------------------------------- 2 49152 (@1 int)SELECT * FROM AdventureWorks].[Sales].[SalesOrderHeader] WHERE [SalesOrderID]=@1 |
数据库配置选项PARAMETERIZATION可以影响T-SQL语句怎样被自动地参数化。对于这个选项有两种不同的设置,SIMPLE和FORCED。当PARAMETERIZATION设置被设置为SIMPLE时,只有简单的T-SQL语句才会被参数化。要介绍这个,看下下面的命令:
SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = 56000 |
当你设置数据库选项为使用FORCE PARAMETERIZATION时,查询优化器试图参数化所有的查询,而不仅仅是简单的查询。你可能会认为这很好。但是在某些情况下,当数据库设置PARAMETERIZATION为FORCED时,查询优化器将选择不是很理想的查询计划。当数据库设置PARAMETER为FORCED时,它改变查询中的字面常量。这可能导致当查询中涉及计算字段时索引和索引视图不被选中参与到执行计划中,从而导致一个无效的计划。FORCED PARAMETERIZATION选项可能是改进具有大量类似的、传递过来的参数稍有不同的查询的数据库性能的一个很好的解决方案。一个在线销售应用程序,它的客户对你的产品执行大量的类似搜索, 产品值不同,这可能是一个能够受益于FORCED PARAMETERIZATION的很好的应用程序类型。
不是所有的查询从句都会被参数化。例如查询的TOP、TABLESAMPLE、 HAVING、GROUP BY、ORDER BY、OUTPUT...INTO或FOR XML从句不会被参数化。使用sp_execute_sql来参数化你的T-SQL
你不需要依赖于数据库的PARAMETERIZATION选项来使得查询优化器参数化一个查询。你可以参数化你自己的查询。你通过重新编写你的T-SQL语句并使用“sp_executesql”系统存储过程执行重写的语句来实现。正如已经看到的,上面包括一个“JOIN”从句的SELECT语句在数据库的PARAMETERIZATION设置为SIMPLE时没有被自动参数化。让我重新编写这个查询以便查询优化器将创建一个可重用的参数化查询执行计划。
为了说明,让我们看两个类似的、不会被自动参数化的T-SQL语句,并创建两个不同的缓存执行计划。然后我将重新编写这两个查询使得它们都使用相同的缓存参数化执行计划。
让我们看看这个代码:
DBCC FREEPROCCACHE GO SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = 56000 GO SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = 56001 GO SELECT stats.execution_count AS cnt, p.size_in_bytes AS [size], LEFT([sql].[text], 200) AS [plan_text] FROM sys.dm_exec_cached_plans p OUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sql JOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handle; GO |
cnt size plan_text --- ----------- ------------------------------------------------------------------------------- 1 49152 SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = 56001 1 49152 SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = 56000 |
DBCC FREEPROCCACHE; GO EXEC sp_executesql N'SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = @SalesOrderID', N'@SalesOrderID INT', @SalesOrderID = 56000; GO EXEC sp_executesql N'SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = @SalesOrderID', N'@SalesOrderID INT', @SalesOrderID = 56001; GO SELECT stats.execution_count AS exec_count, p.size_in_bytes AS [size], [sql].[text] AS [plan_text] FROM sys.dm_exec_cached_plans p OUTER APPLY sys.dm_exec_sql_text (p.plan_handle) sql JOIN sys.dm_exec_query_stats stats ON stats.plan_handle = p.plan_handle; GO |
现在当我运行上面的代码时,我从DMV SELECT语句得到下面的输出(注意,输出被重新格式化了,以便它更易读):
cnt size plan_text --- ----------- ----------------------------------------------------------------------------------------- 2 49152 (@SalesOrderID INT)SELECT SUM(LineTotal) AS LineTotal FROM AdventureWorks.Sales.SalesOrderHeader H JOIN AdventureWorks.Sales.SalesOrderDetail D ON D.SalesOrderID = H.SalesOrderID WHERE H.SalesOrderID = @SalesOrderID |
使用参数化查询来节省资源和优化性能
在语句可以被执行之前,每个T-SQL语句都需要被评估,而且需要建立一个执行计划。创建执行计划会占用宝贵的CPU资源。当执行计划被创建后,它使用内存空间将它存储在过程缓存中。降低CPU和内存使用的一个方法是利用参数化查询。尽管数据库可以被设置为对所有查询FORCE参数化,但是这不总是最好的选择。通过了解你的哪些T-SQL语句可以被参数化然后使用sp_executesql存储过程,你可以帮助SQL Server节省资源并优化你的查询的性能。