{"id":2075,"date":"2015-08-14T00:00:00","date_gmt":"2015-08-14T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/basic-sql-server-performance-troubleshooting-for-developers\/"},"modified":"2022-05-06T17:37:20","modified_gmt":"2022-05-06T17:37:20","slug":"basic-sql-server-performance-troubleshooting-for-developers","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/performance-sql-server\/basic-sql-server-performance-troubleshooting-for-developers\/","title":{"rendered":"Basic SQL Server Performance Troubleshooting For Developers"},"content":{"rendered":"<div class=\"article-content\">\n<h1>Uncovering Indexing Problems with Execution Plans<\/h1>\n<p>Often, developers will use an Object-Relational Mapping (ORM) tool, such as Entity Framework or nHibernate, to auto-generate SQL code that SQL Server then executes to return the required data. Usually, this approach works well, and the productivity benefits for the developer are obvious. However, it can also result in SQL that returns too much data and has inefficiencies that causes SQL Server to perform far more work than necessary to return the required data. In such cases, the developer needs easy access to SQL Server tools that provide insight into how exactly SQL Server chose to execute their ORM-generated query, and possible causes of poor performance, without dragging them too deeply into SQL Server internals.<\/p>\n<p>One obvious candidate is the <strong>SQL Server execution plan<\/strong>. This article serves as a jumpstart into execution plans for the developer who occasionally has to troubleshoot SQL queries that currently don&#8217;t perform to specification, and needs deeper insight into the SQL their data access layer generated, and how SQL Server chose to execute it.<\/p>\n<p>It describes what an execution plan is and why it is important, and then looks at some examples of how execution plans can reveal query performance problems relating to <strong>m<\/strong><strong>issing o<\/strong><strong>r<\/strong><strong> poorly-designed <\/strong>indexes, meaning that SQL Server has available inefficient data access paths and performs more IO than necessary to return the required data.<\/p>\n<p>Of course, execution plans can reveal a range of other potential query problems beyond index issues, and subsequent articles will drill deeper into specific problems in SQL, such as ad-hoc queries, data type mismatches, function misuse and so on, which cause excessive reads, plan cache bloat and other problems.<\/p>\n<h1>What is an execution plan?<\/h1>\n<p>SQL Server executes each SQL query according to a set of instructions called an <strong>execution plan<\/strong>, which is devised by a component of the relational engine called the SQL Server Query Optimizer. An execution plan specifies the set of <strong>operators<\/strong> required to implement physically each logical step of the submitted query, and the order in which those operations will occur.<\/p>\n<p>Based on information it has about the available data access paths (table, indexes) and statistical information about the data in those tables, the optimizer makes judgements about how much data will be returned from each source, whether indexes are available that will reduce the cost of data retrieval, and so on. Based on these judgements, it selects a plan that it estimates will have the lowest overall execution cost.<\/p>\n<p>Figure 1 shows the graphical execution plan for a simple query against the <code>AdventureWorks2012<\/code> database, as viewed in SQL Server Management Studio.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68908\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/1.png\" alt=\"1\" width=\"620\" height=\"364\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 1<\/p>\n<p>SQL Server has a large set of specialized operators from which to choose, in order to implement each task required in gathering the data and then manipulating it into the correct form to return to the user. Each operator in a plan performs one specific task. The plan in Figure 1 has six <strong>operators<\/strong>, connected by arrows, pointing right to left, which represent the flow of data from one operator to the next.<\/p>\n<h2>The Basics of Reading Plans<\/h2>\n<p>The natural way to read a plan is to follow the data flow, right to left. The two operators on the right-hand side (an <strong>Index Seek<\/strong> and <strong>Clustered Index Scan<\/strong>), and the one at the bottom (<strong>Clustered Index Seek<\/strong>), are data access operators and represent various means of reading the data from the underlying table and indexes.<\/p>\n<div class=\"tips\">\n<h4>More on Reading Execution Plans<\/h4>\n<p>I can&#8217;t cover more than the absolute basics of reading graphical execution here. Please see <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms175913(v=sql.105).aspx\">Books Online<\/a> or <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/complete-showplan-operators\/\">Fabiano Amorim&#8217;s eBook<\/a> for a description of all execution plan operators, and Grant Fritchey&#8217;s <a href=\"https:\/\/www.simple-talk.com\/books\/sql-books\/sql-server-execution-plans,-second-edition,-by-grant-fritchey\/\">SQL Server Execution Plans<\/a> book (available as a free download) for a full tutorial on capturing, reading and interpreting plans.<\/p>\n<\/div>\n<p>The optimizer chose first to implement the logical <code>INNER JOIN<\/code> between the <code>Location<\/code> and <code>ProductInventory<\/code> tables, <em>i.e.<\/em> only returning matching rows, and it chose to implement the join, physically, using a <strong>Nested Loops<\/strong> operation, though it can choose other physical join implementations (<a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms189582(v=sql.105).aspx\">Hash Match<\/a> or <a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms189961(v=sql.105).aspx\">Merge Join<\/a>) depending on the number of rows returned and ordering of the data. It seeks an index on the outer table (<code>Location<\/code>) for <code>'Paint'<\/code> and for each row it finds, it uses that row&#8217;s <code>LocationID<\/code> value to perform a search on the inner table (<code>ProductInventory<\/code>) for matching rows. In this case, it performs a scan of <code>ProductInventory<\/code> because the available index is ordered on <code>ProductID<\/code>, not <code>LocationID<\/code>. The optimizer uses another <strong>Nested Loops<\/strong> operation to join this data stream with matching rows in the <code>Product<\/code> table. Finally, we see a <strong>SELECT<\/strong> operator, representing the final result set.<\/p>\n<p>While it&#8217;s natural to read a plan right-to-left, the actual execution occurs left-to-right. Each operator supports a method called <code>GetNext<\/code><code>(<\/code><code>)<\/code>, which is simply a request for a row from the operators immediately to the right. Streaming operators pass on each row, to the left, as it becomes available. All the operators in Figure 1 are streaming. However some operators are partially blocking, meaning they must complete some part of their work before passing on the first row, such as building a hash table in memory (e.g. for a <strong>Hash Match<\/strong> join), or collecting all the rows in a grouping set in order to perform an aggregation (e.g. aggregating operators such as a <strong>Stream Aggregat<\/strong><strong>e<\/strong>), and others, such as a <strong>Sort<\/strong> operator, are completely blocking and must collect the entire data set before passing on the first row.<\/p>\n<div class=\"tips\">\n<h4>Look out for Sort Warnings in Execution plans<\/h4>\n<p class=\"illustration\">A yellow exclamation mark on a Sort operator indicates that SQL Server had to spill the sort operation to disk, in <code>tempdb<\/code>. These Sort spills are often very expensive operations. I&#8217;ll cover an example of this on the next article=&#8221;float:left&#8221;&gt;<\/p>\n<\/div>\n<p>In order to read a plan, we must understand what each operator does and how much data is flowing between each operator. In order to use a plan to uncover possible query problems, we must use the all the information in the plan to understand why the optimizer chose particular operators, and to spot common signs of trouble. This often means that we need to explore the <strong>Properties<\/strong> of each operator.<\/p>\n<h2>Operator Properties<\/h2>\n<p>For each operator, the plan displays a wealth of property values. Some of these properties are operator-specific while others are common to all operators. The plan in Figure 1 (an <em>estimated<\/em> plan; more on this shortly) shows the properties for the <strong>Index <\/strong><strong>Seek<\/strong> operator on the <code>Location<\/code> table. We can see properties such as the number of rows returned, the number of times the operator was called, various estimated costs associated with executing the operator, the cardinality of the table, and more. Notice that, in this case, the <strong>Seek Predicates<\/strong> property shows how our logical <code>WHERE<\/code> clause was implemented as part for the <strong>Index Seek<\/strong> operation.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68909\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/2.png\" alt=\"2\" width=\"620\" height=\"467\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 2<\/p>\n<h2>Why Execution Plans are important<\/h2>\n<p>Straight away, we can see that an execution plan can reveal very valuable information about how SQL Server chose to execute the query we submitted. In this simple example, we can immediately see the objects accessed by the query and the order in which they were accessed, which indexes were used (or not), and how joins were implemented. In more complex queries, the plan would also reveal when sorting occurred, how calculations and aggregation were performed, and more. If we know how to read a plan and can mine the information they divulge, we use the plans to look for common signs of potential trouble, which lead to poor query performance.<\/p>\n<p>More fundamentally, the mere existence of a component called the Query Optimizer tells us that SQL Server, not the database developer, decides how a query should be executed. We submit an SQL query to describe the required set of data. We do not tell SQL Server how to execute it. The optimizer is completely free to choose the set of operators for the plan, and set their order, within the overriding requirement that the resulting plan must guarantee to return exactly the same data set as would be returned according to the <a href=\"http:\/\/bit.ly\/1QBzQwS\">logical processing order<\/a> of the query. Except for very simple queries, the physical processing order, as expressed in the plan, will likely not match the logical processing order. We saw a very simple example of this with the previous example. Logically, the processing order of the <code>WHERE<\/code> clause comes after the <code>FROM<\/code> clause, but in practice it is more efficient to &#8220;push down&#8221; the predicate and retrieve only the rows that match the predicate condition, rather than retrieve all the rows and then filter out those that are not required.<\/p>\n<p>In the vast majority of cases, decisions on processing order should be left entirely in the hands of the optimizer. Developers need to be wary of bringing to SQL Server their standard, imperative approach to programming, with line-by-line control over what happens and when. It can lead to sub-optimal plans and poor performance. We won&#8217;t discuss this further here, but Peter Larsson, who writes SQL most can only dream of, explores these ideas in more detail in <a href=\"https:\/\/www.simple-talk.com\/sql\/database-administration\/the-road-to-professional-database-development-set-based-thinking\/\">this article<\/a>.<\/p>\n<h2>How the optimizer generates plans<\/h2>\n<p>When a query reaches the relational engine, it is parsed by the Parser, bound by the Algebrizer and then optimized by the Query Optimizer. Collectively, these processes are referred to as <strong>query compilation<\/strong> <em>i.e.<\/em> the process of compiling a query into an execution plan.<\/p>\n<p>The optimizer generates a number of candidate execution plans for each query submitted, and chooses the plan that it <em>estimates<\/em> will have the <strong>lowest overall <\/strong><strong>execution <\/strong><strong>cost<\/strong> in terms of CPU and IO. Of course, the optimizer cannot actually execute any queries, so its cost estimates and subsequent plan choices are based on its knowledge of the underlying data structures (i.e. the tables and available indexes), and on <strong>statistics<\/strong><strong>, <\/strong>aggregated information based on a sample of the data, describing the volume and distribution of data in those data structures.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68911\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/3.png\" alt=\"3\" width=\"620\" height=\"286\" \/><\/p>\n<p class=\"caption\">Figure 3<\/p>\n<p>The optimizer&#8217;s cost estimates depend largely on its <em>cardinality estimations<\/em>, of how many rows are in a table, and how many of those rows satisfy the various search and join conditions.<\/p>\n<p>It uses all this information to answer questions such as:<\/p>\n<ul>\n<li>How many rows in the table?<\/li>\n<li>How many rows match the search condition?<\/li>\n<li>What indexes are available?<\/li>\n<li>How is each index ordered?<\/li>\n<li>What is the index density?<\/li>\n<li>How many rows match the join conditions?<\/li>\n<li>How are each of the required data streams ordered?<\/li>\n<\/ul>\n<p>Based on its knowledge and estimates, the optimizer chooses the most efficient data access path and the most appropriate operators to implement each phase of the query.<\/p>\n<div class=\"tips\">\n<h4>The optimizer and statistics<\/h4>\n<p>SQL Server generates statistics collected on columns and indexes within the database, and gradually these statistics go &#8220;stale&#8221; as queries modify data in the table until a certain threshold level of change is passed and SQL Server auto-updates the statistics. I can&#8217;t cover this topic in any detail here, but see <a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/managing-sql-server-statistics\/\">Managing SQL Server Statistics<\/a>.<\/p>\n<\/div>\n<p>The optimizer&#8217;s selected plan passes to the query execution engine, and is also stored in an area of memory called the <strong>plan cache<\/strong>. The next time we submit the same query, SQL Server may be able to reuse an existing plan in cache, rather than generate a new one, and therefore bypassing the optimization phase. We&#8217;ll tackle the topic of plan reuse in a later article.<\/p>\n<h1>Viewing Execution Plans<\/h1>\n<p>SQL Server stores execution plans in the plan cache, in a binary format. SQL Server can output these plans in XML format (recommended) or in text format (deprecated). When viewing execution plans in SQL Server Management Studio (SSMS), and in some third party tools, we can see a visual representation of the XML, called a <strong>graphical plan<\/strong> (see Figure 1). Graphical plans are most common format in which to read execution plans, and are the only format presented in his article.<\/p>\n<h2>View graphical plans in SSMS<\/h2>\n<p>In SSMS, we can view an execution plan with or without runtime information. The version of the plan <em>without<\/em> runtime information is called the <strong>estimated<\/strong> execution plan, and the version with runtime information is called the <strong>actual<\/strong> execution plan.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68912\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/4.png\" alt=\"4\" width=\"620\" height=\"311\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 4<\/p>\n<p>When we request to display an estimated plan for a query, the optimizer performs the parsing and binding phases, and then the optimization phase, assuming no there is no plan it can reuse in the cache, as described in Figure 3. However, it does not execute the query (no result set will be returned). Therefore, all property values displayed with the plan, and for each operator, such the number of rows returned, the number of times the operator was called, and the various costs associated with executing the operator, are all estimates. Estimated plans are useful during development for testing large, complex queries that could take a long time to run.<\/p>\n<p>If we request what SSMS refers to as the &#8220;actual plan&#8221;, it means we wish to execute the query. Upon completing execution, SQL Server has available certain runtime information, namely the actual number of rows and actual number of executions, which it can display with the plan. All costs associated with the operators are still estimates.<\/p>\n<div class=\"tips\">\n<h4>There is only one plan<\/h4>\n<p>The &#8220;actual&#8221; and &#8220;estimated&#8221; plans are not different plans, as I&#8217;ve seen implied in some places. It is the same plan in each case, but displayed with or without runtime information. The plan is stored in the cache, but not the individual runtime information. However, if we request it at the time of execution (by requesting an &#8220;actual plan&#8221;), then SQL Server will display the available runtime information with the plan. SQL Server stores aggregated runtime information for cached plans in a Dynamic Management View called <a href=\"https:\/\/msdn.microsoft.com\/en-US\/library\/ms189741.aspx\">sys.dm_exec_query_stats<\/a>.<\/p>\n<\/div>\n<h2>Use the SET options to return XML plans for a session<\/h2>\n<p>We can use <code>SET<\/code> statements to return the execution plan in XML format, again either with or without runtime statistics:<\/p>\n<ul>\n<li><code>SET SHOWPLAN_XML ON | OFF<\/code> &#8211; returns &#8220;estimated&#8221; plans (no runtime information); no T-SQL statements are executed<\/li>\n<li><code>SET STATISTICS XML ON | OFF<\/code> &#8211; executes the queries and returns the plan with runtime information (the &#8220;actual&#8221; plan)<\/li>\n<\/ul>\n<p>Plans will be returned for all statements executed in the session, until the option is turned off. A common problem for developers who don&#8217;t have access to SSMS, or don&#8217;t want to use it, is that they don&#8217;t have an easy way to read and interpret the XML plan.<\/p>\n<p>One solution is to use a free web-based tool called <a href=\"http:\/\/sqltuneup.sqlservercentral.com\/\">SQL Tune Up<\/a>, available through SQLServerCentral.com. Simply drop the execution plan file (<code>.<\/code><code>sqlplan<\/code>) onto the page, and it will display it as a graphical plan.<\/p>\n<p>This functionality is built into the ANTS Performance Profiler tool, which we&#8217;ll cover shortly.<\/p>\n<h2>Retrieve the cached plan<\/h2>\n<p>We can use a variety of tools, such as Extended Events or SQL Trace, to retrieve from the plan cache the plans for previously-executed queries. I won&#8217;t cover these tools in this article. We can also retrieve the cached plan by querying the <code>sys.dm_exec_cached_plans<\/code> dynamic management object (a little more on this shortly).<\/p>\n<h2>Use a Code Profiling tool<\/h2>\n<p>A commercial code profiling tool such as Red Gate&#8217;s <a href=\"http:\/\/www.red-gate.com\/products\/dotnet-development\/ants-performance-profiler\/?utm_source=simpletalk&amp;utm_medium=article&amp;utm_content=basicsqltroubleshooting&amp;utm_campaign=sql&amp;utm_term=simpletalk\">ANTS Performance Profiler (APP)<\/a> exposes expensive methods in your .NET application code. The latest version of the tool also exposes expensive SQL calls, and allows the developer to drill into these expensive data operations and examine cached execution plans for these queries. It means that developers who use the tool now have a way to troubleshoot side-by-side bottlenecks in both the .NET code and in the SQL code.<\/p>\n<h1>The Index Operators in Execution Plans<\/h1>\n<p>I won&#8217;t cover heaps in this article; in other words, I&#8217;ll assume that all tables have a clustered index, which is a generally-accepted best practice, except for some small tables with a very high read:write ratio. In addition to a clustered index, most tables will have a number of non-clustered indexes, designed to aid the performance of critical, frequent and expensive queries in the workload.<\/p>\n<p>There are essentially three ways that SQL Server can access data in an index:<\/p>\n<ul>\n<li><strong>Seek<\/strong> &#8211; navigate directly to the page(s) containing the qualifying rows or the start\/end of a range of rows.\n<ul>\n<li>Execution Plan Operators:<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><code>Clustered Index Seek<\/code><\/p>\n<p><code>Index Seek<\/code> <code>(<\/code><code>NonC<\/code><code>lustered<\/code><code>)<\/code><\/p>\n<ul>\n<li><strong>Scan<\/strong> &#8211; navigate down to first or last leaf level page of the index and then scan forward or backward through the leaf pages. A scan usually reads the entire index, but may read only a portion of the index in some cases.\n<ul>\n<li>Execution Plan Operators:<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><code>Clustered<\/code> <code>Index Scan<\/code><\/p>\n<p><code>Index<\/code> <code>Scan<\/code> <code>(<\/code><code>NonC<\/code><code>lustered<\/code><code>)<\/code><\/p>\n<ul>\n<li><strong>Lookup<\/strong> &#8211; occurs in addition to an <strong>Index Seek<\/strong> or <strong>Index <\/strong><strong>Scan<\/strong>, when the index is non-covering. SQL Server performs a key lookup to the clustered index to retrieve values for columns not available in the non-clustered index.\n<ul>\n<li>Execution Plan Operator:<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><code>Key<\/code> <code>Lookup<\/code><code> (Clustered)<\/code><\/p>\n<h3>Clustered Index Seeks and Scans<\/h3>\n<p>Indexes consist of 8K pages connected in a B-tree structure. Figure 5 represents a simplified view of the B-tree structure for a simplified version of the clustered index on the <code>SalesOrderDetail<\/code> table of the <code>AdventureWorks2012<\/code> database. In this version the clustering key is <code>SalesOrderID<\/code> (in reality the clustered index uses a composite key on <code>SalesOrderID<\/code> and <code>SalesOrderDetailID<\/code>). There are 120K rows in the table.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68913\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/5.png\" alt=\"5\" width=\"620\" height=\"302\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 5<\/p>\n<p>A clustered index is not a &#8220;copy&#8221; of the table. It is the table, with a b-tree structure built on top of it, so that the data is organized by the clustering key. This is why we can only create one clustered index per table.<\/p>\n<p>The leaf level pages of a clustered index store the data rows, ordered according to the clustering key. In Figure 5, a query requests the row for a specific <code>SalesOrderID<\/code> value, and SQL Server navigates the b-tree structure directly to the page containing that row, as represented by the orange arrows. This results in a <strong>clustered index seek<\/strong> operator in the execution plan. Each page it needs to read in order to retrieve the data represents a logical read, so in this case it can return the data in 3 logical reads.<\/p>\n<p>If, instead, our query searches for a row based on a column value other than <code>SalesOrderID<\/code>, such as <code>CarrierTrackingNumber<\/code> then assuming there isn&#8217;t a non-clustered index on <code>CarrierTrackingNumber<\/code>, nor a covering non-clustered index that it could scan, SQL Server will scan the clustered index.<\/p>\n<p>It will navigate down to the first leaf level page of the clustered index and then scan all subsequent leaf pages looking for matching rows. This is a <strong>clustered index scan<\/strong>, as represented by the blue, dashed arrow in Figure 5. The number of logical reads to return the data will likely be the number of leaf level pages in the index (plus the root and any intermediate pages). In Figure 5, each leaf level page stores 20K rows, which is unrealistic. Each leaf page in the figure might represent 100 leaf pages in a real index (<em>i.e.<\/em> 200 rows per page and 600 logical reads in a scan of an index of this size, although these are just &#8216;ballpark&#8217; figures).<\/p>\n<p>Sometimes, scans are highly efficient operations, especially for queries that return a substantial proportion of the table data. However, a general goal for critical and frequently-executed queries is to have non-clustered indexes in place that will allow SQL Server to <strong>seek<\/strong> the required data in as few logical reads as possible.<\/p>\n<h3>Index Seek and Scans, plus Key Lookups<\/h3>\n<p>A non-clustered index has the same b-tree structure, but the difference is that the leaf level pages do not contain the data rows, just the data for the index key columns, plus the clustered index key columns, plus any columns that we add to the index using the <code>INCLUDE<\/code> clause.<\/p>\n<p>Figure 6 shows a simplified depiction of a non-clustered index on the <code>ProductID<\/code> column of the <code>SalesOrderDetail<\/code> table. The index has no <code>INCLUDE<\/code> columns. The leaf pages contain only the values for the index key column (<code>ProductID<\/code>) and the clustered index key columns (<code>SalesOrderID<\/code>).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68914\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/6.png\" alt=\"6\" width=\"620\" height=\"342\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 6<\/p>\n<p>If SQL Server uses this index for a query that searches on a column other than <code>ProductID<\/code>, it will result in an <strong>Index Scan<\/strong> (as described previously for clustered index scan). If a query searches on <code>ProductID<\/code>, and only requires this and the <code>SalesOrderID<\/code> values, then SQL Server will perform an <code>Index Seek<\/code>. If the query searches on <code>ProductID<\/code>, but also requires additional columns, such as <code>CarrierTrackingNum<\/code><code>b<\/code><code>er<\/code>, then we will see in the plan an <strong>Index Seek<\/strong> plus a <strong>Key Lookup<\/strong> operator, where SQL Server will look up in the leaf level of the clustered index the matching values for <code>CarrierTrackingNum<\/code><code>b<\/code><code>er<\/code>.<\/p>\n<h1>Other Useful Tools for Index Investigation<\/h1>\n<p>I will mention briefly here, but not cover in any detail, a few tools that I find useful when investigating further any potential indexing problems that I uncover through the execution plans. The first, which I use in this article, is Michelle Ufford&#8217;s Estimating rows per page script, as reproduced in Listing 1.<\/p>\n<pre class=\"lang:tsql theme:ssms2012 \">USE AdventureWorks2012;\r\nGO\r\nSELECT  OBJECT_NAME(i.object_id) AS 'tableName' ,\r\n        i.name AS 'indexName' ,\r\n        i.type_desc ,\r\n        MAX(p.partition_number) AS 'partitions' ,\r\n        SUM(p.rows) AS 'rows' ,\r\n        SUM(au.data_pages) AS 'dataPages' ,\r\n        SUM(p.rows) \/ SUM(au.data_pages) AS 'rowsPerPage'\r\nFROM    sys.indexes AS i\r\n        JOIN sys.partitions AS p ON i.object_id = p.object_id\r\n                                    AND i.index_id = p.index_id\r\n        JOIN sys.allocation_units AS au ON p.hobt_id = au.container_id\r\nWHERE   OBJECT_NAME(i.object_id) NOT LIKE 'sys%'\r\n        AND au.type_desc = 'IN_ROW_DATA'\r\n       AND OBJECT_NAME(i.object_id) LIKE 'SalesOrderDetail%' - table filter \r\nGROUP BY OBJECT_NAME(i.object_id) ,\r\n        i.name ,\r\n        i.type_desc\r\nHAVING  SUM(au.data_pages) &gt; 100 -- remove filter here for smaller tables<\/pre>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68915\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/7.png\" alt=\"7\" width=\"620\" height=\"62\" \/><\/p>\n<p class=\"caption\">Listing 1<\/p>\n<p>For critical queries, I find the data useful in providing a &#8220;target&#8221; number of logical reads, depending on the rows-per-page and the number of rows the query needs to return. I also recommend many of the other index-related scripts, and others, in <a href=\"https:\/\/github.com\/MichelleUfford\/sql-scripts\">Michelle&#8217;s archive<\/a> (now open-sourced).<\/p>\n<p>Kimberley Tripps&#8217;s version of <a href=\"http:\/\/www.sqlskills.com\/blogs\/kimberly\/use-this-new-sql-server-2012-rewrite-for-sp_helpindex\/\">sp_helpindex<\/a>, an improved version of the tool of the same name on MSDN, is also very useful for investigating the various indexes on your tables and their structure<\/p>\n<p>The <a href=\"https:\/\/msdn.microsoft.com\/en-gb\/library\/ms187974.aspx\">index-related Dynamic Management Views<\/a> provide a wealth of information regarding existing indexes and their usage statistics, as well as potentially missing indexes. There are numerous excellent community-supplied scripts available that mine the information in these objects, some of which I reference in <em>Further Reading<\/em>, at the end of the article.<\/p>\n<p>One approach I find particularly useful during testing is to use these objects to <a href=\"https:\/\/www.simple-talk.com\/sql\/performance\/identifying-and-solving-index-scan-problems\/\">search the plan cache<\/a> for existing plans for queries that caused expensive scans. I can then examine the plans to see which columns are involved in the searches and joins for those queries, and look for indexing opportunities.<\/p>\n<h1>Investigating Indexing Issues with Execution Plans<\/h1>\n<p>This section will provide some examples that demonstrate how to spot signs of potential SQL Server indexing problems in the execution plan. The examples are simple and the target tables quite small, meaning that indexing becomes less critical. Nevertheless, they do illustrate some of the thought processes behind sensible indexing, which will apply for larger tables.<\/p>\n<p>It will cover issues such as:<\/p>\n<ul>\n<li><strong>Choice of clustered index<\/strong> &#8211; ideally narrow, static, ever-increasing but establishing the &#8220;natural order&#8221; of the data.<\/li>\n<li><strong>Missing non-clustered indexes<\/strong> &#8211; to support Foreign Key columns, common searches, aggregations and so on.<\/li>\n<li><strong>Why SQL Server may chose not to use a<\/strong><strong> non-clustered <\/strong><strong>index<\/strong> &#8211; if the index is non-covering and not selective enough for the key lookups to be efficient.<\/li>\n<\/ul>\n<p>We&#8217;ll focus on the details of the plans, and provide references for further reading on the broader topic of general indexing strategies for SQL Server databases.<\/p>\n<h2>ANTS Performance Profiler and the LibraryManager Application<\/h2>\n<p>The examples in this article use ANTS performance Profiler (APP) to view the execution plans, along with an internal <strong>LibraryManager<\/strong> application, which by design has a few application performance problems, introduced to help illustrate functionality in ANTS Performance Profiler. Library Manager is an admin console that could be used at a library. It is built with WinForms and Entity Framework and uses a SQL Server database called <code>LibraryManager<\/code>.<\/p>\n<p>As part of the code download you will find the <strong>.<\/strong><strong>sln<\/strong> file for a simple web form that will run the LibraryManager queries that we investigate in the article, against the LibraryManager database. You will just need to update the <strong>App.config<\/strong> file with the connection string to your database server, build the solution in Visual Studio, and then start up APP and attach to the executable (in the debug folder). You&#8217;ll also find a database backup file <strong>(.<\/strong><strong>bak<\/strong>), which you can restore to create the <code>LibraryManager<\/code> database.<\/p>\n<p>Alternatively, in the code download file you&#8217;ll find SQL code files that will allow you to run the queries in SSMS, if you prefer, in which case you will simply need to create a copy of the <code>LibraryManager<\/code> database.<\/p>\n<h2>Getting Started<\/h2>\n<p>If you wish to use APP, fire it up, define a <strong>New<\/strong><strong> profiling session<\/strong> and attach to the LibraryManager application by entering the path to the executable. Make sure you choose the <strong>Line-level and method-level timings<\/strong>.<\/p>\n<p>Upon startup the LibraryManager application automatically fires off a selection of queries to the database, so we see some immediate action in APP (you won&#8217;t see exactly the same queries executed each time you start or restart a profiling session).<\/p>\n<p>The top third of the screen shows the activity timeline including, at the top, an adjustable sliding region control, which we can use to focus on a specific area of interest on the timeline. APP continues collecting diagnostic data, in the background, while we focus in on a specific region. We can use bookmarks to save interesting regions, so we can always jump back to that regions as APP continues to collect profiling data.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68916\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/8.png\" alt=\"8\" width=\"444\" height=\"184\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 7<\/p>\n<p>The lower portion of screen shows, by default, the call tree view of application activity, in other words the various method calls made in the selected time period, and their timings.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68917\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/9.png\" alt=\"9\" width=\"620\" height=\"266\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 8<\/p>\n<p>We can see immediately several &#8220;hot&#8221; (expensive) .NET methods, which we&#8217;re going to ignore in this article, since we&#8217;re focusing in on database calls. The important point is that we&#8217;re seeing expensive SQL calls right alongside the expensive .NET methods.<\/p>\n<p>By default the timings show CPU time as a percentage of total cost of all method calls in that time window. For SQL calls, we need to switch the timing options to <strong>Wall-clock time<\/strong> and <strong>Milliseconds<\/strong>. Also, the timing switch occurs automatically if we select the <strong>Database calls<\/strong> view, as shown in Figure 9.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68918\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/10.png\" alt=\"10\" width=\"620\" height=\"171\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 9<\/p>\n<p>Let&#8217;s investigate the most expensive queries, using execution plans, since some of them could benefit from more efficient indexing.<\/p>\n<h2>Clustered Index operations<\/h2>\n<p>The design the clustering index is mainly from the perspective of organizing the table. The key characteristics of a clustering key, as discussed in many articles (see <em>Further Reading<\/em>), are for it to be narrow, static and ever-increasing. For this reason, many clustered indexes tend to use an <code>IDENTITY<\/code> column as the clustering key. However, date-based columns are often useful clustering keys in OLTP databases, particularly when the data is commonly-queries by date ranges.<\/p>\n<p>Let&#8217;s consider our first example. The most expensive query currently identified for our LibararyManager application returns various details relating to the most recently loaned book.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT TOP (1) \r\n    [Extent1].[Id] AS [Id], \r\n    [Extent1].[MemberId] AS [MemberId], \r\n    [Extent1].[StartDate] AS [StartDate], \r\n    [Extent1].[DueDate] AS [DueDate], \r\n    [Extent1].[FineIncurred] AS [FineIncurred], \r\n    [Extent1].[BookInstanceId] AS [BookInstanceId], \r\n    [Extent1].[Returned] AS [Returned], \r\n    [Extent1].[FinePaid] AS [FinePaid]\r\n    FROM [dbo].[Loans] AS [Extent1]\r\n    WHERE [Extent1].[StartDate] &lt; (SysDateTime())\r\n    ORDER BY [Extent1].[StartDate] DESC;\r\nGO<\/pre>\n<p class=\"caption\">Listing 2<\/p>\n<p>The application uses Entity Framework to generate these queries. The code download file provides &#8220;sanitized&#8221; version of these queries, minus the Entity Framework-generated clutter.<\/p>\n<p>One point to notice is that this is effectively a &#8220;<code>SELECT *<\/code>&#8221; query, returning every column in the table. This makes the task of creating effective indexes much harder. If possible, limit the columns returned to only those that are really required.<\/p>\n<p>Click on the <strong>Plan<\/strong> button to reveal how the SQL Server Query optimizer chose to execute this query. You may have to enter Windows or SQL credentials to connect to the target instance. Above the plan you&#8217;ll see a note (not shown in Figure 9) that the plan is a <strong>cached<\/strong> execution plan, meaning the plan generated by the query optimizer and stored in the plan cache. As discussed earlier, all row-, execution- and cost-related properties for this plan will be estimated values, based on statistics. If we were to copy the query text from Listing 2 into SSMS and execute it, selecting <strong>Include Actual Execution Plan<\/strong>, we will see the same plan, but this time with actual row number and execution counts alongside the estimated ones.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-medium wp-image-68919\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/11.png\" alt=\"11\" width=\"300\" height=\"119\" \/><\/p>\n<p class=\"caption\">Figure 9<\/p>\n<p>The query accesses only one table, the <code>Loans<\/code> table, and we can see that the optimizer opted to access the data via a <strong>Clustered Index Scan<\/strong>. In other words, it scanned all the pages in the leaf level of the clustered index, <code>PK_Loans_Id<\/code>. The <code>Loans<\/code> table has 24693 rows, as indicated by <strong>Table Cardinality<\/strong> in the <strong>PROPERTIES<\/strong> link below the Clustered Index Scan operator. This scan operation passes on the 22352 rows that match the predicate condition.<\/p>\n<p>The <strong>TopN<\/strong><strong> Sort<\/strong> operator, which combines both Sort and Top operations, sorts the data in order of descending <code>StartDate<\/code> and then selects only the top row, which it passes to the <strong>SELECT<\/strong> operator.<\/p>\n<p>APP provides some &#8220;sugar coating&#8221; to the plans that we would normally see in SSMS. It makes the estimated IO and CPU costs and number of rows immediately visible, so that we don&#8217;t have to dig through the each operator&#8217;s <strong>Properties<\/strong> window to see them. For each operator certain key properties appear in separate links, again to make the information more easily accessible. For example, if we click the <strong>PREDICATE<\/strong> link for the scan, we&#8217;ll see that the search predicate in our <code>WHERE<\/code> clause was &#8220;pushed down&#8221; into this operator.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-68920\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/12.png\" alt=\"12\" width=\"283\" height=\"319\" \/><\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\">Figure 10<\/p>\n<div class=\"tips\">\n<h4>Left-to-right execution<\/h4>\n<p>Remove the <code>ORDER BY<\/code> from listing 1 and re-execute the query, and you&#8217;ll see that rather than returning 22K+ rows, the scan now returns only a single row to a <strong>Top<\/strong> operator, illustrating that execution order is left-to-right, with control returning to the <strong>Select<\/strong> as soon as all required rows are gathered. Of course, the query now returns a &#8220;random&#8221; row rather than the one relating to the most recent loan, but it does illustrate the substantial additional cost of the <strong>Sort<\/strong>.<\/p>\n<\/div>\n<p>APP also highlights in red boxes, to the left of the plan, expensive and &#8220;data heavy&#8221; operations, the <strong>Sort<\/strong> and <strong>Clustered Index <\/strong><strong>Scan<\/strong> respectively, in this case. The <strong>Sort<\/strong> is a blocking operation. It must collect all 22352 rows, sort them, before passing on the first row. In this case, the first row is only one the query requires.<\/p>\n<p>The second problem, potentially, is the clustered index scan. Currently the clustered index key is the <code>I<\/code><code>d<\/code> column, an <code>IDENTITY<\/code> column. While this column fulfils the preferred criteria for a clustering key, it offers no support for searches based on date, which are common, and there are no searches on <code>I<\/code><code>d<\/code> in the workload. The current query sorts on <code>StartDate<\/code>, and uses <code>StartDate<\/code> in a search predicate.<\/p>\n<p>Let&#8217;s collect execution statistics for Listing 2.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SET STATISTICS TIME ON;\r\nSET STATISTICS IO ON;\r\n\r\n&lt;...Listing 2 code here...&gt;\r\n\r\nSET STATISTICS TIME OFF;\r\nSET STATISTICS IO OFF;<\/pre>\n<p>We see 146 logical reads to return the data, with 40 ms elapsed time.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SQL Server Execution Times:\r\n   CPU time = 0 ms,  elapsed time = 0 ms.\r\n\r\n(1 row(s) affected)\r\nTable 'Loans'. Scan count 1, logical reads 146, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 47 ms,  elapsed time = 40 ms.\r\nSQL Server parse and compile time: \r\n   CPU time = 0 ms, elapsed time = 0 ms.<\/pre>\n<p>Figure10 shows the output from the data-rows-per-page script (Listing 1) for the <code>Loans<\/code> table in the <code>LibraryManager<\/code> database.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68921 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/13.png\" width=\"536\" height=\"75\" \/><\/p>\n<p class=\"caption\">Figure 10<\/p>\n<p>The <code>PK_Loans_Id<\/code> clustered index contains 144 data pages (leaf level pages) with an average of 171 rows per page. Our index is 2-levels deep (you can check this using the <code>index_depth<\/code> column in the <code>sys.dm_db_index_physical_stats<\/code> DMV). SQL Server scans every leaf-level page of <code>PK_Loans_<\/code><code>I<\/code><code>d<\/code>, plus the root page (plus the <a href=\"http:\/\/www.sqlskills.com\/blogs\/paul\/inside-the-storage-engine-iam-pages-iam-chains-and-allocation-units\/\">Index Allocation Map<\/a> page) for a total of 146 logical reads. Ultimately, we only return only a single row.<\/p>\n<p>Clearly, it would be beneficial to have an index ordered by <code>StartDate<\/code>. One solution might be to create a non-clustered index on <code>StartDate<\/code>, but then that index would need to include all of the required columns, if we wished to avoid <strong>Key Lookups<\/strong> (covered later).<\/p>\n<p>If searches based on <code>StartDate<\/code> are very common in our library application, then another option is to reconsider the choice of clustered index key for this table, and instead use <code>StartDate<\/code> as the clustered index key. Alternatively, you might decide to create a composite key on <code>(<\/code><code>StartDate<\/code><code>, ID)<\/code>. In the former case, SQL Server will add a &#8220;uniqueifier&#8221; to duplicate key values. The uniqueifier is an <code>INT<\/code>, so it&#8217;s more overhead than adding the <code>IDENTITY<\/code> column to the clustered index.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">ALTER TABLE dbo.Loans DROP CONSTRAINT PK_Loans_Id;\r\nGO\r\n\r\nCREATE CLUSTERED INDEX IX_Loans_StartDate\r\nON dbo.Loans (StartDate); \r\n\r\nALTER TABLE dbo.Loans ADD CONSTRAINT PK_Loans_Id PRIMARY KEY(Id);\r\nGO<\/pre>\n<p class=\"caption\">Listing 3<\/p>\n<p>If we re-execute Listing 2, capturing new execution statistics, we see a big difference; only 2 logical reads to return the row we require instead of 146.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SQL Server Execution Times:\r\nCPU time = 0 ms,  elapsed time = 0 ms.\r\nSQL Server parse and compile time: \r\nCPU time = 0 ms, elapsed time = 3 ms.\r\n(1 row(s) affected)\r\nTable 'Loans'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nSQL Server Execution Times:\r\nCPU time = 0 ms,  elapsed time = 0 ms.\r\nSQL Server parse and compile time: \r\nCPU time = 0 ms, elapsed time = 0 ms.<\/pre>\n<p>If we re-run the APP profiling session on LibraryManager, we will see that the query in Listing 2 has now slipped well down the list of expensive queries. The execution plan reveals a <strong>Clustered Index Seek<\/strong>, in place of the scan and that we no longer need the expensive <strong>S<\/strong><strong>ort<\/strong> operation, since the data can be retrieved from the clustered index in the correct order.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68922 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/14.png\" width=\"620\" height=\"296\" \/><\/p>\n<p class=\"caption\">Figure 12<\/p>\n<p>The Compute Scalar and Constant Scan operators represent calculation or conversions (related to use of <code>SysDateTime<\/code> in this case). If you open the Properties for the <strong>Clustered Index Seek<\/strong>, you&#8217;ll see that the Scan Direction is <code>BACKWARD<\/code>, since the index is organized in ascending order with respect to <code>StartDate<\/code>, so SQL Server searches it backwards, to get the most recent date. It&#8217;s not really a concern for this query, but note that backwards scans cannot be parallelized. We could have considered created the clustered key as <code>StartDate<\/code><code> DESC<\/code>, but this can cause index fragmentation.<\/p>\n<p>Of course, this index will also support broader queries in the workload based on <code>StartDate<\/code>, such as searches for all loans in the past 12 months (<code>WHERE<\/code><code> <\/code><code>StartDate<\/code><code> &gt;= <\/code><code>DATEADD(<\/code><code>MONTH, -12, CURRENT_TIMESTAMP<\/code>)).<\/p>\n<h2>Adding Non-Clustered Indexes<\/h2>\n<p>Having chosen carefully the clustered index for each table, the next task is to choose the set of non-clustered indexes that will best serve the critical queries in your workload. Our goal is to design a minimum set of non-clustered indexes that will support our most frequent (<em>i.e.<\/em> executed many time per day) and most business-critical queries. Find out how those queries filter, and then create a set of indexes such these queries filter (or join, or aggregate) on a left-based subset of the index key.<\/p>\n<p>We need to avoid the tendency to index every column &#8220;just in case&#8221;, which could destroy the performance of data modifications on that table, since every time we modify a column, SQL Server must all modify all indexes in which that column participates. If a query has multiple conditions in the <code>WHERE<\/code> clause, it is far better to create one index that contains all of these columns (the order might be important) than to create separate indexes on each column.<\/p>\n<p>Let&#8217;s look at the next most expensive query in our list.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT \r\n    [GroupBy1].[A1] AS [C1]\r\n    FROM ( SELECT \r\n        SUM([Filter1].[A1]) AS [A1]\r\n        FROM ( SELECT \r\n            CASE WHEN ([Extent1].[FineIncurred] IS NULL)                   THEN CAST(0 as decimal(18))                   ELSE [Extent1].[FineIncurred] END AS [A1]\r\n            FROM [dbo].[Loans] AS [Extent1]\r\n            WHERE 0 =  CAST( [Extent1].[FinePaid] AS int)\r\n        )  AS [Filter1]\r\n    )  AS [GroupBy1];\r\nGO<\/pre>\n<p class=\"caption\">Listing 4<\/p>\n<p>Entity Framework&#8217;s &#8220;ultra-conservative&#8221; mode of SQL generation makes this query look a little more complex than it really is. It is equivalent to Listing 5 and simply calculates the sum of unpaid fines.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SELECT SUM( FineIncurred)\r\nFROM dbo.Loans\r\nWHERE FinePaid = 0;\r\nGO<\/pre>\n<p class=\"caption\">Listing 5<\/p>\n<p>Here is the execution plan for Listing 4, from APP.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68923 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/15.png\" width=\"620\" height=\"147\" \/><\/p>\n<p class=\"caption\">Figure 13<\/p>\n<p>To return this data SQL Server scans every leaf-level page in the clustered index (146 logical reads in total, as before) and returns the 22263 rows where <code>FinePaid<\/code> is 0. The <strong>PREDICATE<\/strong> for the scan shows an explicit data type conversion of <code>FinePaid<\/code> to an <code>int<\/code> (the underlying data type of the column is <code>tinyint<\/code>). Listing 5 avoids this conversion.<\/p>\n<p>The <strong>DEFINED VALUES<\/strong> for the first <strong>Compute Scalar<\/strong> operator indicates that for each of the 22263 rows returned, it performs an implicit conversion of the <code>FineIncurred<\/code> column from <code>money<\/code> to <code>decimal<\/code><code>(22,4)<\/code> and assigns the resulting value to a variable called <code>Expr<\/code><code>XXXX<\/code>. Running the simplified query in Listing 5 eliminates both this and the previous explicit conversion. The <strong>Stream Aggregate<\/strong> operator performs our <code>SUM<\/code> aggregation and will ignore any <code>NULL<\/code> values in <code>FineIncurred<\/code>.<\/p>\n<p>If we refine the <code>WHERE<\/code> clause in our query in Listing 4 or 5 to discount rows with <code>NULL<\/code> values for <code>FineIncurred<\/code>, by adding <code>AND<\/code> <code>FineIncurred<\/code><code> IS NOT NULL<\/code>, then the plan remains the same except the scan returns only 51 rows, which reduces the cost of the aggregation slightly (to 14%).<\/p>\n<p>For the query in Listing 4 (or 5), the missing index advisor in SSMS pops up and suggests an index that we might like to create to increase the performance of this query. APP exposes this as a &#8220;warning&#8221; that SQL Server has detected an index that could reduce the cost of the query by 77%.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68924 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/16.png\" width=\"228\" height=\"405\" \/><\/p>\n<p class=\"caption\">Figure 14<\/p>\n<p>Listing 6 shows the script for the suggested missing index.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">USE [LibraryManager] GO\r\nCREATE NONCLUSTERED INDEX [&lt;Name of Missing Index, sysname,&gt;] \r\nON [dbo].[Loans] ([FinePaid]) INCLUDE ([FineIncurred])\r\nGO<\/pre>\n<p class=\"caption\"><strong><strong>Listing 6<\/strong><\/strong><\/p>\n<p>I have mixed feelings about seeing this raised in <a href=\"http:\/\/www.red-gate.com\/products\/dotnet-development\/ants-performance-profiler\/?utm_source=simpletalk&amp;utm_medium=article&amp;utm_content=basicsqltroubleshooting&amp;utm_campaign=sql&amp;utm_term=simpletalk\">APP<\/a> as a &#8220;warning&#8221;, because it is well-documented in many online articles (see <em>Further Reading<\/em> section) that the suggestions of the missing index advisor, as well as the data mined directly from the missing index Dynamic Management Views, should be treated as a guide only. Your indexing strategy should be considered in relation to the workload as a whole, and certainly it&#8217;s a bad idea to create every index suggested by the advisor, as you will end up with a lot of duplicate or very similar indexes.<\/p>\n<p>Nevertheless, in this case, let&#8217;s accept the advice and create the index. I won&#8217;t reshow the plan, but you will find that the clustered index scan is replaced by an <strong>Index Seek<\/strong> on the new index. Collecting execution statistics will reveal that the number of logical reads is down from 146 to 77. Rerunning Listing 1 reveals that the new index has 83 data pages (297 rows per page). The seek operation traverses the vast majority of these pages simply because most of the entries in <code>FinePaid<\/code> are 0.<\/p>\n<p>Generally, I&#8217;d question the overall benefit of such an index, unless this happened to be a query that ran very frequently. The problem is that the majority of the values in the <code>Fine<\/code><code>Incurred<\/code> column are <code>NULL<\/code> (only 2481 are non-<code>NULL<\/code>), and therefore the corresponding <code>FinePaid<\/code> values for all of these entries is 0. It could be beneficial in cases such as this one to use a filtered index (first, drop the existing index on <code>FinePaid<\/code>).<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE NONCLUSTERED INDEX idx_nc_filtered_Loans_FinePaid\r\nON dbo.Loans(FinePaid, FineIncurred)\r\nWHERE FineIncurred IS NOT NULL;\r\n\r\n\r\nSET STATISTICS IO ON;\r\nSET STATISTICS TIME ON;\r\nSELECT SUM( FineIncurred)\r\nFROM dbo.Loans\r\nWHERE FinePaid = 0\r\nAND FineIncurred IS NOT NULL;\r\nGO\r\nSET STATISTICS IO OFF;\r\nSET STATISTICS TIME OFF;<\/pre>\n<p class=\"caption\">Listing 7<\/p>\n<p>The filter in a filtered index must match or be a superset of the filter in a query. This index only has 9 leaf level pages and SQL Server only needs to read one of them to retrieve our data (we see a total of 3 logical reads).<\/p>\n<p>As with any index, before creating a filtered index you would want to check the workload and ensure that it&#8217;s useful in general and not for one specific query.<\/p>\n<h2>Lookups and non-covering indexes<\/h2>\n<p>The Query Optimizer makes it choices about which indexes will be useful based in large part on the density of the index and selectivity of the predicate.<\/p>\n<p>A unique index has the lowest possible density and equality predicates against a unique index have the highest possible selectivity, since if we search it for a particular value we are guaranteed to return one row. Conversely, columns such as &#8220;Gender&#8221; or the <code>FinePaid<\/code> column from the previous example have relatively high density; searches on the index are likely to return many rows.<\/p>\n<p>If an index is non-covering, <em>i.e.<\/em> doesn&#8217;t hold all the column values that a query requires, then whether or not the optimizer opts to use it will depend on estimated selectivity of the predicate.<\/p>\n<p>Here&#8217;s a third query from our <code>LibraryM<\/code><code>a<\/code><code>nager<\/code> list, followed by its simplified equivalent.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">SET STATISTICS IO ON;\r\nSET STATISTICS TIME ON;\r\nSELECT \r\n    [GroupBy1].[A1] AS [C1]\r\n    FROM ( SELECT \r\n        COUNT(1) AS [A1]\r\n        FROM [dbo].[Loans] AS [Extent1]\r\n        WHERE (0 =  CAST( [Extent1].[Returned] AS int))         AND ([Extent1].[DueDate] &lt; (SysDateTime()))\r\n    )  AS [GroupBy1];\r\nGO\r\n\r\nSELECT  COUNT(Id)\r\nFROM    dbo.Loans\r\nWHERE  Returned=0 AND DueDate &lt; SYSDATETIME();\r\nGO\r\nSET STATISTICS IO OFF;\r\nSET STATISTICS TIME OFF;<\/pre>\n<p><strong>Listing <\/strong><strong>8<\/strong><\/p>\n<p>I won&#8217;t show the plan again, since it&#8217;s simply a clustered index scan of <code>IX_Loans_StartDate<\/code>, which again causes 146 logical reads. Let&#8217;s assume we decide to cover the query with an index.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">CREATE NONCLUSTERED INDEX idx_nc_Loans_DueDate_Returned\r\nON dbo.Loans(DueDate)\r\nINCLUDE (Returned);\r\nGO<\/pre>\n<p><strong>Listing <\/strong><strong>9<\/strong><\/p>\n<p>Now, the plan for our query will show an index seek. However, will other queries in our workload be able to make use of this index? For example, consider the query in Listing 7 to see all loans that are due to be returned in the next 2 weeks.<\/p>\n<pre class=\"lang:tsql theme:ssms2012\">USE LibraryManager;\r\nGO\r\nSELECT  Id ,\r\n        MemberId ,\r\n        StartDate ,\r\n        DueDate ,\r\n        FineIncurred ,\r\n        BookInstanceId ,\r\n        Returned ,\r\n        FinePaid\r\nFROM    dbo.Loans\r\nWHERE   DueDate BETWEEN CURRENT_TIMESTAMP\r\n                AND     DATEADD(DAY, 14, CURRENT_TIMESTAMP)\r\n        AND Returned = 0\r\nORDER BY DueDate DESC;<\/pre>\n<p><strong>Listing <\/strong><strong>10<\/strong><\/p>\n<p>The Optimizer estimates that 34 rows will be returned out of 24693 (about 0.014%). This is selective enough that the optimizer will use the index, even this it is not covering for this query, and then perform a key lookup to the clustered index to retrieve values for the remaining columns. As you can see from the actual execution plan in Figure 15, captured this time in SSMS, it actually only returns 6 rows.<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68925 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/17.png\" width=\"620\" height=\"195\" \/><\/p>\n<p class=\"caption\">Figure 15<\/p>\n<p>However, there is a low &#8220;tipping point&#8221; at which the optimizer will switch to ignoring our index and instead scanning the clustered index. For example, extending the search to books due to be returned in the next month results in a clustered index scan, along with a suggestion to create an index to cover the query (which would make our previous index redundant).<\/p>\n<p class=\"illustration\">\u00a0<\/p>\n<p class=\"caption\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-68926 size-full\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2015\/08\/18.png\" width=\"620\" height=\"141\" \/><\/p>\n<p class=\"caption\">Figure 16<\/p>\n<p>Again, generally, it would be unwise, for reasons previously discussed, to attempt to &#8220;cover&#8221; every query. However, of course, we should create indexes to cover our most frequent and important queries.<\/p>\n<h1>Summary<\/h1>\n<p>When you start to write SQL Queries, you are likely to assume that you are specifying not only the result that you want, but the way that the Relational Databases should fetch the rows. This isn&#8217;t the case. In SQL Server, the query optimizer determines the best way of executing the query, based on the evidence it has. The same query can be executed in many different way as the data size increases, new indexes become available, or as the data distribution changes. You can influence the optimizer by applying hints, but you&#8217;d be hard put to do better than the optimizer. However, you can easily make life difficult for the optimizer in a number of ways, such as failing to provide the right indexes to support the query, or by preventing the optimizer from estimating the size of intermediate results properly. To ensure that your queries are &#8216;kind&#8217; to the optimizer, and are likely to perform well no matter how much data is in the tables, it is essential to be able to understand execution plans.<\/p>\n<h1>Further Reading<\/h1>\n<h4>Choosing Clustered Index Key<\/h4>\n<ul class=\"reference-list\">\n<li><strong>Effective Clustered indexes<\/strong> &#8211; a good explanation of the key design principles for the clustered index<a href=\"https:\/\/www.simple-talk.com\/sql\/learn-sql-server\/effective-clustered-indexes\/\">https:\/\/www.simple-talk.com\/sql\/learn-sql-server\/effective-clustered-indexes\/<\/a><\/li>\n<li><strong>GUIDs as PRIMARY KEYs and\/or the clustering key<\/strong> &#8211; why GUIDs make very bad clustered index keys<a href=\"http:\/\/www.sqlskills.com\/blogs\/kimberly\/guids-as-primary-keys-andor-the-clustering-key\/\">http:\/\/www.sqlskills.com\/blogs\/kimberly\/guids-as-primary-keys-andor-the-clustering-key\/<\/a><\/li>\n<li><strong>Clustered Index Design Guidelines<\/strong> &#8211; advice from Books Online:<a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms190639(v=sql.105).aspx\">https:\/\/technet.microsoft.com\/en-us\/library\/ms190639(v=sql.105).aspx<\/a><\/li>\n<\/ul>\n<h4>General Indexing Considerations<\/h4>\n<p>As general resources, I recommend the Index section of the blogs of Gail Shaw (<a href=\"http:\/\/sqlinthewild.co.za\/index.php\/category\/sql-server\/indexes\/\">http:\/\/sqlinthewild.co.za\/index.php\/category\/sql-server\/indexes\/<\/a>) and Kimberley Tripp (http:\/\/www.sqlskills.com\/blogs\/kimberly\/category\/indexes\/).<\/p>\n<p>See also:<\/p>\n<ul class=\"reference-list\">\n<li><strong>Nonclustered<\/strong><strong> Index Design Guidelines<\/strong> &#8211; advice from Books Online<a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/ms179325(v=sql.105).aspx\">https:\/\/technet.microsoft.com\/en-us\/library\/ms179325(v=sql.105).aspx<\/a><\/li>\n<li><strong>SQL University: Advanced Indexing &#8211; Indexing Strategies<\/strong> <a href=\"http:\/\/sqlinthewild.co.za\/index.php\/2011\/11\/11\/sql-university-advanced-indexing-indexing-strategies\/\">http:\/\/sqlinthewild.co.za\/index.php\/2011\/11\/11\/sql-university-advanced-indexing-indexing-strategies\/<\/a><\/li>\n<li><strong>One wide index or multiple narrow indexes?<\/strong><a href=\"http:\/\/sqlinthewild.co.za\/index.php\/2010\/09\/14\/one-wide-index-or-multiple-narrow-indexes\/\">http:\/\/sqlinthewild.co.za\/index.php\/2010\/09\/14\/one-wide-index-or-multiple-narrow-indexes\/<\/a><\/li>\n<li><strong>Are <\/strong><strong>your<\/strong><strong> indexing strategies working? (aka Indexing DMVs)<\/strong><a href=\"http:\/\/www.sqlskills.com\/blogs\/kimberly\/the-accidental-dba-day-20-of-30-are-your-indexing-strategies-working-aka-indexing-dmvs\/\">http:\/\/www.sqlskills.com\/blogs\/kimberly\/the-accidental-dba-day-20-of-30-are-your-indexing-strategies-working-aka-indexing-dmvs\/<\/a><\/li>\n<li><strong>Don&#8217;t just blindly create those &#8220;missing&#8221; indexes!<\/strong><a href=\"http:\/\/sqlperformance.com\/2013\/06\/t-sql-queries\/missing-index\">http:\/\/sqlperformance.com\/2013\/06\/t-sql-queries\/missing-index<\/a><\/li>\n<li><strong>Query Tuning Fundamentals: Density, Predicates, Selectivity, and Cardinality<\/strong><a href=\"http:\/\/blogs.msdn.com\/b\/bartd\/archive\/2011\/01\/25\/query_5f00_tuning_5f00_key_5f00_terms.aspx\">http:\/\/blogs.msdn.com\/b\/bartd\/archive\/2011\/01\/25\/query_5f00_tuning_5f00_key_5f00_terms.aspx<\/a><\/li>\n<\/ul>\n<h4>Filtered Index Design Guidelines<\/h4>\n<ul class=\"reference-list\">\n<li><strong>Filtered Index Design Guidelines<\/strong><a href=\"https:\/\/technet.microsoft.com\/en-us\/library\/cc280372(v=sql.105).aspx\">https:\/\/technet.microsoft.com\/en-us\/library\/cc280372(v=sql.105).aspx<\/a><\/li>\n<li><strong>SQL University: Advanced Indexing &#8211; Filtered Indexes<\/strong><a href=\"http:\/\/sqlinthewild.co.za\/index.php\/2011\/11\/09\/sql-university-advanced-indexing-filtered-indexes-2\/\">http:\/\/sqlinthewild.co.za\/index.php\/2011\/11\/09\/sql-university-advanced-indexing-filtered-indexes-2\/<\/a><\/li>\n<\/ul>\n<div>\n<p>Tony Davis will be presenting on the topic of &#8220;Uncovering SQL Server query problems with execution plans&#8221; at SQL in the City <a href=\"http:\/\/sqlinthecity.red-gate.com\/london-2015\/?utm_source=simpletalk&amp;utm_medium=article&amp;utm_content=basicsqltroubleshooting&amp;utm_campaign=sql&amp;utm_term=simpletalk\">London<\/a> (Oct 16, \u00a350) and <a href=\"http:\/\/sqlinthecity.red-gate.com\/seattle-2015\/?utm_source=simpletalk&amp;utm_medium=article&amp;utm_content=basicsqltroubleshooting&amp;utm_campaign=sql&amp;utm_term=simpletalk\">Seattle<\/a> (Oct 26, Free). Hope to see you there!<\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The speed of a slow SQL Query can almost always be improved. In SQL Server, the query optimizer determines the best way of executing the query, based on the evidence it has. The same query can be executed in many different ways as the data size increases, new indexes become available, or as the data distribution changes. If the appropriate index doesn&#8217;t exist or can&#8217;t be used, then SQL Server shrugs and does the best it can. Tony Davis explains how to find out what a query needs to perform w&hellip;<\/p>\n","protected":false},"author":200703,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143529],"tags":[4150,5842,4151],"coauthors":[7955],"class_list":["post-2075","post","type-post","status-publish","format-standard","hentry","category-performance-sql-server","tag-sql","tag-sql-monitor","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2075","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/200703"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=2075"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2075\/revisions"}],"predecessor-version":[{"id":76569,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2075\/revisions\/76569"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2075"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2075"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2075"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2075"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}