Login or Sign Up to become a member!
LessThanDot Sit Logo

LessThanDot

Data Management

Less Than Dot is a community of passionate IT professionals and enthusiasts dedicated to sharing technical knowledge, experience, and assistance. Inside you will find reference materials, interesting technical discussions, and expert tips and commentary. Once you register for an account you will have immediate access to the forums and all past articles and commentaries.

LTD Social Sitings

Lessthandot twitter Lessthandot Linkedin Lessthandot friendfeed Lessthandot facebook Lessthandot rss

Note: Watch for social icons on posts by your favorite authors to follow their postings on these and other social sites.

Your profile

    Search

    XML Feeds

    Google Ads

    « Database Designing With Performance In MindThe OLE DB Source and the Oracle Date Literal »
    comments

    It was a little remark from Bob Beauchemin (B|T) during the Belgian SQL Server Days that started me writing this post.

    Showing cached Execution Plans
    In this post I’m going to use information from the following Dynamic Management Views and Functions to show some information about the cached Execution Plans of the queries used in this post:

    • sys.dm_exec_cached_plans shows types, usage, size… of the Execution Plans;
    • sys.dm_exec_sql_text shows the actual code of the query;
    • sys.dm_exec_query_plan is used to get the XML Execution Plan itself.

    In fact I use following query to get the results:

    1. SELECT cp.objtype, cp.cacheobjtype, cp.usecounts, cp.size_in_bytes, st.[text], qp.query_plan
    2.     FROM sys.dm_exec_cached_plans cp
    3.     CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    4.     CROSS APPLY sys.dm_exec_query_plan (cp.plan_handle)qp;

    The result set of this query looks like this:

    The result set can contain thousands of rows, depending on the uptime of the server, the number of queries and their corresponding execution plans, the available buffer memory…

    IF using 2 code blocks
    So let’s create a simple Stored Procedure that executes against the AdventureWorks2008R2 database:

    1. USE AdventureWorks2008R2;
    2. GO
    3.  
    4. CREATE PROCEDURE TwoPlans
    5.     @IfParameter int
    6. AS
    7.     SET NOCOUNT ON;
    8.     IF @IfParameter = 1
    9.         SELECT a.City, COUNT(bea.AddressID) EmployeeCount
    10.         FROM Person.BusinessEntityAddress bea
    11.             INNER JOIN Person.Address a
    12.                 ON bea.AddressID = a.AddressID
    13.         GROUP BY a.City
    14.         ORDER BY a.City
    15.     ELSE
    16.         SELECT DATEPART(yyyy,OrderDate) AS N'Year'
    17.             ,SUM(TotalDue) AS N'Total Order Amount'
    18.         FROM Sales.SalesOrderHeader
    19.         GROUP BY DATEPART(yyyy,OrderDate)
    20.         ORDER BY DATEPART(yyyy,OrderDate);
    21. GO

    The results of the query aren’t important what is important is to see what happens in the Procedure Cache. To be able to see what’s happening we are going to empty the procedure cache:

    WARNING: executing the following code deletes all cached plans from the Procedure Cache. All Execution Plans need to be recompiled. This can result in a slow or unresponsive server.
    DON’T EXECUTE THE FOLLOWING CODE ON A PRODUCTION SERVER!!!

    1. DBCC FREEPROCCACHE;
    2. GO

    When you execute our first query again:

    1. SELECT cp.objtype, cp.cacheobjtype, cp.usecounts, cp.size_in_bytes, st.[text], qp.query_plan
    2.     FROM sys.dm_exec_cached_plans cp
    3.     CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    4.     CROSS APPLY sys.dm_exec_query_plan (cp.plan_handle)qp;

    You can see in the result set that only the Execution Plan from the above query is stored in the Procedure Cache.

    Now execute the stored procedure:

    1. EXEC TwoPlans 1;
    2. GO

    Execute the query against the Procedure Cache again:

    1. SELECT cp.objtype, cp.cacheobjtype, cp.usecounts, cp.size_in_bytes, st.[text], qp.query_plan
    2.     FROM sys.dm_exec_cached_plans cp
    3.     CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    4.     CROSS APPLY sys.dm_exec_query_plan (cp.plan_handle)qp;

    The result set shows us the execution of the above query with a usecount of 2 and the Execution Plan of our Stored Procedure:

    Now click the XML link in the query_plan column. A new tab will open in SSMS showing you the XML Execution Plan:

    You can now read the XML plan and you will find both the statements in the query plan:

    1. ShowPlanXML xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan" Version="1.1" Build="10.50.2500.0">
    2.   <BatchSequence>
    3.     <Batch>
    4.       <Statements>
    5.         <StmtSimple StatementText="CREATE procedure TwoPlans&#xD;&#xA;&#x9;@IfParameter int&#xD;&#xA;as&#xD;&#xA;&#x9;SET NOCOUNT ON;&#xD;&#xA;" StatementId="1" StatementCompId="3" StatementType="SET ON/OFF" />
    6.         <StmtCond StatementText="&#x9;If @IfParameter = 1&#xD;&#xA;&#x9;" StatementId="2" StatementCompId="4" StatementType="COND">
    7.           <Condition />
    8.           <Then>
    9.             <Statements>
    10.               <StmtSimple StatementText="&#x9;SELECT a.City, COUNT(bea.AddressID) EmployeeCount&#xD;&#xA;&#x9;&#x9;FROM Person.BusinessEntityAddress bea &#xD;&#xA;&#x9;&#x9;&#x9;INNER JOIN Person.Address a&#xD;&#xA;&#x9;&#x9;&#x9;&#x9;ON bea.AddressID = a.AddressID&#xD;&#xA;&#x9;&#x9;GROUP BY a.City&#xD;&#xA;&#x9;&#x9;ORDER BY a.City&#xD;&#xA;" StatementId="3" StatementCompId="5" StatementType="SELECT" StatementSubTreeCost="0.604708" StatementEstRows="574.696" StatementOptmLevel="FULL" QueryHash="0x03E92D79FC617C86" QueryPlanHash="0xED13B89036D1A5E6" StatementOptmEarlyAbortReason="GoodEnoughPlanFound">
    11.                 <StatementSetOptions QUOTED_IDENTIFIER="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" NUMERIC_ROUNDABORT="false" />
    12.                 <QueryPlan CachedPlanSize="32" CompileTime="7" CompileCPU="7" CompileMemory="344">
    13.              </StmtSimple>
    14.             </Statements>
    15.           </Then>
    16.           <Else>
    17.             <Statements>
    18.               <StmtSimple StatementText="&#x9;Else&#xD;&#xA;&#x9;&#x9;SELECT DATEPART(yyyy,OrderDate) AS N'Year'&#xD;&#xA;&#x9;&#x9;&#x9;,SUM(TotalDue) AS N'Total Order Amount'&#xD;&#xA;&#x9;&#x9;FROM Sales.SalesOrderHeader&#xD;&#xA;&#x9;&#x9;GROUP BY DATEPART(yyyy,OrderDate)&#xD;&#xA;&#x9;&#x9;ORDER BY DATEPART(yyyy,OrderDate);&#xD;" StatementId="4" StatementCompId="8" StatementType="SELECT" StatementSubTreeCost="0.780189" StatementEstRows="4" StatementOptmLevel="FULL" QueryHash="0xA73814A2D4649412" QueryPlanHash="0x7A5BDE1102728DAA" StatementOptmEarlyAbortReason="GoodEnoughPlanFound">
    19.             </Statements>
    20.           </Else>
    21.         </StmtCond>
    22.       </Statements>
    23.     </Batch>
    24.   </BatchSequence>
    25. </ShowPlanXML>

    An easier trick to see the Graphical Execution Plans is to open SQL Sentry Plan Explorer and copy the XML into the Plan XML tab:

    After doing this SQL Sentry Plan Explorer will give you all the details about the Execution Plan and you’ll see in the Plan Diagram that SQL Server created an Execution Plan for both the queries altough only the first one was used:

    IF calling 2 Stored Procedures
    Now let’s create 2 Stored Procedures that each will execute 1 part of the code from the previous query:

    1. CREATE PROCEDURE EmpCntCity
    2. AS
    3.     SET NOCOUNT ON;
    4.     SELECT a.City, COUNT(bea.AddressID) EmployeeCount
    5.     FROM Person.BusinessEntityAddress bea
    6.         INNER JOIN Person.Address a
    7.             ON bea.AddressID = a.AddressID
    8.     GROUP BY a.City
    9.     ORDER BY a.City;
    10. GO
    11.  
    12. CREATE PROCEDURE OrderAmountYear
    13. AS
    14.     SET NOCOUNT ON;
    15.     SELECT DATEPART(yyyy,OrderDate) AS N'Year'
    16.         ,SUM(TotalDue) AS N'Total Order Amount'
    17.     FROM Sales.SalesOrderHeader
    18.     GROUP BY DATEPART(yyyy,OrderDate)
    19.     ORDER BY DATEPART(yyyy,OrderDate);
    20. GO

    Next we create a Stored Procedure that will contain the IF…ELSE logic and based on the input parameter will execute one of the above Stored Procedures:

    1. CREATE PROCEDURE TwoProcs
    2.     @IfParameter int
    3. AS
    4.     SET NOCOUNT ON;
    5.     IF @IfParameter = 1
    6.         EXEC EmpCntCity
    7.     ELSE
    8.         EXEC OrderAmountYear;
    9. GO

    Now we can call our Stored Procedure and execute 1 of the Stored Procedures:

    1. EXEC TwoProcs 1;
    2. GO

    Let’s again query the Procedure Cache:

    1. SELECT cp.objtype, cp.cacheobjtype, cp.usecounts, cp.size_in_bytes, st.[text], qp.query_plan
    2.     FROM sys.dm_exec_cached_plans cp
    3.     CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
    4.     CROSS APPLY sys.dm_exec_query_plan (cp.plan_handle)qp;

    And have a look at the result:

    Now we see that executing the second Stored Procedure also created 2 Executions Plans: 1 for the Stored Procedure “TwoProcs” and one for the Stored Procedure “EmpCntCity” that was effectively executed. But we see that there was no Execution Plan created for the Stored Procedure “OrderAmountYear”. And this Execution Plan was more complex than the Execution Plan of our “TwoProcs” Stored Procedure. We can also see that the sum of the sizes of the 2 Execution Plans (65536 + 16384 = 81920) is still smaller than the Execution Plan of the “TwoPlans” Stored Procedure (98304).

    Conclusion
    Avoid Stored Procedures that contain complete code blocks encapsulated in IF...ELSE or CASE blocks. It will result in SQL Server creating Execution Plans for all possibilities, consuming more Buffer Cache (memory) and in the end slow down the execution of the code.
    As a bonus, troubleshooting the individual Stored Procedures will be much easier and there is a bigger chance that you can reuse the Stored Procedures.

    About the Author

    User bio imageAxel started his IT career more than a decade ago. After a few months he was the accidental DBA for the SQL Server 6.5 running the Service Desk application. It didn't took long or Axel started to focus on SQL Server and after another year he became the full-time DBA in a large Belgian datacenter managing the SQL Servers for the hosted customers.It's more than five years now since he switched to consulting and teaching SQL Server. Axel is a Microsoft Certified DBA and Trainer.
    Social SitingsTwitterLinkedInLTD RSS Feed
    1668 views
    InstapaperVote on HN

    No feedback yet

    Leave a comment


    Your email address will not be revealed on this site.

    To mislead the spambots.

    Your URL will be displayed.
    (Line breaks become <br />)
    (Name, email & website)
    (Allow users to contact you through a message form (your email will not be revealed.)