How do I obtain a Query Execution Plan in SQL Server?

前端 未结 12 1931
不知归路
不知归路 2020-11-21 04:17

In Microsoft SQL Server how can I get a query execution plan for a query / stored procedure?

相关标签:
12条回答
  • 2020-11-21 04:58

    Explaining execution plan can be very detailed and takes up quite a reading time, but in summary if you use 'explain' before the query it should give you a lot of info including which parts were executed first and so. if you wanna read a bit more details about this, I compiled a small blog about this which points you as well to the right refs. https://medium.com/swlh/jetbrains-datagrip-explain-plan-ac406772c470

    0 讨论(0)
  • 2020-11-21 05:01

    Here's one important thing to know in addition to everything said before.

    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
    

    The text column in the resulting table is however not very handy compared to an XML column. To be able to click on the result to be opened in a separate tab as a diagram, without having to save its contents to a file, you can use a little trick (remember you cannot just use CAST(... AS XML)), although this will only work for a single row:

    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
    
    0 讨论(0)
  • 2020-11-21 05:02

    There are a number of methods of obtaining an execution plan, which one to use will depend on your circumstances. Usually you can use SQL Server Management Studio to get a plan, however if for some reason you can't run your query in SQL Server Management Studio then you might find it helpful to be able to obtain a plan via SQL Server Profiler or by inspecting the plan cache.

    Method 1 - Using SQL Server Management Studio

    SQL Server comes with a couple of neat features that make it very easy to capture an execution plan, simply make sure that the "Include Actual Execution Plan" menu item (found under the "Query" menu) is ticked and run your query as normal.

    Include Action Execution Plan menu item

    If you are trying to obtain the execution plan for statements in a stored procedure then you should execute the stored procedure, like so:

    exec p_Example 42
    

    When your query completes you should see an extra tab entitled "Execution plan" appear in the results pane. If you ran many statements then you may see many plans displayed in this tab.

    Screenshot of an Execution Plan

    From here you can inspect the execution plan in SQL Server Management Studio, or right click on the plan and select "Save Execution Plan As ..." to save the plan to a file in XML format.

    Method 2 - Using SHOWPLAN options

    This method is very similar to method 1 (in fact this is what SQL Server Management Studio does internally), however I have included it for completeness or if you don't have SQL Server Management Studio available.

    Before you run your query, run one of the following statements. The statement must be the only statement in the batch, i.e. you cannot execute another statement at the same time:

    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
    

    These are connection options and so you only need to run this once per connection. From this point on all statements run will be acompanied by an additional resultset containing your execution plan in the desired format - simply run your query as you normally would to see the plan.

    Once you are done you can turn this option off with the following statement:

    SET <<option>> OFF
    

    Comparison of execution plan formats

    Unless you have a strong preference my recommendation is to use the STATISTICS XML option. This option is equivalent to the "Include Actual Execution Plan" option in SQL Server Management Studio and supplies the most information in the most convenient format.

    • 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.

    Method 3 - Using SQL Server Profiler

    If you can't run your query directly (or your query doesn't run slowly when you execute it directly - remember we want a plan of the query performing badly), then you can capture a plan using a SQL Server Profiler trace. The idea is to run your query while a trace that is capturing one of the "Showplan" events is running.

    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!

    1. Open SQL Server Profiler and create a new trace connecting to the desired database against which you wish to record the trace.
    2. Under the "Events Selection" tab check "Show all events", check the "Performance" -> "Showplan XML" row and run the trace.
    3. While the trace is running, do whatever it is you need to do to get the slow running query to run.
    4. Wait for the query to complete and stop the trace.
    5. 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.

    The plan you get is equivalent to the "Include Actual Execution Plan" option in SQL Server Management Studio.

    Method 4 - Inspecting the query cache

    If you can't run your query directly and you also can't capture a profiler trace then you can still obtain an estimated plan by inspecting the SQL query plan cache.

    We inspect the plan cache by querying SQL Server DMVs. The following is a basic query which will list all cached query plans (as xml) along with their SQL text. On most database you will also need to add additional filtering clauses to filter the results down to just the plans you are interested in.

    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)
    

    Execute this query and click on the plan XML to open up the plan in a new window - right click and select "Save execution plan as..." to save the plan to file in XML format.

    Notes:

    Because there are so many factors involved (ranging from the table and index schema down to the data stored and the table statistics) you should always try to obtain an execution plan from the database you are interested in (normally the one that is experiencing a performance problem).

    You can't capture an execution plan for encrypted stored procedures.

    "actual" vs "estimated" execution plans

    An actual execution plan is one where SQL Server actually runs the query, whereas an estimated execution plan SQL Server works out what it would do without executing the query. Although logically equivalent, an actual execution plan is much more useful as it contains additional details and statistics about what actually happened when executing the query. This is essential when diagnosing problems where SQL Servers estimations are off (such as when statistics are out of date).

    • Estimated and Actual execution plan revisited

    How do I interpret a query execution plan?

    This is a topic worthy enough for a (free) book in its own right.

    See also:

    • Execution Plan Basics
    • SHOWPLAN Permission and Transact-SQL Batches
    • SQL Server 2008 – Using Query Hashes and Query Plan Hashes
    • Analyzing the SQL Server Plan Cache
    0 讨论(0)
  • 2020-11-21 05:03

    Query plans can be obtained from an Extended Events session via the query_post_execution_showplan event. Here's a sample XEvent session:

    /*
        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
    

    After you create the session, (in SSMS) go to the Object Explorer and delve down into Management | Extended Events | Sessions. Right-click the "GetExecutionPlan" session and start it. Right-click it again and select "Watch Live Data".

    Next, open a new query window and run one or more queries. Here's one for 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
    

    After a moment or two, you should see some results in the "GetExecutionPlan: Live Data" tab. Click one of the query_post_execution_showplan events in the grid, and then click the "Query Plan" tab below the grid. It should look similar to this:

    EDIT: The XEvent code and the screen shot were generated from SQL/SSMS 2012 w/ SP2. If you're using SQL 2008/R2, you might be able to tweak the script to make it run. But that version doesn't have a GUI, so you'd have to extract the showplan XML, save it as a *.sqlplan file and open it in SSMS. That's cumbersome. XEvents didn't exist in SQL 2005 or earlier. So, if you're not on SQL 2012 or later, I'd strongly suggest one of the other answers posted here.

    0 讨论(0)
  • 2020-11-21 05:05

    In addition to the comprehensive answer already posted sometimes it is useful to be able to access the execution plan programatically to extract information. Example code for this is below.

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

    Example StartCapture Definition

    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
    

    Example StopCapture Definition

    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
    
    0 讨论(0)
  • 2020-11-21 05:06

    Assuming you're using Microsoft SQL Server Management Studio

    • For Estimated Query Plan you can press Ctrl + L or the following button.

    • For Actual Query Plan, you can press Ctrl + M or the following button before executing query.

    • For Live Query Plan, (only in SSMS 2016) use the following button before executing query.

    0 讨论(0)
提交回复
热议问题