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


当前回答

有许多获得执行计划的方法,使用哪一种取决于你的情况。通常你可以使用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计划缓存

其他回答

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

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

查询计划可以通过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或更高版本,我强烈建议你使用这里发布的其他答案之一。

解释执行计划可能会非常详细,并占用大量阅读时间,但总的来说,如果你在查询之前使用'explain',它应该会给你很多信息,包括哪些部分首先执行等等。 如果你想了解更多关于这方面的细节,我写了一篇关于这方面的小博客,它也会给你指明正确的裁判。 https://medium.com/swlh/jetbrains-datagrip-explain-plan-ac406772c470

与SQL Server Management Studio(已经解释过了)一样,Datagrip也可以在这里解释。

右键单击SQL语句,并选择Explain plan。 在Output窗格中,单击Plan。 默认情况下,您将看到查询的树表示形式。去看 查询计划时,单击“显示可视化”图标,或按 Ctrl + Shift + Alt + U

预估执行计划

估计的执行计划是由Optimizer在不运行SQL查询的情况下生成的。

为了获得估计的执行计划,您需要在执行查询之前启用SHOWPLAN_ALL设置。

SET SHOWPLAN_ALL ON

现在,当执行以下SQL查询时:

SELECT p.id
FROM post p
WHERE EXISTS (
  SELECT 1
  FROM post_comment pc
  WHERE
    pc.post_id = p.id AND
    pc.review = 'Bingo'
)
ORDER BY p.title
OFFSET 20 ROWS
FETCH NEXT 10 ROWS ONLY

SQL Server将生成以下估计的执行计划:

| NodeId | Parent | LogicalOp            | EstimateRows | EstimateIO  | EstimateCPU | AvgRowSize | TotalSubtreeCost | EstimateExecutions |
|--------|--------|----------------------|--------------|-------------|-------------|------------|------------------|--------------------|
| 1      | 0      | NULL                 | 10           | NULL        | NULL        | NULL       | 0.03374284       | NULL               |
| 2      | 1      | Top                  | 10           | 0           | 3.00E-06    | 15         | 0.03374284       | 1                  |
| 4      | 2      | Distinct Sort        | 30           | 0.01126126  | 0.000504114 | 146        | 0.03373984       | 1                  |
| 5      | 4      | Inner Join           | 46.698       | 0           | 0.00017974  | 146        | 0.02197446       | 1                  |
| 6      | 5      | Clustered Index Scan | 43           | 0.004606482 | 0.0007543   | 31         | 0.005360782      | 1                  |
| 7      | 5      | Clustered Index Seek | 1            | 0.003125    | 0.0001581   | 146        | 0.0161733        | 43                 |

在运行我们想要获得估计执行计划的查询之后,您需要禁用SHOWPLAN_ALL,否则,当前数据库会话将只生成估计的执行计划,而不是执行提供的SQL查询。

SET SHOWPLAN_ALL OFF

SQL Server Management Studio估计计划

在SQL Server Management Studio应用程序中,您可以通过按CTRL+L快捷键轻松获得任何SQL查询的估计执行计划。

实际执行计划

实际的SQL执行计划由Optimizer在运行SQL查询时生成。如果数据库表统计信息是准确的,那么实际计划应该与估计的计划没有太大差异。

要获得SQL Server上的实际执行计划,需要启用STATISTICS IO, TIME, PROFILE设置,如下SQL命令所示:

SET STATISTICS IO, TIME, PROFILE ON

现在,当运行前面的查询时,SQL Server将生成以下执行计划:

| Rows | Executes | NodeId | Parent | LogicalOp            | EstimateRows | EstimateIO  | EstimateCPU | AvgRowSize | TotalSubtreeCost |
|------|----------|--------|--------|----------------------|--------------|-------------|-------------|------------|------------------|
| 10   | 1        | 1      | 0      | NULL                 | 10           | NULL        | NULL        | NULL       | 0.03338978       |
| 10   | 1        | 2      | 1      | Top                  | 1.00E+01     | 0           | 3.00E-06    | 15         | 0.03338978       |
| 30   | 1        | 4      | 2      | Distinct Sort        | 30           | 0.01126126  | 0.000478783 | 146        | 0.03338679       |
| 41   | 1        | 5      | 4      | Inner Join           | 44.362       | 0           | 0.00017138  | 146        | 0.02164674       |
| 41   | 1        | 6      | 5      | Clustered Index Scan | 41           | 0.004606482 | 0.0007521   | 31         | 0.005358581      |
| 41   | 41       | 7      | 5      | Clustered Index Seek | 1            | 0.003125    | 0.0001581   | 146        | 0.0158571        |
 
SQL Server parse and compile time:
   CPU time = 8 ms, elapsed time = 8 ms.
 
(10 row(s) affected)
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'post'. Scan count 0, logical reads 116, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'post_comment'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 
(6 row(s) affected)
 
SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 1 ms.

运行查询后,我们感兴趣的是获得实际的执行计划,你需要禁用STATISTICS IO, TIME, PROFILE ON设置,如下所示:

SET STATISTICS IO, TIME, PROFILE OFF

SQL Server Management Studio实际计划

在SQL Server Management Studio应用程序中,您可以通过按CTRL+M快捷键轻松获得任何SQL查询的估计执行计划。