关于sql:如何获取查询执行计划?

How do I obtain a Query Execution Plan?

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


有许多方法可以获得一个执行计划,使用哪一个将取决于您的情况。通常,您可以使用SQL Server Management Studio获取计划,但是,如果由于某种原因无法在SQL Server Management Studio中运行查询,那么您可能会发现通过SQL Server事件探查器或通过检查计划缓存获取计划很有帮助。好的。方法1-使用SQL Server Management Studio

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

Include Action Execution Plan menu item好的。

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

1
EXEC p_Example 42

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

Screenshot of an Execution Plan好的。

从这里可以检查SQL Server Management Studio中的执行计划,或者右键单击该计划并选择"将执行计划另存为…"将该计划保存为XML格式的文件。好的。方法2-使用Showplan选项

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

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

1
2
3
4
5
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

这些是连接选项,因此每个连接只需要运行一次。从这一点开始,所有运行的语句都将由一个额外的结果集进行比较,该结果集以所需的格式包含您的执行计划——只需像通常看到计划那样运行查询。好的。

完成后,可以使用以下语句关闭此选项:好的。

1
SET <<option>> OFF

执行计划格式比较

除非你有强烈的偏好,我的建议是使用STATISTICS XML选项。此选项相当于SQL Server Management Studio中的"包括实际执行计划"选项,并以最方便的格式提供最多的信息。好的。

  • SHOWPLAN_TEXT—显示基于文本的基本估计执行计划,而不执行查询
  • SHOWPLAN_ALL—显示基于文本的估计执行计划和成本估计,而不执行查询
  • SHOWPLAN_XML—显示一个基于XML的估计执行计划,其中包含成本估计,而不执行查询。这相当于SQL Server Management Studio中的"显示估计执行计划…"选项。
  • STATISTICS PROFILE—执行查询并显示基于文本的实际执行计划。
  • STATISTICS XML—执行查询并显示基于XML的实际执行计划。这相当于SQL Server Management Studio中的"包括实际执行计划"选项。

方法3-使用SQL Server事件探查器

如果不能直接运行查询(或者直接执行查询时查询运行速度不慢-请记住,我们希望查询计划执行得不好),则可以使用SQL Server事件探查器跟踪捕获计划。其思想是在捕获"showplan"事件之一的跟踪正在运行时运行查询。好的。

请注意,根据负载的不同,您可以在生产环境中使用此方法,但显然应该谨慎使用。SQL Server分析机制旨在将对数据库的影响降至最低,但这并不意味着不会有任何性能影响。如果数据库大量使用,那么在跟踪中筛选和识别正确的计划也可能会出现问题。显然,您应该与您的DBA一起检查,看看他们是否愿意您在他们宝贵的数据库上这样做!好的。

  • 打开SQL Server事件探查器并创建一个新的跟踪,该跟踪连接到您希望记录跟踪的所需数据库。
  • 在"事件选择"选项卡下,选中"显示所有事件",检查"性能"->"ShowPlanXML"行并运行跟踪。
  • 当跟踪正在运行时,您需要做什么就做什么,以使运行缓慢的查询运行起来。
  • 等待查询完成并停止跟踪。
  • 要保存跟踪,请右键单击SQL Server事件探查器中的计划XML,然后选择"提取事件数据…"将计划保存为XML格式的文件。
  • 您得到的计划相当于SQL Server Management Studio中的"包括实际执行计划"选项。好的。方法4-检查查询缓存

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

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

    1
    2
    3
    4
    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以在新窗口中打开计划-右键单击并选择"将执行计划另存为…"以XML格式将计划保存到文件中。好的。笔记:

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

    无法捕获加密存储过程的执行计划。好的。"实际"与"估计"执行计划

    实际的执行计划是指SQL Server实际运行查询的计划,而估计的执行计划SQL Server在不执行查询的情况下完成它的工作。尽管在逻辑上是等效的,但实际的执行计划更有用,因为它包含有关执行查询时实际发生的事情的附加详细信息和统计信息。在诊断SQL Server估计值已关闭的问题(例如统计数据已过期)时,这一点非常重要。好的。

    • 重新评估估计和实际执行计划

    如何解释查询执行计划?

    这是一个有足够价值的主题(免费)一本书本身。好的。参见:

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

    好啊。


    除了已经发布的综合答案之外,有时还可以通过编程访问执行计划来提取信息。下面是这方面的示例代码。

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

    示例StartCapture定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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

    示例StopCapture定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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


    Assuming you're using Microsoft SQL Server Management Studio

    • 对于估计的查询计划,可以按ctrl+l或以下按钮。

    enter image description here

    • 对于实际的查询计划,可以按Ctrl键+m或执行查询前的以下按钮。

    enter image description here

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

    enter image description here


    除了前面答案中描述的方法之外,您还可以使用一个免费的执行计划查看器和查询优化工具apexsql plan(我最近碰到过这个工具)。

    您可以安装apexsql计划并将其集成到SQL Server Management Studio中,这样就可以直接从ssms查看执行计划。

    查看apexsql计划中的估计执行计划

  • 单击SSMS中的"新建查询"按钮,并将查询文本粘贴到"查询文本"窗口中。右键单击并从上下文菜单中选择"显示估计执行计划"选项。
  • New Query button in SSMS

  • 执行计划图将显示在"结果"部分的"执行计划"选项卡中。然后右键单击执行计划,并在上下文菜单中选择"open in apexsql plan"选项。
  • Execution Plan

  • 估计的执行计划将在apexsql计划中打开,可以对其进行分析以进行查询优化。
  • Estimated execution plan

    查看apexsql计划中的实际执行计划

    要查看查询的实际执行计划,请从前面提到的第2步继续,但现在,一旦显示了估计计划,请单击apexsql计划中主功能区栏中的"实际"按钮。

    click the

    单击"实际"按钮后,将显示实际执行计划以及成本参数的详细预览以及其他执行计划数据。

    Actual execution plan

    通过以下链接可以找到有关查看执行计划的更多信息。


    我最喜欢的获取和深入分析查询执行计划的工具是SQL Sentry Plan Explorer。与SSMS相比,它对执行计划的详细分析和可视化更为用户友好、方便和全面。

    下面是一个示例屏幕截图,您可以了解该工具提供的功能:

    SQL Sentry Plan Explorer window screen shot

    它只是工具中可用的视图之一。请注意,在应用程序窗口底部有一组选项卡,可以让您获得不同类型的执行计划表示和有用的附加信息。

    此外,我还没有注意到它的免费版有任何限制,这些限制会阻止您每天使用它,或者最终迫使您购买Pro版。所以,如果你喜欢坚持使用免费版本,没有什么能阻止你这样做。

    更新:(感谢Martin Smith)Plan Explorer现在是免费的!有关详细信息,请参阅http://www.sqlsentry.com/products/plan-explorer/sql-server-query-view。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /*
        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"会话并启动它。再次右键单击并选择"观看实时数据"。

    接下来,打开一个新的查询窗口并运行一个或多个查询。以下是冒险作品:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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"选项卡中看到一些结果。单击网格中的一个查询"执行后"显示计划事件,然后单击网格下面的"查询计划"选项卡。它应该看起来像这样:

    enter image description here

    编辑:XEvent代码和屏幕截图是从SQL/SSMS 2012 w/sp2生成的。如果您使用的是SQL 2008/R2,则可以调整脚本使其运行。但该版本没有GUI,因此您必须提取ShowPlanXML,将其保存为*.sqlplan文件并在SSMS中打开。那太麻烦了。XEvents在SQL 2005或更早版本中不存在。因此,如果您不在SQL 2012或更高版本上,我强烈建议您选择这里发布的其他答案之一。


    从SQL Server 2016+开始,引入了查询存储功能来监视性能。它提供了对查询计划选择和性能的深入了解。它不是跟踪或扩展事件的完全替代,但是随着它从一个版本发展到另一个版本,我们可能会在未来的SQL Server版本中得到一个功能完整的查询存储。查询存储的主流

  • SQL Server现有组件通过使用查询存储管理器与查询存储进行交互。
  • 查询存储管理器确定应使用哪个存储,然后将执行传递给该存储(计划或运行时状态或查询等待状态)
    • 计划存储-保存执行计划信息
    • 运行时统计存储-持久化执行统计信息
    • 查询等待统计信息存储-保持等待统计信息。
  • 计划、运行时状态和等待存储将查询存储用作SQL Server的扩展。
  • enter image description here

  • 启用查询存储:查询存储在服务器上的数据库级别工作。

    • 默认情况下,新数据库的查询存储不活动。
    • 不能为master或tempdb数据库启用查询存储。
    • 有效二甲基亚砜

      sys.database_query_store_options (Transact-SQL)

  • 在查询存储中收集信息:我们使用查询存储DMV(数据管理视图)从三个存储中收集所有可用信息。

    • 查询计划存储:保存执行计划信息,并负责捕获与查询编译相关的所有信息。


      sys.query_store_query (Transact-SQL)
      sys.query_store_plan (Transact-SQL)
      sys.query_store_query_text (Transact-SQL)

    • 运行时状态存储:保存执行统计信息,它可能是最频繁更新的存储。这些统计数据表示查询执行数据。


      sys.query_store_runtime_stats (Transact-SQL)

    • 查询等待状态存储:保存和捕获等待统计信息。


      sys.query_store_wait_stats (Transact-SQL)

  • 注意:查询等待状态存储仅在SQL Server 2017中可用+


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

    查询计划通常过于复杂,无法用内置XML列类型表示,该列类型的嵌套元素限制为127级。这就是sys.dm eu exec_query_plan可能返回NULL,甚至在早期的MS SQL版本中抛出错误的原因之一,因此通常使用sys.dm_exec_text_query_plan更安全。后者还具有一个有用的额外功能,即为特定语句而不是整批语句选择计划。以下是如何使用它查看当前运行语句的计划:

    1
    2
    3
    4
    5
    6
    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列相比,结果表中的文本列并不十分方便。为了能够单击要在单独的选项卡中作为图表打开的结果,而不必将其内容保存到文件中,您可以使用一个小技巧(记住,您不能只使用EDOCX1[1]),尽管这只适用于一行:

    1
    2
    3
    4
    5
    6
    7
    8
    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

    与SQL Server Management Studio(已经解释过)一样,也可以像这里解释的那样使用数据报。

  • Right-click an SQL statement, and select Explain plan.
  • In the Output pane, click Plan.
  • By default, you see the tree representation of the query. To see the
    query plan, click the Show Visualization icon, or press
    Ctrl+Shift+Alt+U