在Microsoft SQL Server如何获得查询/存储过程的查询执行计划?


当前回答

查询计划可以通过query_post_execution_showplan事件从扩展事件会话中获得。下面是一个XEvent会话示例:

/*
    Generated via "Query Detail Tracking" template.
*/
CREATE EVENT SESSION [GetExecutionPlan] ON SERVER 
ADD EVENT sqlserver.query_post_execution_showplan(
    ACTION(package0.event_sequence,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)),

/* Remove any of the following events (or include additional events) as desired. */
ADD EVENT sqlserver.error_reported(
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.module_end(SET collect_statement=(1)
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.rpc_completed(
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sp_statement_completed(SET collect_object_name=(1)
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_statement_completed(
    ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.database_id,sqlserver.plan_handle,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.session_id,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack)
    WHERE ([package0].[greater_than_uint64]([sqlserver].[database_id],(4)) AND [package0].[equal_boolean]([sqlserver].[is_system],(0)))) 
ADD TARGET package0.ring_buffer
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)
GO

创建会话后,(在SSMS中)转到对象资源管理器并深入到管理|扩展事件|会话。右键单击“GetExecutionPlan”会话并启动它。再次右键选择“观看实时数据”。

接下来,打开一个新的查询窗口并运行一个或多个查询。以下是AdventureWorks的一个例子:

USE AdventureWorks;
GO

SELECT p.Name AS ProductName, 
    NonDiscountSales = (OrderQty * UnitPrice),
    Discounts = ((OrderQty * UnitPrice) * UnitPriceDiscount)
FROM Production.Product AS p 
INNER JOIN Sales.SalesOrderDetail AS sod
    ON p.ProductID = sod.ProductID 
ORDER BY ProductName DESC;
GO

过一会儿,您应该在“GetExecutionPlan: Live Data”选项卡中看到一些结果。单击网格中的query_post_execution_showplan事件之一,然后单击网格下面的“Query Plan”选项卡。它应该看起来像这样:

编辑:XEvent代码和屏幕截图是从SQL/SSMS 2012 w/ SP2生成的。如果您使用的是SQL 2008/R2,则可以调整脚本以使其运行。但那个版本没有GUI,所以你需要提取showplan XML,保存为*。并在SSMS中打开。这是麻烦的。XEvents在SQL 2005或更早的版本中并不存在。所以,如果你没有使用SQL 2012或更高版本,我强烈建议你使用这里发布的其他答案之一。

其他回答

除了已经发布的综合答案之外,有时能够以编程方式访问执行计划以提取信息也是有用的。下面是示例代码。

DECLARE @TraceID INT
EXEC StartCapture @@SPID, @TraceID OUTPUT
EXEC sp_help 'sys.objects' /*<-- Call your stored proc of interest here.*/
EXEC StopCapture @TraceID

StartCapture定义

CREATE PROCEDURE StartCapture
@Spid INT,
@TraceID INT OUTPUT
AS
DECLARE @maxfilesize BIGINT = 5
DECLARE @filepath NVARCHAR(200) = N'C:\trace_' + LEFT(NEWID(),36)

EXEC sp_trace_create @TraceID OUTPUT, 0, @filepath, @maxfilesize, NULL 

exec sp_trace_setevent @TraceID, 122, 1, 1
exec sp_trace_setevent @TraceID, 122, 22, 1
exec sp_trace_setevent @TraceID, 122, 34, 1
exec sp_trace_setevent @TraceID, 122, 51, 1
exec sp_trace_setevent @TraceID, 122, 12, 1
-- filter for spid
EXEC sp_trace_setfilter @TraceID, 12, 0, 0, @Spid
-- start the trace
EXEC sp_trace_setstatus @TraceID, 1

停止捕获定义

CREATE  PROCEDURE StopCapture
@TraceID INT
AS
WITH  XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' as sql), 
      CTE
     as (SELECT CAST(TextData AS VARCHAR(MAX)) AS TextData,
                ObjectID,
                ObjectName,
                EventSequence,
                /*costs accumulate up the tree so the MAX should be the root*/
                MAX(EstimatedTotalSubtreeCost) AS EstimatedTotalSubtreeCost
         FROM   fn_trace_getinfo(@TraceID) fn
                CROSS APPLY fn_trace_gettable(CAST(value AS NVARCHAR(200)), 1)
                CROSS APPLY (SELECT CAST(TextData AS XML) AS xPlan) x
                CROSS APPLY (SELECT T.relop.value('@EstimatedTotalSubtreeCost',
                                            'float') AS EstimatedTotalSubtreeCost
                             FROM   xPlan.nodes('//sql:RelOp') T(relop)) ca
         WHERE  property = 2
                AND TextData IS NOT NULL
                AND ObjectName not in ( 'StopCapture', 'fn_trace_getinfo' )
         GROUP  BY CAST(TextData AS VARCHAR(MAX)),
                   ObjectID,
                   ObjectName,
                   EventSequence)
SELECT ObjectName,
       SUM(EstimatedTotalSubtreeCost) AS EstimatedTotalSubtreeCost
FROM   CTE
GROUP  BY ObjectID,
          ObjectName  

-- Stop the trace
EXEC sp_trace_setstatus @TraceID, 0
-- Close and delete the trace
EXEC sp_trace_setstatus @TraceID, 2
GO

有许多获得执行计划的方法,使用哪一种取决于你的情况。通常你可以使用SQL Server Management Studio来获得一个计划,但是如果由于某种原因你不能在SQL Server Management Studio中运行你的查询,那么你可能会发现通过SQL Server Profiler或检查计划缓存来获得一个计划是有帮助的。

方法1 -使用SQL Server Management Studio

SQL Server提供了一些简洁的功能,可以很容易地捕获执行计划,只需确保选中“包含实际执行计划”菜单项(在“查询”菜单下找到),并正常运行查询即可。

如果你试图获取存储过程中语句的执行计划,那么你应该执行存储过程,如下所示:

exec p_Example 42

当您的查询完成时,您应该在结果窗格中看到一个名为“执行计划”的额外选项卡。如果您运行了许多语句,那么您可能会在这个选项卡中看到许多计划。

从这里,您可以在SQL Server Management Studio中查看执行计划,或者右键单击计划并选择“另存执行计划为…”,将计划保存为XML格式的文件。

方法2 -使用SHOWPLAN选项

此方法与方法1非常相似(实际上,这是SQL Server Management Studio内部所做的),但是为了完整起见,或者如果您没有可用的SQL Server Management Studio,我将其包括在内。

在运行查询之前,运行以下语句之一。该语句必须是批处理中唯一的语句,即不能同时执行另一条语句:

SET SHOWPLAN_TEXT ON
SET SHOWPLAN_ALL ON
SET SHOWPLAN_XML ON
SET STATISTICS PROFILE ON
SET STATISTICS XML ON -- The is the recommended option to use

这些是连接选项,因此每个连接只需要运行一次。从这一点开始,所有运行的语句都将附带一个附加的结果集,其中包含所需格式的执行计划-只需像正常情况下那样运行查询就可以看到计划。

一旦你完成了,你可以用下面的语句关闭这个选项:

SET <<option>> OFF

执行计划格式的比较

除非您有强烈的偏好,否则我建议使用STATISTICS XML选项。该选项相当于SQL Server Management Studio中的“包含实际执行计划”选项,以最方便的格式提供最多的信息。

SHOWPLAN_TEXT - Displays a basic text based estimated execution plan, without executing the query SHOWPLAN_ALL - Displays a text based estimated execution plan with cost estimations, without executing the query SHOWPLAN_XML - Displays an XML based estimated execution plan with cost estimations, without executing the query. This is equivalent to the "Display Estimated Execution Plan..." option in SQL Server Management Studio. STATISTICS PROFILE - Executes the query and displays a text based actual execution plan. STATISTICS XML - Executes the query and displays an XML based actual execution plan. This is equivalent to the "Include Actual Execution Plan" option in SQL Server Management Studio.

方法3 -使用SQL Server分析器

如果你不能直接运行你的查询(或者你的查询在你直接执行它的时候运行得不慢——记住我们想要一个执行糟糕的查询计划),那么你可以使用SQL Server Profiler跟踪来捕获一个计划。其思想是在捕获“Showplan”事件之一的跟踪运行时运行查询。

Note that depending on load you can use this method on a production environment, however you should obviously use caution. The SQL Server profiling mechanisms are designed to minimize impact on the database but this doesn't mean that there won't be any performance impact. You may also have problems filtering and identifying the correct plan in your trace if your database is under heavy use. You should obviously check with your DBA to see if they are happy with you doing this on their precious database!

Open SQL Server Profiler and create a new trace connecting to the desired database against which you wish to record the trace. Under the "Events Selection" tab check "Show all events", check the "Performance" -> "Showplan XML" row and run the trace. While the trace is running, do whatever it is you need to do to get the slow running query to run. Wait for the query to complete and stop the trace. To save the trace right click on the plan xml in SQL Server Profiler and select "Extract event data..." to save the plan to file in XML format.

你得到的计划相当于SQL Server Management Studio中的“包含实际执行计划”选项。

方法4 -检查查询缓存

如果不能直接运行查询,也不能捕获分析器跟踪,那么仍然可以通过检查SQL查询计划缓存来获得估计的计划。

我们通过查询SQL Server dmv来检查计划缓存。下面是一个基本查询,它将列出所有缓存的查询计划(以xml格式)及其SQL文本。在大多数数据库中,您还需要添加额外的筛选子句,以便将结果筛选到您感兴趣的计划。

SELECT UseCounts, Cacheobjtype, Objtype, TEXT, query_plan
FROM sys.dm_exec_cached_plans 
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)

执行此查询并单击计划XML,在新窗口中打开计划-右键单击并选择“Save execution plan as…”,以XML格式将计划保存为文件。

注:

因为涉及到很多因素(从表和索引模式到存储的数据和表统计信息),所以应该始终尝试从您感兴趣的数据库(通常是遇到性能问题的数据库)获取执行计划。

无法捕获加密存储过程的执行计划。

“实际”和“估计”执行计划

实际执行计划是指SQL Server实际运行查询的计划,而预估执行计划则是指SQL Server在不执行查询的情况下会做什么。尽管逻辑上是等价的,但实际的执行计划更有用,因为它包含执行查询时实际发生的情况的额外细节和统计信息。这在诊断SQL server估计错误的问题(例如统计数据过期)时非常重要。

预估和实际执行计划重审

如何解释查询执行计划?

这是一个值得写一本(免费)书的主题。

参见:

执行计划基础知识 SHOWPLAN权限和Transact-SQL批处理 SQL Server 2008 -使用查询哈希和查询计划哈希 分析SQL Server计划缓存

除了前面所说的,还有一件重要的事情需要知道。

Query plans are often too complex to be represented by the built-in XML column type which has a limitation of 127 levels of nested elements. That is one of the reasons why sys.dm_exec_query_plan may return NULL or even throw an error in earlier MS SQL versions, so generally it's safer to use sys.dm_exec_text_query_plan instead. The latter also has a useful bonus feature of selecting a plan for a particular statement rather than the whole batch. Here's how you use it to view plans for currently running statements:

SELECT p.query_plan
FROM sys.dm_exec_requests AS r
OUTER APPLY sys.dm_exec_text_query_plan(
                r.plan_handle,
                r.statement_start_offset,
                r.statement_end_offset) AS p

然而,与XML列相比,结果表中的文本列不是很方便。为了能够单击要在单独的选项卡中作为图表打开的结果,而不必将其内容保存到文件中,您可以使用一个小技巧(记住您不能只使用CAST(…AS XML)),尽管这将只适用于单行:

SELECT Tag = 1, Parent = NULL, [ShowPlanXML!1!!XMLTEXT] = query_plan
FROM sys.dm_exec_text_query_plan(
                -- set these variables or copy values
                -- from the results of the above query
                @plan_handle,
                @statement_start_offset,
                @statement_end_offset)
FOR XML EXPLICIT

假设您正在使用Microsoft SQL Server Management Studio

对于估计的查询计划,可以按Ctrl + L或以下按钮。

对于“实际查询计划”,可以按“Ctrl +” M或以下按钮,然后执行查询。

对于实时查询计划,(仅在SSMS 2016中)在执行查询之前使用以下按钮。

在SQL Server Management Studio:

“Ctrl + M”将生成实际执行计划

“Ctrl + L”将生成估计执行计划

“Shift + Alt + S”为客户端统计

“Ctrl + Alt + P”用于SQL Server Profiler中的跟踪查询。