tut - sql server query performance guidelines.docx

Upload: arjunec633

Post on 02-Jun-2018

233 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    1/36

    SQL Server Query Performance Guidelines

    Overview

    In my years being a DBA Ive seen many (even made some myself) common mistakes when

    reviewing the SQL queries that run against the systems I maintain. With this experience Ive

    found that there are some general guidelines that should be followed when writing queries

    and also when designing a database schema. In this tutorial we will take a look at a few

    different areas where these common mistakes are made and what can be done to fix them.

    These areas include:

    Query writing

    Indexing

    Schema design

    Explanation

    In each section of this tutorial we will take a look at specific examples that will illustrate

    things that should be avoided when it comes to performance in SQL Server. For each of

    these items I will provide a solution or alternative that would provide better performance.

    Please keep in mind that these are general guidelines and there will be exceptions to these

    examples but in general following these basic principles should get you off to a fast start

    performance wise.

    The specific topics that will be covered in this tip are as follows:

    Query writing:

    o How Join Order Can Affect the Query Plan

    o Remove Function Calls From the SELECT List

    o Avoid Using in WHERE Clause

    o Avoid Using Functions in WHERE Clause

    o Avoid Using Wildcard Characters to Start Search Criteria

    o Use a Derived Table in Place of IN Predicate With Aggregate Functions

    Indexing:

    o Make Sure All JOIN Columns are Indexed

    o Use WHERE, JOIN, ORDER BY, SELECT Column Order When Creating Indexes

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    2/36

    o Make Sure All Tables Have a Clustered Index Defined

    Schema design:

    o Use DELETE CASCADE Option to Handle Child Key Removal in Foreign Key

    Relationships

    o Denormalize For Performance

    Sample Table and Data Setup

    So you can work through these examples in your own environment below is a sample

    schema with some test data you can use to test each topic.

    -- NOTE: You have to recreate these tables after each section of the tutorial

    -- table creation logic

    -- parent table

    CREATE TABLE [dbo].[Parent](

    [ParentID] [bigint] NOT NULL,

    [IntDataColumn] [bigint] NULL,

    [VarcharDataColumn] [varchar](1000) NULL,

    [DateDataColumn] [datetime] NULL,

    CONSTRAINT [PK_Parent] PRIMARY KEY CLUSTERED

    ([ParentID] ASC)

    )

    GO

    -- child table

    CREATE TABLE [dbo].[Child](

    [ChildID] [bigint] NOT NULL,

    [ParentID] [bigint] NULL,

    [IntDataColumn] [bigint] NULL,

    [VarcharDataColumn] [varchar](10) NULL,

    CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    3/36

    ([ChildID] ASC)

    )

    GO

    -- foreign key constraint

    ALTER TABLE [dbo].[Child] WITH CHECK

    ADD CONSTRAINT [FK_Child_Parent] FOREIGN KEY([ParentID])

    REFERENCES [dbo].[Parent] ([ParentID])

    ON DELETE CASCADE

    GO

    -- child detail table

    CREATE TABLE [dbo].[ChildDetail](

    [ChildDetailID] [bigint] NOT NULL,

    [ChildID] [bigint] NOT NULL,

    [ExtraDataColumn] [bigint] NULL,

    CONSTRAINT [PK_ChildDetail] PRIMARY KEY CLUSTERED

    ([ChildDetailID],[ChildID] ASC)

    )

    GO

    -- foreign key constraint

    ALTER TABLE [dbo].[ChildDetail] WITH CHECK

    ADD CONSTRAINT [FK_ChildDetail_Child] FOREIGN KEY([ChildID])

    REFERENCES [dbo].[Child] ([ChildID])

    ON DELETE CASCADE

    GO

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    4/36

    -- data load

    DECLARE @val BIGINT

    DECLARE @val2 BIGINT

    SELECT @val=1

    WHILE @val < 100000

    BEGIN

    INSERT INTO dbo.[Parent] VALUES(@val,@val,'TEST' + CAST(@val AS

    VARCHAR),getdate()-(@val/24.0))

    SELECT @val2=1

    WHILE @val2 < 20

    BEGIN

    INSERT INTO dbo.[Child] VALUES ((@val*100000)+@val2,@val,@val,'TEST' +

    CAST(@val AS VARCHAR))

    INSERT INTO dbo.[ChildDetail] VALUES (1,(@val*100000)+@val2,9999)

    INSERT INTO dbo.[ChildDetail] VALUES (2,(@val*100000)+@val2,1111)

    INSERT INTO dbo.[ChildDetail] VALUES (3,(@val*100000)+@val2,3333)

    INSERT INTO dbo.[ChildDetail] VALUES (4,(@val*100000)+@val2,7777)

    SELECT @val2=@val2+1

    END

    SELECT @val=@val+1

    END

    GO

    -- small table for joins

    CREATE TABLE [dbo].[Small](

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    5/36

    [SmallID] [bigint] NOT NULL,

    [IntDataColumn] [bigint] NULL,

    [VarcharDataColumn] [varchar](100) NULL,

    CONSTRAINT [PK_Small] PRIMARY KEY CLUSTERED

    ([SmallID] ASC)

    )

    GO

    -- data load

    INSERT INTO dbo.[Small] VALUES(50,80,'TEST5080')

    INSERT INTO dbo.[Small] VALUES(510,810,'TEST510810')

    INSERT INTO dbo.[Small] VALUES(7001,9030,'TEST70019030')

    INSERT INTO dbo.[Small] VALUES(12093,10093,'TEST1209310093')

    INSERT INTO dbo.[Small] VALUES(48756,39843,'TEST48756,39843')

    INSERT INTO dbo.[Small] VALUES(829870,57463,'TEST82987057463')

    GO

    -- cleanup statements

    --DROP TABLE [dbo].[Small]

    --DROP TABLE [dbo].[ChildDetail]

    --DROP TABLE [dbo].[Child]

    --DROP TABLE [dbo].[Parent]

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    6/36

    How Join Order Can Affect the Query Plan

    Overview

    The order in which the tables in your queries are joined can have a dramatic effect on how

    the query performs. If your query happens to join all the large tables first and then joins to a

    smaller table later this can cause a lot of unnecessary processing by the SQL engine.

    Explanation

    Generally speaking theSQL Server Optimizerwill try to first join the tables that allows it to

    work with the smallest result set possible. To do this theoptimizerwill try to figure out

    which join order provides the smallest result set early in the processing but in very complex

    queries it does not try all possible combinations as this can become quite resource

    intensive. As a best practice you should try to order your table join so the join that reduces

    the result set the most is joined first. Before we start let's add an index to the column in the

    table we'll be using as the join condition (you'll see more on this in a later topic).

    CREATE NONCLUSTERED INDEX idxChild_ParentID

    ON [dbo].[Child] ([ParentID])

    -- cleanup statements

    --DROP INDEX Child.idxChild_ParentID

    Since in most cases this issue arises when queries become really complex and

    theoptimizerhas a lot of possible plans to evaluate, i.e.. multiple table joins, in order to

    illustrate this point more clearly we'll use the force order hint with a simple query. Here is

    the code to illustrate our poor join order.

    SELECT P.ParentID,C.ChildID,S.SmallID

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON C.ParentID=P.ParentID INNER JOIN

    [dbo].[Small] S ON S.SmallID=C.ParentID

    OPTION (FORCE ORDER)

    Looking at theexplain planfor this query we can see that the Parent and Child tables are

    joined first resulting in 1899980 rows which is then joined to the Small table which reduces

    the final recordset to 95 rows.

    http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    7/36

    And now let's join them in the proper order so the smallest table is joined first. Here is the

    SQL statement.

    SELECT P.ParentID,C.ChildID,S.SmallID

    FROM [dbo].[Small] S INNER JOIN

    [dbo].[Parent] P ON S.SmallID=P.ParentID INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    Looking at theexplain planfor this query we see that the Parent table is first joined to the

    Small table resulting in 5 rows which is then joined to the Child table which produces the

    final recordset of 95 rows (as above).

    Just looking at theexplain plansshould be enough information for us to see that the second

    query will perform better but let's take a look at theSQL Profilerstatistics just to confirm. As

    we see from below joining the Small table first significantly reduces the amount of data the

    query has to process therefore reducing the resources required to execute this query.

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    8/36

    CPU Reads Writes Duration

    Poor Join Order 265 5935 0 309

    Good Join Order 0 35 0 0

    Additional Information

    Joining tables in SQL Server

    SQL Server Join Hints

    Remove Function Calls From the SELECT List

    Overview

    Using functions as a part of any type of programming is usually a good practice as it

    generally makes code more readable and allows you to use to use it over and over again.

    This is also true for SQL queries except for the fact that there are cases when running the

    same statement over and over again might not be the most efficient way to get your result.

    Explanation

    Let's take a look at a high level what happens when you use a function in theSELECTlist of a

    query. Basically that function needs to be called for every record returned by the query. If

    this function contains a multi table join to do some sort of lookup this process could get

    quite expensive. In some cases in makes sense to remove this function call and simply join

    the tables from the function directly to the other tables in your query. To illustrate this point

    let's take a simple query that does a lookup into another table. Below is some SQL we will

    need to run before out test in order to create our function and add an index on the lookup

    column.

    CREATE FUNCTION fn_getParentDate (@ParentID bigint) RETURNS datetime AS

    BEGIN

    DECLARE @DateData datetime

    SELECT @DateData = DateDataColumn from [dbo].[Parent] where ParentID=@ParentID

    RETURN @DateData

    END

    GO

    http://www.mssqltips.com/sqlservertutorial/14/join-tables-in-a-select-statement-example/http://www.mssqltips.com/sqlservertutorial/14/join-tables-in-a-select-statement-example/http://www.mssqltips.com/sqlservertip/2917/sql-server-join-hints/http://www.mssqltips.com/sqlservertip/2917/sql-server-join-hints/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertip/2917/sql-server-join-hints/http://www.mssqltips.com/sqlservertutorial/14/join-tables-in-a-select-statement-example/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    9/36

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    10/36

    Looking at theexplain planfor this query we can see it only has to access the Parent table

    once but it now has to do a scan of this table before it performs a merge join.

    It's not entirely clear from just looking at the aboveexplain planswhich statement will

    perform better. The index seek in the query with the function might lead you to believe that

    it would be faster but let's run the statements and take a look at theSQL Profilerresults

    below. We can see from these results that in fact the query without the function ran more

    than twice as fast and used considerably less resources than the one that uses the function

    call.

    CPU Reads Writes Duration

    Function 14985 5705126 0 25982

    No Function 578 5933 0 11964

    Additional Information

    Removing function calls for better performance

    Avoid Using Not Equal in WHERE Clause

    Overview

    In almost all cases when we use the operator (or any other operator in conjunction with

    the NOT operator, i.e.. NOT IN) index seeks will not be performed and instead a table/index

    scan is required.

    Explanation

    For this example let's make an update to one of our test tables to skew the data a little.

    We'll also add an index to the table on the column that will be used in ourWHEREclause.

    UPDATE [dbo].[Child] SET IntDataColumn=60000

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/2727/removing-function-calls-for-better-performance-in-sql-server/http://www.mssqltips.com/sqlservertip/2727/removing-function-calls-for-better-performance-in-sql-server/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/2727/removing-function-calls-for-better-performance-in-sql-server/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    11/36

    UPDATE [dbo].[Child] SET IntDataColumn=3423 WHERE ParentID=4788

    UPDATE [dbo].[Child] SET IntDataColumn=87347 WHERE ParentID=34268

    UPDATE [dbo].[Child] SET IntDataColumn=93423 WHERE ParentID=84938

    UPDATE [dbo].[Child] SET IntDataColumn=5564 WHERE ParentID=74118

    CREATE NONCLUSTERED INDEX idxChild_IntDataColumn

    ON [dbo].[Child] ([IntDataColumn],[ParentID]) INCLUDE ([ChildID])

    -- cleanup statements

    --DROP INDEX Child.idxChild_IntDataColumn

    Now let's look at a simple query which would return all the records where IntDataColumn

    60000. Here is what that would look like.

    SELECT P.ParentID,C.ChildID,C.IntDataColumn

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    WHERE C.IntDataColumn 60000

    Looking at theexplain planfor this query we see something really interesting. Since

    theoptimizerhas some statistics on the data in this column it has rewritten the query to use

    separate < and > clauses. We can see this in the details of the Index Seek under the Seek

    Predicate heading.

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    12/36

    Now let's see what happens if we have two clauses as follows.

    SELECT P.ParentID,C.ChildID,C.IntDataColumn

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    WHERE C.IntDataColumn 60000 and C.IntDataColumn 5564

    Looking at theexplain planfor this query we also see that the optimizer has done some

    manipulation to the WHERE clause. It is now using the new value we added in the Seek

    Predicate and the original value as the other Predicate. Both have been changed to use

    separate < and > clauses.

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    13/36

    Although the changes that theoptimizerhas made have certainly helped the query by

    avoiding an index scan it's always best to use an equality operator, like = or IN, in you query

    if you want the best performance possible. One thing you should consider before making a

    change like is you want to make sure you have a good understanding of your data as

    changes in your table data can then affect your query results. With that said and given thatwe know our table has very few records that satisfy theWHEREcondition let's flip it to an

    equality operator and see the difference in performance. Here is the new query.

    SELECT P.ParentID,C.ChildID,C.IntDataColumn

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    WHERE C.IntDataColumn IN (3423,87347,93423)

    http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    14/36

    Looking at theexplain planfor this query we can see that it's also doing an index seek but

    looking deeper into the Seek Predicate we can now see it's using the equality operator

    which should be much faster given the number of records that satisfy the WHERE condition.

    Now let's take a look at theSQL Profilerresults for these two queries. We can see below

    that the example using the equality operator runs faster and requires much less resources.

    Note: Both queries returned the same result set.

    Clause CPU Reads Writes Duration

    Inequality 250 110901 0 255

    Equality 15 654 0 15

    Additional Information

    How to find outdated SQL Server statistics

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/2628/how-to-find-outdated-statistics-in-sql-server-2008/http://www.mssqltips.com/sqlservertip/2628/how-to-find-outdated-statistics-in-sql-server-2008/http://www.mssqltips.com/sqlservertip/2628/how-to-find-outdated-statistics-in-sql-server-2008/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    15/36

    Introduction to SQL Server statistics

    Avoid Using Functions in WHERE Clause

    Overview

    As was mentioned in an earlier topic function calls do make code more readable but in some

    cases they cause other unwanted effects that reduce the performance of our queries.

    Certain scenarios of using a function in aWHEREclause is one of those cases.

    Explanation

    There are two reasons why you want to avoid having a function call in yourWHEREclause

    and more specifically on the columns you are filtering on in yourWHEREclause. The first isthe same as the previous topic, the function needs to be called for every record in the result

    set which can slow down your query performance. The second reason which can have even

    more impact on query performance is the fact that if there is a function surrounding the

    column you are trying to filter on, any indexes on that column can not be used. Let's take a

    look at a simple example. First we'll need to create an index on the column in

    ourWHEREclause so we can show how it's not used when we add a function around it.

    Here is the code.

    CREATE NONCLUSTERED INDEX idxParent_DateDataColumn

    ON [dbo].[Parent] ([DateDataColumn])

    -- cleanup statements

    DROP INDEX Parent.idxParent_DateDataColumn

    Now let's look at a simple query which would return all the records in the Parent table that

    are less than 30 days old. Here is one way that we could write the SQL statement.

    SELECT ParentID

    FROM [dbo].[Parent]

    WHERE dateadd(d,30,DateDataColumn) > getdate()

    Looking at theexplain planfor this query we can see that the index on the DateDataColumn

    that we created is ignored and an index scan is performed.

    http://www.mssqltips.com/sql-server-video/79/introduction-to-sql-server-statistics/http://www.mssqltips.com/sql-server-video/79/introduction-to-sql-server-statistics/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sql-server-video/79/introduction-to-sql-server-statistics/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    16/36

    Now let's rewrite this query and move the function to the other side of the > operator. Here

    is the SQL statement.

    SELECT ParentID

    FROM [dbo].[Parent]

    WHERE DateDataColumn > dateadd(d,-30,getdate())

    Looking at theexplain planfor this query we can see that theoptimizeris now using the

    index and performs a seek rather than a scan.

    To confirm that it is indeed faster let's take a look at theSQL Profilerresults for these two

    queries. We can see below that when using an index, as is usually the case, we use fewer

    resources and our statement executes faster.

    CPU Reads Writes Duration

    Function 5 274 0 43

    No Function 0 5 0 5

    Additional Information

    More examples onfunctions in WHERE clause causing issues with performance

    Avoid Using Wildcard Characters to Start Search Criteria

    Overview

    When using the LIKE operator and having the first character in your search string a wildcard

    character, either % or _, theSQL Optimizerwill be forced to do a table/index scan when

    executing your query.

    Explanation

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    17/36

    Before we get into the details of our explanation let's first create an index on the column

    that we are going to use in theWHEREclause of our query. Here is the code to create that

    index on the Child table.

    CREATE NONCLUSTERED INDEX idxChild_VarcharDataColumn

    ON [dbo].[Child] ([VarcharDataColumn])

    -- cleanup statements

    --DROP INDEX Child.idxChild_VarcharDataColumn

    So why does it have to perform a table/index scan? Since all SQL Server indexes are stored

    in aB-Tree structurewhen we begin our search criteria with a wildcard character

    theoptimizeris not able to use an index to perform a seek to find the data quickly. It either

    performs a scan of the table or a scan of an index if all the columns required for the query

    are part of the index. Now I understand that there are some cases where this would not be

    possible based on your requirements but the following example shows why you should try

    to avoid doing this whenever it's possible. Let's write a simple query that performs a search

    on the column we indexed above. Here is the code for this simple SQL statement.

    SELECT * FROM [dbo].[Child]

    WHERE VarcharDataColumn LIKE '%EST5804%'

    Looking at theexplain planfor this query we can see that the index on the

    VarcharDataColumn that we created is ignored and a clustered index scan (essentially a

    table scan) has to be performed.

    Now let's change the search string in this query to remove the wildcard so the string you are

    searching for begins with a valid character. Here is the updated SQL statement. Note: I

    picked the search criteria so that both queries return the same result set so that the results

    are not skewed by one query returning a larger result set.

    SELECT * FROM [dbo].[Child]

    WHERE VarcharDataColumn LIKE 'TEST5804%'

    Looking at theexplain planfor this query we can see that theoptimizeris now using the

    index we created and performs a seek rather than a scan.

    http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://technet.microsoft.com/en-us/library/ms180978(v=sql.105).aspxhttp://technet.microsoft.com/en-us/library/ms180978(v=sql.105).aspxhttp://technet.microsoft.com/en-us/library/ms180978(v=sql.105).aspxhttp://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://technet.microsoft.com/en-us/library/ms180978(v=sql.105).aspxhttp://www.mssqltips.com/sqlservertutorial/136/select-with-where/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    18/36

    Although we should be able to tell from just comparing theexplain plansthat the second

    query will perform better let's just confirm that it indeed uses less resources and executes

    faster than our initial query by looking at theSQL Profilerresults. We can see from below

    that by removing the wildcard character from the start of the query we do in fact see quite a

    big improvement.

    CPU Reads Writes Duration

    Wildcard at Start 328 7042 0 404

    No Wildcard at Start 0 670 0 64

    Additional Information

    Investigate usingFull Text Searchas an alternative to "LIKE '%abc'"

    Use a Derived Table in Place of IN Predicate With Aggregate Functions

    Overview

    Using a derived table in place of the IN predicate when we are aggregating data allows us to

    only have to process certain table records once therefore reducing the amount of resources

    required to execute a query.

    Explanation

    When we use the IN predicate we first have to process the data in our subquery then we are

    processing a lot of the same data again (depending on the WHERE clause) in our main query.

    If we can use a derived table to do most of the work we can avoid the double processing of

    data. Before we take a look at an example to illustrate this point we'll need to add an index

    to our Parent table so the results are not skewed by having to do a table scan. Here is the

    code to create this index.

    CREATE NONCLUSTERED INDEX idxParentID_IntDataColumnParentID

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/32/full-text-search/http://www.mssqltips.com/sql-server-tip-category/32/full-text-search/http://www.mssqltips.com/sql-server-tip-category/32/full-text-search/http://www.mssqltips.com/sql-server-tip-category/32/full-text-search/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    19/36

    ON [dbo].[Parent] ([IntDataColumn],[ParentID])

    -- cleanup statements

    DROP INDEX Parent.idxParentID_IntDataColumnParentID

    Let's look at a query that uses the IN predicate to return the second largest value from a

    table. One way to do this would be as follows.

    SELECT MIN(IntDataColumn)

    FROM [dbo].[Parent]

    WHERE ParentID IN (SELECT TOP 2 ParentID

    FROM [dbo].[Parent]

    ORDER BY IntDataColumn DESC)

    Just by looking at the query we can see we are going to access the Parent table twice to get

    this result. From theexplain planwe can see that the second access does use an index seek

    so it might not be too much of an issue.

    Now let's rewrite this query and use a derived table to generate the result. Here is that SQL

    statement.

    SELECT MIN(IntDataColumn)

    FROM (SELECT TOP 2 IntDataColumn

    FROM [dbo].[Parent]

    ORDER BY IntDataColumn DESC) AS A

    Notice that from the query we only reference the Parent table once and theexplain

    planconfirms that we no longer have to access the Parent table a second time, even with an

    index.

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    20/36

    We can also see from theSQL Profilerresults below that we do get some significant

    resource savings even for this simple query. Although the CPU and total duration were the

    same, we only had to perform 2 reads as opposed to the 8 required by the original query.

    CPU Reads Writes Duration

    IN Predicate 0 8 0 0

    Derived Table 0 2 0 0

    Additional Information

    SQL Server aggregate functions

    Make Sure All JOIN Columns are Indexed

    Overview

    There are many different scenarios when an index can help the performance of a query and

    ensuring that the columns that make up yourJOINpredicate is an important one.

    Explanation

    In order to illustrate this point let's take a look at a simple query that joins the Parent and

    Child tables. We'll keep this result set small by including a ParentID filter in

    theWHEREclause. Here is the statement.

    SELECT *

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    WHERE P.ParentID=32433

    Looking at theexplain planfor this query we can see that theSQL Optimizerhas to perform

    an index scan on the Child table even though we are only looking for a specific ParentID

    from the Parent table.

    http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1221/sql-server-tsql-aggregate-functions/http://www.mssqltips.com/sqlservertip/1221/sql-server-tsql-aggregate-functions/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1221/sql-server-tsql-aggregate-functions/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    21/36

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    22/36

    No Index 110 14217 0 110

    Index 0 63 0 0

    There is one other thing I'd like to mention when it comes to adding indexes on joincolumns. As a general guideline I usually start out by indexing all of my foreign key columns

    and only remove them if I find that they have a negative impact. I recommend this practice

    because more often than not these are the columns that the tables are joined on and you

    tend to see a pretty good performance benefit from having these columns indexed.

    Additional Information

    Choosing non-clustered indexes

    Why foreign keys are important

    Use WHERE, JOIN, ORDERBY, SELECT Column Order When Creating Indexes

    Overview

    The order that the columns are specified in your indexes has an effect on whether or not the

    entire index can be used when theSQL Optimizerparses your query.

    Explanation

    When looking at anexplain planfor a query you'll notice that theSQL Optimizerfirst parses

    theWHEREclause, then theJOINclause, followed by theORDER BYclause and finally it

    processes the data being selected. Based on this fact it makes sense that you would need to

    specify the columns in your index in this order if you want the entire index to be used. This

    is especially true if you are trying to create a covering index. Let's look at the following

    simple query as an example.

    SELECT P.ParentID,C.ChildID,C.IntDataColumn,C.VarcharDataColumn

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID

    WHERE C.IntDataColumn=32433

    ORDER BY ChildID

    And we'll use the following index statement to show how progessively adding columns to

    the index in the order we mentioned above,WHERE-JOIN-ORDER BY-SELECT,will improvethe queries performance. A couple things to note. First, I've included the entire index

    http://www.mssqltips.com/sqlservertutorial/2906/choosing-nonclustered-indexes/http://www.mssqltips.com/sqlservertutorial/2906/choosing-nonclustered-indexes/http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertip/1667/sql-server-join-example/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://www.mssqltips.com/sqlservertutorial/2906/choosing-nonclustered-indexes/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    23/36

    statement here but you can add the columns one at a time to see the difference in each

    step. Second, the second create index statement is just an alternative to adding

    theSELECTcolumns directly to the index, instead they are part of an INCLUDE clause.

    CREATE NONCLUSTERED INDEX idxChild_JOINIndex

    ON [dbo].[Child] ([IntDataColumn],[ParentID],[ChildID],[VarcharDataColumn])

    CREATE NONCLUSTERED INDEX idxChild_JOINIndex

    ON [dbo].[Child] ([IntDataColumn],[ParentID],[ChildID]) INCLUDE ([VarcharDataColumn])

    -- cleanup statements

    DROP INDEX Child.idxChild_JOINIndex

    Let's first take a look at theexplain plansfor each of these queries as we progessively add

    columns to the index.

    No Index

    WHERE Index

    http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    24/36

    WHERE,JOIN Index

    WHERE,JOIN,ORDER BY Index

    WHERE,JOIN,ORDER BY, SELECT Index

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    25/36

    WHERE,JOIN,ORDER BY, INCLUDE Index

    It's hard to tell just from theexplain plansif each step will see an improvement or not

    except for maybe just adding the initial index which eliminated the index scan so let's take a

    look at theSQL Profilerresults to see the actual performance benefit.

    Table Type CPU Reads Writes Duration

    No Index 110 14271 0 103

    WHERE Index 0 129 0 2

    WHERE, JOIN Index 0 117 0 0

    WHERE, JOIN, ORDER BY Index 0 117 0 0

    WHERE, JOIN, ORDER BY, SELECT Index 0 60 0 0

    WHERE, JOIN, ORDER BY, INCLUDE Index 0 60 0 0

    We can see from these results that as we add each column we do see the SQL engine has to

    perform less reads to execute the query thereby executing a little faster. The only exception

    to this is the step where we added theORDER BYto the index but this can be attributed to

    the fact that we are ordering by ChildID which is a primary key so it's already sorted. The

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sqlservertutorial/135/select-with-order-by/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    26/36

    other thing we should note is that there isn't really a performance difference between

    adding theSELECTcolumn directly to the index vs. using the INCLUDE clause.

    Additional Information

    SQL Server index column order - Does it matter?

    Make Sure All Tables Have a Clustered Index Defined

    Overview

    There are two different storage types for tables in SQL Server, Heap tables and Clustered

    tables. Heap tables are tables that do not have a clustered index defined on them and

    Clustered tables are tables that have a clustered index defined. There are few reasons whyit's recommended that most if not all tables have a clustered index defined.

    Explanation

    The first benefit to having a clustered index defined on a column is it saves a lookup when

    you query the based on the indexed column because the data is part of the index. It's

    because of this that it's recommended that you create your clustered indexes on columns

    that are most heavily used inWHEREclauses not necessarily the primary key column if it's

    not used that often to access the table. For example, if you had an OrderDetail table where

    the primary key was an identity column OrderDetailID but most queries accessed the table

    using the OrderID column then it would be better if the clustered index was on the OrderID

    column as this would produce more row level locks when the tables is accessed. Let's take a

    look at the simple query of the Parent table. First we'll have to create an index on one of the

    secondary columns and also remove the clustered index from this table. Here are the SQL

    statements that do both of these operations.

    ALTER TABLE dbo.Parent DROP CONSTRAINT PK_Parent

    ALTER TABLE dbo.Parent ADD CONSTRAINT

    PK_Parent PRIMARY KEY NONCLUSTERED (ParentID)

    CREATE NONCLUSTERED INDEX idxParent_IntDataColumn

    ON [dbo].[Parent] ([IntDataColumn])

    And here is our query of the Parent table.

    http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/http://www.mssqltips.com/sqlservertip/2718/sql-server-index-column-order--does-it-matter/http://www.mssqltips.com/sqlservertip/2718/sql-server-index-column-order--does-it-matter/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertutorial/136/select-with-where/http://www.mssqltips.com/sqlservertip/2718/sql-server-index-column-order--does-it-matter/http://www.mssqltips.com/sqlservertutorial/10/select-command-for-sql-server/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    27/36

    SELECT * FROM [dbo].[Parent] P

    WHERE P.IntDataColumn=32433

    Looking at theexplain planfor this query we can see that theSQL Optimizerhas to perform

    a lookup on the Heap table after finding the record in the index.

    Now let's recreate this index on the InDataColumn as a clustered index. Here is the SQL

    statements.

    DROP INDEX Parent.idxParent_IntDataColumn

    CREATE CLUSTERED INDEX idxParent_IntDataColumn

    ON [dbo].[Parent] ([IntDataColumn])

    Checking theexplain plannow we can see that the SQL optimizer now just has to do an

    index seek.

    Looking at theSQL Profilerresults for this query we can confirm that having a clustered

    index does in fact allow SQL Server to execute the query using less resources, specifically the

    number of reads it has to perform to process the data.

    Table Type CPU Reads Writes Duration

    Heap 0 7 0 6

    Clustered 0 3 0 0

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    28/36

    The second benefit to having a clustered index on a table is it provides a way to reorganize

    the table data when it becomes fragmented. Let's run an update on our table so it becomes

    a little bit fragmented. We'll also put the table back to its original state with only the

    clustered primary key to make it easier to view the results. Here are the SQL statements to

    perform these tasks.

    DROP INDEX Parent.idxParent_IntDataColumn

    ALTER TABLE dbo.Parent DROP CONSTRAINT PK_Parent

    ALTER TABLE dbo.Parent ADD CONSTRAINT

    PK_Parent PRIMARY KEY CLUSTERED (ParentID)

    DECLARE @x BIGINT

    DECLARE @y BIGINT

    SELECT @x=1

    WHILE @x < 100000

    BEGIN

    UPDATE [dbo].[Parent] SET

    VarcharDataColumn='TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    29/36

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST

    TESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTESTTE

    STTESTTESTTESTTESTTESTTEST'+

    CAST(@x AS VARCHAR)

    WHERE ParentID=@x

    SELECT @x=@x+1

    END

    We can double check the fragmentation level of our table using the following query.

    SELECT

    index_level,avg_fragmentation_in_percent,fragment_count,avg_fragment_size_in_pages,p

    age_count

    FROM sys.dm_db_index_physical_stats(DB_ID(N'master'), OBJECT_ID(N'dbo.Parent'),

    NULL, NULL , 'DETAILED')

    We can see from the following results after executing the update above we have some

    fragmentation in our table.

    index_lev

    el

    avg_fragmentation_in_pe

    rcent

    fragment_co

    unt

    avg_fragment_size_in_p

    ages

    page_cou

    nt

    0 14.3 3507 6.9 24394

    1 5.3 111 1.0 112

    2 0 1 1 1

    Now if our table did not have a clustered index we would have to create a temporary table

    and reload the data into this table, then recreate all of the indexes, then drop the original

    table and rename the temporary table. We would also have to have disabled any referential

    integrity constraints before doing any of this and add them back when we were done. All of

    these tasks would also require downtime for the application. Since our table does have a

    clustered index we can simply rebuild this index to reorganize the table data. Doing a

    regular rebuild would require some downtime but we would avoid all the extra steps

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    30/36

    required by the reload. If we don't have the luxury of being able to take our application

    offline to do maintenance the SQL Server does provide the ability to perform this task

    online, while the table is being accessed. Here is the SQL statement to do an online rebuild

    (note: simply remove the WITH condition or replace ON with OFF to perform a regular

    offline rebuild).

    ALTER INDEX PK_Parent ON Parent REBUILD WITH (ONLINE=ON)

    After running the index rebuild statement we can again check the fragmentation in our

    table using thesys.dm_db_index_physical_statsquery from earlier.

    index_lev

    el

    avg_fragmentation_in_pe

    rcent

    fragment_co

    unt

    avg_fragment_size_in_p

    ages

    page_cou

    nt

    0 0.01 18 694.4 12500

    1 0 4 7.5 30

    2 0 1 1 1

    Additional Information

    Find a better candidate for your SQL Server clustered indexes

    Rebuilding indexes using the ONLINE option

    Use DELETE CASCADE Option to Handle Child Key Removal in Foreign Key

    Relationships

    Overview

    Using the DELETE CASCADE option in your foreign key constraint definitions means better

    performance and less code when removing records from tables that have a parent-child

    relationship defined.

    Explanation

    Let's first confirm that our current schema does indeed have the DELETE CASCADE option

    defined on the foreign key between the Parent and Child table. Here is the SQL statement

    the check this as well as the result.

    SELECT name,delete_referential_action_desc

    http://technet.microsoft.com/en-us/library/ms188917.aspxhttp://technet.microsoft.com/en-us/library/ms188917.aspxhttp://technet.microsoft.com/en-us/library/ms188917.aspxhttp://www.mssqltips.com/sqlservertip/1642/finding-a-better-candidate-for-your-sql-server-clustered-indexes/http://www.mssqltips.com/sqlservertip/1642/finding-a-better-candidate-for-your-sql-server-clustered-indexes/http://www.mssqltips.com/sqlservertip/2361/rebuilding-sql-server-indexes-using-the-online-option/http://www.mssqltips.com/sqlservertip/2361/rebuilding-sql-server-indexes-using-the-online-option/http://www.mssqltips.com/sqlservertip/2361/rebuilding-sql-server-indexes-using-the-online-option/http://www.mssqltips.com/sqlservertip/1642/finding-a-better-candidate-for-your-sql-server-clustered-indexes/http://technet.microsoft.com/en-us/library/ms188917.aspx
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    31/36

    FROM sys.foreign_keys

    name delete_referential_action_desc

    FK_Child_Parent CASCADE

    Now that we've confirmed we have this option defined let's delete a record from the Parent

    table using the following SQL statement.

    DELETE FROM [dbo].[Parent] where ParentID=82433

    Looking at theexplain planfor this query we want to note that theSQL Optimizeris first

    removing the child records then performing the delete on the Parent table. Because of this

    it only needs to access each table once.

    Now let's remove the DELETE CASCADE option from our foreign key definition and see if

    there are any differences. In order to do this we'll need to drop and recreate the foreign key

    without the DELETE CASCADE option. Here are the SQL statements to make this change.

    ALTER TABLE [dbo].[Child] DROP CONSTRAINT [FK_Child_Parent]

    ALTER TABLE [dbo].[Child] WITH CHECK

    ADD CONSTRAINT [FK_Child_Parent] FOREIGN KEY([ParentID])

    REFERENCES [dbo].[Parent] ([ParentID])

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sql-server-tip-category/37/query-optimization/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    32/36

    Once the foreign key has been recreated we can run a second delete to see if there is any

    difference in performance. One thing to note here is that without the DELETE CASCADE

    option defined we need to run an additional delete statement to remove the records from

    the Child table first. Here are the SQL statements to perform the delete.

    DELETE FROM [dbo].[Child] where ParentID=62433

    DELETE FROM [dbo].[Parent] where ParentID=62433

    Looking at theexplain planfor these statements we see that they are quite similar. The only

    difference being that because we are executing separate delete statements the Child table

    needs to be accessed a second time to check the foreign key constraint when deleting from

    the Parent table.

    Using theSQL Profilerresults from each query we can confirm this extra scan of the Child

    table does indeed mean that the DELETE CASCADE option performs better. We can see

    below that the DELETE CASCADE option uses less resources in every category and runs

    about 20% faster.

    CPU Reads Writes Duration

    No Delete Cascade 344 28488 0 399

    Delete Cascade 250 14249 0 312

    Additional Information

    Using the DELETE CASCADE option for foreign keys

    The importance of SQL Server foreign keys

    You can't define a foreign key constraint thatcontains multiple cascade paths

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/2743/using-delete-cascade-option-for-foreign-keys/http://www.mssqltips.com/sqlservertip/2743/using-delete-cascade-option-for-foreign-keys/http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://support.microsoft.com/kb/321843http://support.microsoft.com/kb/321843http://support.microsoft.com/kb/321843http://support.microsoft.com/kb/321843http://www.mssqltips.com/sqlservertip/1296/the-importance-of-sql-server-foreign-keys/http://www.mssqltips.com/sqlservertip/2743/using-delete-cascade-option-for-foreign-keys/http://www.mssqltips.com/sql-server-tip-category/83/profiler-and-trace/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    33/36

    Denormalize For Performance

    Overview

    Even though it might mean storing a bit of redundant data, schema denormalization can

    sometimes provide better query performance. The only question then becomes is the extra

    space used worth the performance benefit.

    Explanation

    Before we get started on the example let's make sure our join columns are indexed (as we

    saw in an earlier topic) so the performance results are not skewed by any scans. Here are

    the SQL statements to create these indexes.

    CREATE NONCLUSTERED INDEX idxChild_ParentID

    ON [dbo].[Child] ([ParentID])

    CREATE NONCLUSTERED INDEX idxChildDetail_ChildID

    ON [dbo].[ChildDetail] ([ChildID])

    To test the performance of both our normalized and denormalized schemas we'll use the

    following simple 3 table join query.

    SELECT *

    FROM [dbo].[Parent] P INNER JOIN

    [dbo].[Child] C ON P.ParentID=C.ParentID INNER JOIN

    [dbo].[ChildDetail] CD ON C.ChildID=CD.ChildID

    WHERE P.ParentID=32433

    Looking at theexplain planfor this query it behaves just as we would suspect, using index

    seeks and lookups as it joins each table in the query.

    http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/http://www.mssqltips.com/sqlservertip/1873/how-to-read-sql-server-graphical-query-execution-plans/
  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    34/36

    Now let's do some denormalization by moving the ChildDetail table data into the Child

    table. We'll first need to add the required columns to the Child table. Then before we can

    migrate any data we'll need to remove the primary and foreign key constraints and once the

    data is migrated we can recreate them. The following SQL statements perform these tasks.

    ALTER TABLE [dbo].[Child] ADD [ChildDetailID] [bigint] NOT NULL DEFAULT

    0,[ExtraDataColumn] [bigint]

    ALTER TABLE [dbo].[ChildDetail] DROP CONSTRAINT [FK_ChildDetail_Child]

    ALTER TABLE [dbo].[Child] DROP CONSTRAINT [PK_Child]

    INSERT INTO [dbo].[Child]

    SELECT C.ChildID,C.ParentID,C.IntDataColumn,C.VarcharDataColumn,

    CD.ChildDetailID,CD.ExtraDataColumn

    FROM [dbo].[Child] C INNER JOIN

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    35/36

  • 8/10/2019 TUT - SQL Server Query Performance Guidelines.docx

    36/36

    CPU Reads Writes Duration

    Normalized 0 365 0 75

    Denormalized 0 250 0 5

    We should also take a look at how much extra space we are using as this is important in

    deciding whether or not to implement this type of change. The following SQL statement will

    tell you the amount of disk space each of your tables is consuming.

    SELECT o.name,SUM(reserved_page_count) * 8.0 / 1024 AS 'Size (MB)'

    FROM sys.dm_db_partition_stats ddps INNER JOIN

    sys.objects o ON ddps.object_id=o.object_id

    WHERE o.name in ('Parent','Child','ChildDetail')

    GROUP BY o.name

    The following table shows the results of the above query for both the normalized and

    denormalized table schemas. As we can see the denormalized table schema does use about

    18MB more disk space. The only question now becomes, is the performance benefit worth

    the space this redundant data is holding.

    Table Normalized Size (MB) Denormalized Size (MB)

    Parent 5.9 5.9

    Child 151.6 679.2

    ChildDetail 509.6 N/A

    Total 667.1 685.1

    Additional Information

    Optimizing the Database Design by Denormalizing

    http://technet.microsoft.com/en-us/library/cc505841.aspxhttp://technet.microsoft.com/en-us/library/cc505841.aspxhttp://technet.microsoft.com/en-us/library/cc505841.aspx