《Microsoft Sql server 2008 Internals》读书笔记--第九章Plan Caching and Recompilation(10)
《Microsoft Sql server 2008 Internals》读书笔记订阅地址:
http://www.cnblogs.com/downmoon/category/230397.html/rss
《Microsoft Sql server 2008 Internals》索引目录:
《Microsoft Sql server 2008 Internal》读书笔记--目录索引
上文主要介绍计划缓存的时机和计划缓存冲突。本文将继续关注计划指南(Plan Guide)和优化指示(Optimization Hints),由于篇幅所限,计划指南分两部分,第一部分包括:优化提示、计划指南的意图、计划指南的类型。第二部分包括:计划指南的管理、计划指南的考虑因素、计划指南的有效性、从计划缓存中冻结计划。本文是第一部分。
■优化提示(Optimization Hints)
◆ReCompile Recompile提示强制SQL Server重新编译一个计划。这在一个批处理中仅仅一个语句时特别有用。SQL Server编译T-SQL批处理为一个Unit,为批处理中的每个语句决定执行计划,直到整个批处理被编译之前,它不会执行任何语句。这意味着如果批处理中包含变量声明和赋值,但赋值在编译阶段并没有实际发生。当下列批处理被优化时,SQL Server并没有为变量定义一个值:
USE Northwind2;
DECLARE @custID nchar(10);
SET @custID = 'LAZYK';
SELECT * FROM Orders WHERE CustomerID = @custID;
这个针对select语句的计划将表明SQL Server正在扫描完整的聚集索引。因为在优化期间,SQL Server并不知道什么值将被搜索,不能使用索引统计的直方图得到一个好的行数的估算。如果我们用常量LAZYK取代变量,SQL Server能决定仅仅有数行将被修饰,将有选择地使用CustomID上的非聚集索引。Recompile Hint此时非常有用,因为它告诉优化器在语句被执行前准备一个针对单个select语句的新计划。在Set选项后语句执行:
USE Northwind2;
DECLARE @custID nchar(10);
SET @custID = 'LAZYK';
SELECT * FROM Orders WHERE CustomerID = @custID
OPTION (RECOMPILE);
注意:一个变量并不同于一个参数,即使他们用同样的方式被写。因为过程仅在被执行时编译。而SQL Server总是使用一个定义的参数值,当前一个已编译计划使用不同的参数时问题出现了。然而,对于一个局部变量,当使用变量的语句被编译时,这个值是未知的,直到Recompile提示被使用。
◆Optimize For Optimize For提示告知优化器似乎一个特别的值被用作一个变量或参数。执行使用一个真实的值,记住,Optimize For提示并不强制SQL Server被重编译,它仅仅指示SQL Server假定一个变量或参数有一个特别的值,此时,SQL Server已经决定查询需要优化。
◆Keep Plan Keep Plan提示放宽一个查询的重编译阈值,特别查询访问临时表时。我们在前面的章节中提到过,当访问一个临时表的查询而表的变化达到6个时,查询被重编译。如果使用KeepPlan,则临时表的重编译阈值等同于固定表。
◆Keepfixed Plan Keepfixed plan提示因为优化冲突而阻止所有的重编译。定义该提示,查询将仅仅在强制重编译或基础表的架构改变时重编译。
◆Parameterization Parameterization提示重定义了一个数据库的Parameterization选项。如果数据库被设置为Parameterization Forced,使用Parameterization提示的独立查询能够避免它,并且适合严格的条件列表才会参数化。或者,如果数据库设置为Parameterization Simple,独立的查询能够被在一步一步的基础上被参数化。
◆Use Plan use Plan提示,在第八章中讨论过,作为一种强制SQL Server使用一个可能定义其他提示的计划的方式。定义的计划必须是XML格式,并能被从一个通过使用SET SHOWPLAN_XML ON选项的合适计划的查询中获取。因为USE Plan提示在查询提示中包括一个复杂的XML文档,它们是使用计划向导的最佳实践。
■计划指南的意图
尽管在大多数情况下推荐你允许查询优化器来决定每个查询的最佳计划,但有时查询优化器并不能得出最稳定性计划,你可能需要一个方式使用Hint获取合理的性能。
计划指南,在SQL Server2005中新增的功能,提出一种解决方案,可以给你一个机制仅增加Hint到查询而不需改变查询本身。首要地,一个计划向导告诉优化器,如果它试图优化一个特殊格式的查询,它应该增加一个Hint到查询。SQL Server支持三种类型的计划指南:SQL,Object和templete。
计划指南在标准版。企业版、评估版、和开发版中可用。如果从一个支持的SQL Server版本中detach一个包含计划指南的数据库,然后再附加玛个不支持的版本,如workgroup版本或Express版本中。SQL Server不使用任何计划指南。包含计划指南信息的元数据仍然可用。
■计划指南的类型
可以使用sp_Create_plan_guide存储过程创建三种类型的计划。关于sp_create_plan_guide的用法,参看MSDN:
http://msdn.microsoft.com/zh-cn/library/ms179880.aspx
通用的格式如下:(注意顺序不能变化)
sp_Create_plan_guide 'plan_guide_name','statement_text','type_Of_plan_guide','object_name_or_batch_text','parameter_list','hints'
◆对象计划指南
一个object类型的计划指南,标示你对在出现在SQL Server对象的上下文的T-SQL感兴趣,可能是创建计划指南的数据库中的存储过程、用户自定义函数、或触发器。举例,我们假定一个存储过程Sales.GetOrderByCountry,使用country作为一个参数。在检查错误和其他有效性后,它返回一个结果集。更进一步,假定我们的测试已经决定给定的参数值"US"以最佳计划。这里使用Optimize for提示,以下示例创建一个计划指南,它与在基于应用程序的存储过程的上下文中所执行的查询匹配,并将 OPTIMIZE FOR
提示应用于该查询。
下面是此存储过程:
IF OBJECT_ID(N'Sales.GetSalesOrderByCountry', N'P') IS NOT NULL
DROP PROCEDURE Sales.GetSalesOrderByCountry;
GO
CREATE PROCEDURE Sales.GetSalesOrderByCountry
(@Country_region nvarchar(60))
AS
BEGIN
SELECT *
FROM Sales.SalesOrderHeader AS h
INNER JOIN Sales.Customer AS c ON h.CustomerID = c.CustomerID
INNER JOIN Sales.SalesTerritory AS t
ON c.TerritoryID = t.TerritoryID
WHERE t.CountryRegionCode = @Country_region;
END
下面是为此存储过程中的查询所创建的计划指南:
EXEC sp_create_plan_guide
@name = N'Guide1',
@stmt = N'SELECT *
FROM Sales.SalesOrderHeader AS h
INNER JOIN Sales.Customer AS c
ON h.CustomerID = c.CustomerID
INNER JOIN Sales.SalesTerritory AS t
ON c.TerritoryID = t.TerritoryID
WHERE t.CountryRegionCode = @Country_region',
@type = N'OBJECT',
@module_or_batch = N'Sales.GetSalesOrderByCountry',
@params = NULL,
@hints = N'OPTION (OPTIMIZE FOR (@Country_region = N''US''))';
一旦该计划在AdventureWorks2008数据库中被创建,每次存储过程"Sales.GetSalesOrderByCountry"被编译时,计划里揭示的语句如果实际参数值为'US'时被优化,这个存储过程中的其他语句不受此计划影响,如果定义的查询发生在Sales.GetSalesOrderByCountry外,计划指南不会调用。
◆SQL计划指南
一个SQL类型的计划指南显示你关注特别的SQL语句,如一个独立的语句或一个特别的批处理。被送到SQL Server的T-SQL语句被通过CLR对象或扩展存储过程,或EXEC调用的其他动态SQL语句结构,而作为批处理运行。为了在计划指南中使用它们,它们的类型被设置为SQL。
对于一个单独的SQL语句,@module_or_beach参数应该被设置为NULL,以便SQL Server假定批处理和语句有同样的值,
如果是一个大的批处理,完整的批处理文本需要在@module_or_beach参数中定义。如果一个批处理被定义为一个SQL指南、这个文本需要和应用程序中的完全一样。这在前面章节已以讨论。这个规则并不与adhoc查询计划重用一样严格,但它们很接近。
这里有一个例子,指示SQL Server在一个特殊的查询被作为一个单独的查询时使用仅仅一个CPU(没有并行):
EXEC sp_create_plan_guide
@name = N'plan_SalesOrderHeader_DOP1',
@stmt = N'SELECT TOP 10 *
FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC',
@type = N'SQL',
@module_or_batch = NULL,
@params = NULL,
@hints = N'OPTION (MAXDOP 1)';
一旦该查询指南被创建,每次该定义的语句在批处理中被碰到时,它有一个计划仅仅使用一个CPU。如果发生在一个在的批处理时,计划指南不调用。
◆模板计划指南
一个Templete类型的计划指南,仅仅使用Parameterization Forced或Parameterization Simple提示去重定义数据库的Parameterization数据库设置。有一个存储过程sp_get_query_template用来生成模板。sp_get_query_template的用法,请看MSDN:http://msdn.microsoft.com/zh-cn/library/ms186908.aspx
来看一个使用模板指南和强制参数化的例子,首先清除你的过程缓存,然后执行这两个查询:
DBCC FREEPROCCACHE;
GO
SELECT * FROM AdventureWorks.Sales.SalesOrderHeader AS h
INNER JOIN AdventureWorks.Sales.SalesOrderDetail AS d
ON h.SalesOrderID = d.SalesOrderID
WHERE h.SalesOrderID = 45639;
GO
SELECT * FROM AdventureWorks.Sales.SalesOrderHeader AS h
INNER JOIN AdventureWorks.Sales.SalesOrderDetail AS d
ON h.SalesOrderID = d.SalesOrderID
WHERE h.SalesOrderID = 45640;
用前面介绍的一个视图sp_cacheobjects查看,也可以使用sys.syscacheobjects(http://msdn.microsoft.com/zh-cn/library/ms187815.aspx):
SELECT objtype, dbid, usecounts, sql
FROM sp_cacheobjects
WHERE cacheobjtype = 'Compiled Plan';
SELECT objtype, dbid, usecounts, sql
FROM sys.syscacheobjects
WHERE cacheobjtype = 'Compiled Plan';
此时因为查询被看作复杂化,SQL Server并没有自动参数化。查询结果为null
为了创建一个强制这种类型的语句参数化。我们首先需要调用存储过程sp_get_query_template(http://msdn.microsoft.com/zh-cn/library/ms186908.aspx),传递两上变量作为输出参数。一个参数包含查询的参数化版本,另一个包含参数列表和数据类型。我们试着强制参数化(注意与上面查询比较):
DECLARE @sample_statement nvarchar(max);
DECLARE @paramlist nvarchar(max);
EXEC sp_get_query_template
N'SELECT * FROM AdventureWorks.Sales.SalesOrderHeader AS h
INNER JOIN AdventureWorks.Sales.SalesOrderDetail AS d
ON h.SalesOrderID = d.SalesOrderID
WHERE h.SalesOrderID = 45639;',
@sample_statement OUTPUT,
@paramlist OUTPUT
SELECT @paramlist as parameters, @sample_statement as statement
EXEC sp_create_plan_guide @name = N'Template_Plan',
@stmt = @sample_statement,
@type = N'TEMPLATE',
@module_or_batch = NULL,
@params = @paramlist,
@hints = N'OPTION(PARAMETERIZATION FORCED)';
DBCC FREEPROCCACHE;
GO
SELECT * FROM AdventureWorks.Sales.SalesOrderHeader AS h
INNER JOIN AdventureWorks.Sales.SalesOrderDetail AS d
ON h.SalesOrderID = d.SalesOrderID
WHERE h.SalesOrderID = 45639;
GO
SELECT * FROM AdventureWorks.Sales.SalesOrderHeader AS h
INNER JOIN AdventureWorks.Sales.SalesOrderDetail AS d
ON h.SalesOrderID = d.SalesOrderID
WHERE h.SalesOrderID = 45640;
GO
下文将继续关注计划指南的第二部分:计划指南的管理、计划指南的考虑因素、计划指南的有效性、从计划缓存中冻结计划。