How to make actual execution plan better?

  • HI Gurus,

    I ran a simple query against AdventureWorks database:

    select * from Sales.SalesOrderDetail

    where SalesOrderDetailID = 6

    When I looked execution plan it gave me missing non clustered index suggestion on 'SalesOrderDetailID' even though it has PK!!!!

    So I created non-clustered index on the column and ran again. Now I get on execution plan with 'key lookup', 'nested loop' operator, which I think are bad.

    anyway, my question is how to make 'best' execution plan of this query?

    thanks

  • Tac11 (6/30/2015)


    HI Gurus,

    I ran a simple query against AdventureWorks database:

    select * from Sales.SalesOrderDetail

    where SalesOrderDetailID = 6

    When I looked execution plan it gave me missing non clustered index suggestion on 'SalesOrderDetailID' even though it has PK!!!!

    So I created non-clustered index on the column and ran again. Now I get on execution plan with 'key lookup', 'nested loop' operator, which I think are bad.

    anyway, my question is how to make 'best' execution plan of this query?

    thanks

    Well... You just discovered one of the evils of taking the optimizes word on suggested indexes. It's usually wrong...

    Here is the index that will get you the results you were hoping for.

    CREATE NONCLUSTERED INDEX ix_SalesOrderDetail_SalesOrderDetailID ON Sales.SalesOrderDetail (

    SalesOrderDetailID)

    INCLUDE (

    SalesOrderID,

    CarrierTrackingNumber,

    OrderQty,

    ProductID,

    SpecialOfferID,

    UnitPrice,

    UnitPriceDiscount,

    LineTotal,

    rowguid,

    ModifiedDate)

    Note that the optimizer didn't suggest any included columns... The lack of included columns kept the index from being a "covering index" which is what caused the key lookup (and yes, you are correct... They are bad).

    Also, just because a table has a primary key (which does in fact result in an index), doesn't mean that PK's index is appropriate for a given query.

    HTH,

    Jason

  • Great!!! Hat's off to you.

  • Tac11 (6/30/2015)


    Great!!! Hat's off to you.

    Glad to help. 🙂

  • Tac11 (6/30/2015)


    HI Gurus,

    select * from Sales.SalesOrderDetail where SalesOrderDetailID = 6

    When I looked execution plan it gave me missing non clustered index suggestion on 'SalesOrderDetailID' even though it has PK!!!!

    The first column of the PK is not SalesOrderDetailID. It is SalesOrderID.

    So it is like having the phonebook and looking for firstname = john, but it is sorted on surname first, then firstname.

    How would you get all the firstname johns?

    Scan phonebook from page 1 to 100000.

    So, if you are looking for firstname = john, the engine will give you the suggestion to add an index on firstname / SalesOrderDetailID , which you may not have.

    If the lookup cost is disproportionate, due to say an existing index bringing back too many rows resulting in a nested loop lookup on too many rows, then it may be better to cover the index.

    If you are only doing a lookup for on average 1 row, and doing one lookup for a query, then it may not be worth creating a covering index.

    Catch-all queries done right [/url]
    Gail Shaw's Performance Blog[/url]

  • Tac11 (6/30/2015)


    Now I get on execution plan with 'key lookup', 'nested loop' operator, which I think are bad.

    No they're not. There are no bad operators, if there were they wouldn't be in the product. What there are are inappropriate operators for the number of rows the query affects. Since the query in question is going to return a single row, a key lookup is fine.

    The index Jason suggests essentially duplicates the table, it has all columns in either key or include. Since for the query in question, the key lookup is fine, there's little sense in duplicating one of the larger tables in the database, just so that a query can execute in maybe 3 less reads. It would be a waste of space and time.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Jason A. Long (6/30/2015)


    (and yes, you are correct... They are bad).

    They are not. They are inefficient on large row counts.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • GilaMonster (6/30/2015)


    Tac11 (6/30/2015)


    Now I get on execution plan with 'key lookup', 'nested loop' operator, which I think are bad.

    No they're not. There are no bad operators, if there were they wouldn't be in the product. What there are are inappropriate operators for the number of rows the query affects. Since the query in question is going to return a single row, a key lookup is fine.

    The index Jason suggests essentially duplicates the table, it has all columns in either key or include. Since for the query in question, the key lookup is fine, there's little sense in duplicating one of the larger tables in the database, just so that a query can execute in maybe 3 less reads. It would be a waste of space and time.

    GilaMonster (6/30/2015)


    Jason A. Long (6/30/2015)


    (and yes, you are correct... They are bad).

    They are not. They are inefficient on large row counts.

    Both excellent points Gail. Thank you for the correction. 🙂

  • GilaMonster (6/30/2015)


    The index Jason suggests essentially duplicates the table, it has all columns in either key or include. Since for the query in question, the key lookup is fine, there's little sense in duplicating one of the larger tables in the database, just so that a query can execute in maybe 3 less reads. It would be a waste of space and time.

    +1000 to this. It seems like a lot of folks don't understand that a non-clustered index is a duplication of data that not only affects queries and space on disk but also affects backups, time to restore, space on tape, and time for nightly index and stats maintenance not to mention the ridiculous affect that the quantity of indexes has had on all inserts and many updates. We're still cleaning up ridiculously wide indexes both at the B-Tree and the Leaf Level (Includes) left over by the previous regime that were created to remove row lookups for single row queries. It's just stupid how many there are in our system. Many of the indexes were created through the use of DTA and a whole lot of them provided little or no improvement compared to the improvements that are being made to the code itself. Of course, there are many near-duplicate indexes that we're also continuing to consolidate.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (6/30/2015)


    It seems like a lot of folks don't understand that a non-clustered index is a duplication of data that not only affects queries and space on disk but also affects backups, time to restore, space on tape, and time for nightly index and stats maintenance

    And the buffer pool. The pages of the index and the table are different pages, so worse case you can have double the table's size of memory used up by one table.

    Gail Shaw
    Microsoft Certified Master: SQL Server, MVP, M.Sc (Comp Sci)
    SQL In The Wild: Discussions on DB performance with occasional diversions into recoverability

    We walk in the dark places no others will enter
    We stand on the bridge and no one may pass
  • Also, as much as I'm guilty of saying this, it's not about improving the plan. It's about improving the performance of the query. The plan just exposes how the optimizer is resolving your query with the objects in your database. As Gail said, nothing is bad, it just is.

    "The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood"
    - Theodore Roosevelt

    Author of:
    SQL Server Execution Plans
    SQL Server Query Performance Tuning

  • GilaMonster (6/30/2015)


    Jeff Moden (6/30/2015)


    It seems like a lot of folks don't understand that a non-clustered index is a duplication of data that not only affects queries and space on disk but also affects backups, time to restore, space on tape, and time for nightly index and stats maintenance

    And the buffer pool. The pages of the index and the table are different pages, so worse case you can have double the table's size of memory used up by one table.

    Heh... totally forgot about that. That's one of the worst effects especially on smaller systems with less RAM. Thanks, Gail.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • @MadAdmin, when I query:

    select object_name(id) as tablename, name, indid from sysindexes

    where object_name(id) = 'SalesOrderDetail'

    It give me:

    SalesOrderDetail---------ix_SalesOrderIdDetailId ------1 (clustered index)

    I didn't understand why execution plan suggesting me to create non-clustered index on this while the table already has a clustered index!!!

  • Tac11 (6/30/2015)


    @MadAdmin, when I query:

    select object_name(id) as tablename, name, indid from sysindexes

    where object_name(id) = 'SalesOrderDetail'

    It give me:

    SalesOrderDetail---------ix_SalesOrderIdDetailId ------1 (clustered index)

    I didn't understand why execution plan suggesting me to create non-clustered index on this while the table already has a clustered index!!!

    First off, you can't just look at the name of the index to know what's actually in the index definition. You need to look at sys.index_columns to see what columns are part of the key (key_ordinal will tell you the order) and what columns are included (is_included_column = 1).

    Not sure which version on AdventureWorks you're using but I just check the two version I have on my machine (2008 & 2012). Both have 3 indexes on that table...

    PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID(CLUSTERED)

    AK_SalesOrderDetail_rowguid(NONCLUSTERED)

    IX_SalesOrderDetail_ProductID(NONCLUSTERED)

    PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailID has SalesOrderID & SalesOrderDetailID as the key columns (in that order). Which means that the index is sorted by SalesOrderID first then by SalesOrderDetailID.

    HTH,

    Jason

  • I definitely misspoke when I said key lookups are bad as a blanket statement. So, please accept my apology for that.

    That said, they are one of the operators that I look out for when I'm trying to optimize a query and they do prompt me to look at existing indexes that I could possibly add a an included column to, in order to make the index cover the query... (or if it's option, pull the offending column(s) from the select list)

    Then again, if I'm writing a query that only returns a small number of rows, executes in a fraction of a second and won't be executed a gazillion times a day, I'm probably not going to delicate a huge portion of my day trying to make it faster and I certainly wouldn't create a new index for it.

    The answer I supplied was simply an attempt to explain why the op was seeing what they were seeing and to show a different index that would give them what they were originally expecting to see.

Viewing 15 posts - 1 through 15 (of 17 total)

You must be logged in to reply to this topic. Login to reply