【转】如何知道执行计划使用了那些统计信息
出处:http://blogs.msdn.com/b/apgcdsd/archive/2012/11/28/10372470.aspx
最近被一个客户问到了这样一个有趣的问题——执行计划在生成过程中使用了那些统计信息呢?针对这个问题,客户也做了一些研究,并给了我下面两篇博文作为切入点。
1. How to Find the Statistics Used to Compile an Execution Plan(http://sqlblog.com/blogs/paul_white/archive/2011/09/21/how-to-find-the-statistics-used-to-compile-an-execution-plan.aspx)
2. Statistics used in a cached query plan (http://blogfabiano.com/2012/07/03/statistics-used-in-a-cached-query-plan/)
在查看了这两篇博文后,我把其中的奥秘摘出来跟大家分享一下。
第一篇博文介绍了两个Trace Flag, 9204和9292。这两个Trace Flag是这样解释的:
9204 – 打印出被加载的统计信息
9292 – 打印出从元数据中得到的统计信息的头信息
当然,要看到这些信息,我们还需要打开Trace Flag 3604才能让这些显示出来。以下面的脚本为例,
USE Northwind
GO
DBCC FREEPROCCACHE()
GO
DBCC TRACEON(3604, 9292, 9204)
GO
SELECT Employees.FirstName, COUNT(Orders.OrderID)
FROM Orders
INNER JOIN Employees
ON Orders.EmployeeID = Employees.EmployeeID
WHERE Employees.FirstName = 'Steven'
GROUP BY Employees.FirstName
GO
其返回结果为:
Stats header loaded: DbName: Northwind, ObjName: Orders, IndexId: 4, ColumnName: EmployeeID, EmptyTable: FALSE
Stats loaded: DbName: Northwind, ObjName: Orders, IndexId: 4, ColumnName: EmployeeID, EmptyTable: FALSE
Stats header loaded: DbName: Northwind, ObjName: Orders, IndexId: 5, ColumnName: EmployeeID, EmptyTable: FALSE
Stats header loaded: DbName: Northwind, ObjName: Employees, IndexId: 1, ColumnName: EmployeeID, EmptyTable: FALSE
Stats loaded: DbName: Northwind, ObjName: Employees, IndexId: 1, ColumnName: EmployeeID, EmptyTable: FALSE
Stats header loaded: DbName: Northwind, ObjName: Employees, IndexId: 4, ColumnName: FirstName, EmptyTable: FALSE
Stats loaded: DbName: Northwind, ObjName: Employees, IndexId: 4, ColumnName: FirstName, EmptyTable: FALSE
FirstName
---------- -----------
Steven 42
从这个SELECT语句中,我们不难看出查询结果应该是员工Steven的First Name和这个员工有的所有的订单Order的数量。表Employees和Orders是用过EmployeeID来联系起来的。所以,在Employees.EmployeeID和Orders.EmployeeID上建立的索引上面的统计信息会被用来产生执行计划。当然除了他们,还应该有在字段Employees.FirstName上的统计信息,因为这个字段上没有索引存在,所以系统会在这里自动生成一个统计信息。
需要注意的是,Trace Flag 9204和9292 只会在这个SELECT语句第一次被编译的时候打印出这些统计信息的记录。如果执行计划已经存在于缓存中,那么这些统计信息的记录则不会被打印出来。只有在用DBCC FREEPROCCACHE(plan_handle)清理了这条语句的缓存计划后,再次执行的时候才会再次被打印出来。
第二篇博文介绍了另一个Trace Flag,8666。这个Trace Flag是这样解释的:
8666 – 将Showplan的详细信息打印出来
这个Trace Flag会将一些关于统计信息的内部信息显示出来。例如,
USE Northwind
GO
DBCC FREEPROCCACHE()
GO
DBCC TRACEON(8666)
GO
SELECT Employees.FirstName, COUNT(Orders.OrderID)
FROM Orders
INNER JOIN Employees
ON Orders.EmployeeID = Employees.EmployeeID
WHERE Employees.FirstName = 'Steven'
GROUP BY Employees.FirstName
GO
当你查看XML计划的时候,你可能会看到类似下面的信息。在第一行,我们可以看到有一个XML的标签叫做InternalInfo。再往下,我们会看到两个Recompile的标签,每一个标签对应这个SELECT语句中用到的一张表。在每一个Recompile标签下,又有两个ModTrackingInfo的标签,在这里统计信息被显示出来。在这个例子中,我们看到了四个统计信息的显示,可是实际被执行计划用到的只有三个(从前面介绍的方法可以得知)。也就是说,这里例举出的统计信息并不全都会被使用到,而是会被检查并判定是否被用于产生执行计划。
<InternalInfoLockClassNoHint="0"LockClassIntLockHint="0"LockClassRCIsoHint="0">
<OptmInfo>
<FieldFieldName="m_iOptStage"FieldValue="1" />
<FieldFieldName="m_cOptTask"FieldValue="382" />
<FieldFieldName="m_ullAlgPmoSize"FieldValue="56" />
<FieldFieldName="m_ullOptPmoSize"FieldValue="256" />
<FieldFieldName="m_ullAlgTotalTime"FieldValue="514" />
<FieldFieldName="m_ullAlgNetTime"FieldValue="514" />
<FieldFieldName="m_ullOptTotalTime"FieldValue="51803" />
<FieldFieldName="m_ullOptNetTime"FieldValue="37964" />
</OptmInfo>
<EnvColl>
<Recompile>
<FieldFieldName="wszDb"FieldValue="Northwind" />
<FieldFieldName="wszSchema"FieldValue="dbo" />
<FieldFieldName="wszTable"FieldValue="Employees" />
<FieldFieldName="m_cRowCount"FieldValue="9" />
<FieldFieldName="ullThreshold"FieldValue="500" />
<FieldFieldName="wszReason"FieldValue="small table" />
<FieldFieldName="m_fMissingStatsRecompile"FieldValue="0" />
<FieldFieldName="m_fVisibleOutsideXact"FieldValue="1" />
<FieldFieldName="m_dbId"FieldValue="7" />
<FieldFieldName="m_mdObjectId"FieldValue="1977058079" />
<FieldFieldName="m_mdBaseIndexId"FieldValue="1" />
<FieldFieldName="m_cRowsetId"FieldValue="1" />
<FieldFieldName="m_verStats"FieldValue="153" />
<FieldFieldName="m_fAfterTriggerDelta"FieldValue="0" />
<FieldFieldName="m_fInsteadOfDeltaPopulate"FieldValue="0" />
<FieldFieldName="m_fInsteadOfDeltaInsideTrg"FieldValue="0" />
<FieldFieldName="m_fIsSbQueue"FieldValue="0" />
<ModTrackingInfo>
<FieldFieldName="wszStatName"FieldValue="PK_Employees" />
<FieldFieldName="wszColName"FieldValue="EmployeeID" />
<FieldFieldName="m_cCols"FieldValue="1" />
<FieldFieldName="m_idIS"FieldValue="1" />
<FieldFieldName="m_ullSnapShotModCtr"FieldValue="27" />
<FieldFieldName="m_ullRowCount"FieldValue="9" />
<FieldFieldName="ullThreshold"FieldValue="500" />
<FieldFieldName="wszReason"FieldValue="small table" />
</ModTrackingInfo>
<ModTrackingInfo>
<FieldFieldName="wszStatName"FieldValue="_WA_Sys_00000003_75D7831F" />
<FieldFieldName="wszColName"FieldValue="FirstName" />
<FieldFieldName="m_cCols"FieldValue="1" />
<FieldFieldName="m_idIS"FieldValue="4" />
<FieldFieldName="m_ullSnapShotModCtr"FieldValue="27" />
<FieldFieldName="m_ullRowCount"FieldValue="9" />
<FieldFieldName="ullThreshold"FieldValue="500" />
<FieldFieldName="wszReason"FieldValue="small table" />
</ModTrackingInfo>
</Recompile>
<Recompile>
<FieldFieldName="wszDb"FieldValue="Northwind" />
<FieldFieldName="wszSchema"FieldValue="dbo" />
<FieldFieldName="wszTable"FieldValue="Orders" />
<FieldFieldName="m_cRowCount"FieldValue="830" />
<FieldFieldName="ullThreshold"FieldValue="666" />
<FieldFieldName="wszReason"FieldValue="heuristic" />
<FieldFieldName="m_fMissingStatsRecompile"FieldValue="0" />
<FieldFieldName="m_fVisibleOutsideXact"FieldValue="1" />
<FieldFieldName="m_dbId"FieldValue="7" />
<FieldFieldName="m_mdObjectId"FieldValue="21575115" />
<FieldFieldName="m_mdBaseIndexId"FieldValue="1" />
<FieldFieldName="m_cRowsetId"FieldValue="1" />
<FieldFieldName="m_verStats"FieldValue="152" />
<FieldFieldName="m_fAfterTriggerDelta"FieldValue="0" />
<FieldFieldName="m_fInsteadOfDeltaPopulate"FieldValue="0" />
<FieldFieldName="m_fInsteadOfDeltaInsideTrg"FieldValue="0" />
<FieldFieldName="m_fIsSbQueue"FieldValue="0" />
<ModTrackingInfo>
<FieldFieldName="wszStatName"FieldValue="EmployeeID" />
<FieldFieldName="wszColName"FieldValue="EmployeeID" />
<FieldFieldName="m_cCols"FieldValue="1" />
<FieldFieldName="m_idIS"FieldValue="4" />
<FieldFieldName="m_ullSnapShotModCtr"FieldValue="2490" />
<FieldFieldName="m_ullRowCount"FieldValue="830" />
<FieldFieldName="ullThreshold"FieldValue="666" />
<FieldFieldName="wszReason"FieldValue="heuristic" />
</ModTrackingInfo>
<ModTrackingInfo>
<FieldFieldName="wszStatName"FieldValue="EmployeesOrders" />
<FieldFieldName="wszColName"FieldValue="EmployeeID" />
<FieldFieldName="m_cCols"FieldValue="1" />
<FieldFieldName="m_idIS"FieldValue="5" />
<FieldFieldName="m_ullSnapShotModCtr"FieldValue="2490" />
<FieldFieldName="m_ullRowCount"FieldValue="830" />
<FieldFieldName="ullThreshold"FieldValue="666" />
<FieldFieldName="wszReason"FieldValue="heuristic" />
</ModTrackingInfo>
</Recompile>
</EnvColl>
</InternalInfo>
另外,因为这些信息存在XML计划中,所以我们还可以通过下面的语句来直接把我们需要的信息截取出来。不过需要注意的是,运行下面的语句有时候会造成访问越界,所以请慎用!
WITH XMLNAMESPACES('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as p)
SELECT qt.text AS SQLCommand,
qp.query_plan,
StatsUsed.XMLCol.value('@FieldValue','NVarChar(500)') AS StatsName
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) qt
CROSS APPLY query_plan.nodes('//p:Field[@FieldName="wszStatName"]')StatsUsed(XMLCol)
WHERE qt.text LIKE '%Steven%'
AND qt.text NOT LIKE '%sys.%'
综上所述,如果想要得知执行计划在生成过程中使用了那些统计信息,比较安全的做法是用Trace Flag 9204和9292。