How can I eliminate a Key Lookup (Clustered) operator in my execution plan?
Table tblQuotes
already has a clustered index (on QuoteID
) and 27 nonclustered indexes, so I am trying not to create any more.
I put the clustered index column QuoteID
in my query, hoping it will help – but unfortunately still the same.
Or view it:
This is what the Key Lookup operator says:
Query:
declare
@EffDateFrom datetime ='2017-02-01',
@EffDateTo datetime ='2017-08-28'
SET NOCOUNT ON
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
IF OBJECT_ID('tempdb..#Data') IS NOT NULL
DROP TABLE #Data
CREATE TABLE #Data
(
QuoteID int NOT NULL, --clustered index
[EffectiveDate] [datetime] NULL, --not indexed
[Submitted] [int] NULL,
[Quoted] [int] NULL,
[Bound] [int] NULL,
[Exonerated] [int] NULL,
[ProducerLocationId] [int] NULL,
[ProducerName] [varchar](300) NULL,
[BusinessType] [varchar](50) NULL,
[DisplayStatus] [varchar](50) NULL,
[Agent] [varchar] (50) NULL,
[ProducerContactGuid] uniqueidentifier NULL
)
INSERT INTO #Data
SELECT
tblQuotes.QuoteID,
tblQuotes.EffectiveDate,
CASE WHEN lstQuoteStatus.QuoteStatusID >= 1 THEN 1 ELSE 0 END AS Submitted,
CASE WHEN lstQuoteStatus.QuoteStatusID = 2 or lstQuoteStatus.QuoteStatusID = 3 or lstQuoteStatus.QuoteStatusID = 202 THEN 1 ELSE 0 END AS Quoted,
CASE WHEN lstQuoteStatus.Bound = 1 THEN 1 ELSE 0 END AS Bound,
CASE WHEN lstQuoteStatus.QuoteStatusID = 3 THEN 1 ELSE 0 END AS Exonareted,
tblQuotes.ProducerLocationID,
P.Name + ' / '+ P.City as [ProducerName],
CASE WHEN tblQuotes.PolicyTypeID = 1 THEN 'New Business'
WHEN tblQuotes.PolicyTypeID = 3 THEN 'Rewrite'
END AS BusinessType,
tblQuotes.DisplayStatus,
tblProducerContacts.FName +' '+ tblProducerContacts.LName as Agent,
tblProducerContacts.ProducerContactGUID
FROM tblQuotes
INNER JOIN lstQuoteStatus
on tblQuotes.QuoteStatusID=lstQuoteStatus.QuoteStatusID
INNER JOIN tblProducerLocations P
On P.ProducerLocationID=tblQuotes.ProducerLocationID
INNER JOIN tblProducerContacts
ON dbo.tblQuotes.ProducerContactGuid = tblProducerContacts.ProducerContactGUID
WHERE DATEDIFF(D,@EffDateFrom,tblQuotes.EffectiveDate)>=0 AND DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <=0
AND dbo.tblQuotes.LineGUID = '6E00868B-FFC3-4CA0-876F-CC258F1ED22D'--Surety
AND tblQuotes.OriginalQuoteGUID is null
select * from #Data
Execution Plan:
Best Answer
Key lookups of various flavors occur when the query processor needs to obtain values from columns that are not stored in the index used to locate the rows required for the query to return results.
Take for example the following code, where we're creating a table with a single index:
We'll insert 1,000,000 rows into the table so we have some data to work with:
Now, we'll query the data with the option to display the "actual" execution plan:
The query plan shows:
The query looks at the
IX_Table1
index to find the row withTable1ID = 5000000
since looking at that index is much faster than scanning the entire table looking for that value. However, to satisfy the query results, the query processor must also find the value for the other columns in the table; this is where the "RID Lookup" comes in. It looks in the table for the row ID (the RID in RID Lookup) associated with the row containing theTable1ID
value of 500000, obtaining the values from theTable1Data
column. If you hover the mouse over the "RID Lookup" node in the plan, you see this:The "Output List" contains the columns returned by the RID Lookup.
A table with a clustered index and a non-clustered index makes an interesting example. The table below has three columns; ID which is the clustering key,
Dat
which is indexed by a non-clustered indexIX_Table
, and a third column,Oth
.Take this example query:
We're asking SQL Server to return every column from the table where the
Dat
column contains the wordTest
. We have a couple of choices here; we can look at the table (i.e. the clustered index) - but that would entail scanning the entire thing since the table is ordered by theID
column, which tells us nothing about which row(s) containTest
in theDat
column. The other option (and the one chosen by SQL Server) consists of seeking into theIX_Table1
non-clustered index to find the row whereDat = 'Test'
, however since we need theOth
column as well, SQL Server must perform a lookup into the clustered index using a "Key Lookup" operation. This is the plan for that:If we modify the non-clustered index so that it includes the
Oth
column:Then re-run the query:
We now see a single non-clustered index seek since SQL Server simply needs to locate the row where
Dat = 'Test'
in theIX_Table1
index, which includes the value forOth
, and the value for theID
column (the primary key), which is automatically present in every non-clustered index. The plan: