消除会降低性能的键查找(集群)运算符

Ser*_*dia 24 performance sql-server execution-plan sql-server-2012 bookmark-lookup query-performance

如何消除执行计划中的 Key Lookup (Clustered) 运算符?

tblQuotes已经有一个聚集索引 (on QuoteID) 和 27 个非聚集索引,所以我不想再创建了。

我将聚集索引列QuoteID放在我的查询中,希望它会有所帮助 - 但不幸的是仍然相同。

执行计划在这里

或查看它:

在此处输入图片说明

这是 Key Lookup 运算符所说的:

在此处输入图片说明

询问:

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
Run Code Online (Sandbox Code Playgroud)

执行计划:

在此处输入图片说明

Han*_*non 34

当查询处理器需要从未存储在用于定位查询返回结果所需的行的索引中的列中获取值时,就会出现各种类型的键查找。

以下面的代码为例,我们在其中创建了一个带有单个索引的表:

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    Table1ID int NOT NULL IDENTITY(1,1)
    , Table1Data nvarchar(30) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Table1ID);
GO
Run Code Online (Sandbox Code Playgroud)

我们将向表中插入 1,000,000 行,因此我们有一些数据可以处理:

INSERT INTO dbo.Table1 (Table1Data)
SELECT TOP(1000000) LEFT(c.name, 30)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO
Run Code Online (Sandbox Code Playgroud)

现在,我们将使用显示“实际”执行计划的选项来查询数据:

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;
Run Code Online (Sandbox Code Playgroud)

查询计划显示:

在此处输入图片说明

查询查看IX_Table1索引以查找行,Table1ID = 5000000因为查看该索引比扫描整个表查找该值要快得多。但是,为了满足查询结果,查询处理器还必须找到表中其他列的值;这就是“RID 查找”的用武之地。它在表中查找与包含Table1ID值 500000的行相关联的行 ID(RID 查找中的 RID),从列中获取值Table1Data。如果将鼠标悬停在计划中的“RID 查找”节点上,您会看到:

在此处输入图片说明

“输出列表”包含 RID 查找返回的列。

具有聚集索引和非聚集索引的表是一个有趣的例子。下表包含三列;ID 是聚簇键,Dat由非聚簇索引IX_Table和第三列Oth.

USE tempdb;

IF OBJECT_ID(N'dbo.Table1', N'U') IS NOT NULL
DROP TABLE dbo.Table1
GO

CREATE TABLE dbo.Table1
(
    ID int NOT NULL IDENTITY(1,1) 
        PRIMARY KEY CLUSTERED
    , Dat nvarchar(30) NOT NULL
    , Oth nvarchar(3) NOT NULL
);

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat);
GO

INSERT INTO dbo.Table1 (Dat, Oth)
SELECT TOP(1000000) CRYPT_GEN_RANDOM(30), CRYPT_GEN_RANDOM(3)
FROM sys.columns c
    CROSS JOIN sys.columns c1
    CROSS JOIN sys.columns c2;
GO
Run Code Online (Sandbox Code Playgroud)

以这个示例查询为例:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';
Run Code Online (Sandbox Code Playgroud)

我们要求 SQL Server 返回表中Dat包含单词 的每一列Test。我们在这里有几个选择;我们可以看一下表(即聚集索引) -但是这将需要扫描整个事情,因为该表由有序ID列,它并没有告诉我们这行(S)包含TestDat列。另一个选项(以及 SQL Server 选择的选项)包括在IX_Table1非聚集索引中查找行 where Dat = 'Test',但是由于我们也需要Oth列,SQL Server 必须使用“Key查找”操作。这是为此的计划:

在此处输入图片说明

如果我们修改非聚集索引以使其包含Oth列:

DROP INDEX IX_Table1
ON dbo.Table1;
GO

CREATE INDEX IX_Table1
ON dbo.Table1 (Dat)
INCLUDE (Oth);        <---- This is the only change
GO
Run Code Online (Sandbox Code Playgroud)

然后重新运行查询:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';
Run Code Online (Sandbox Code Playgroud)

我们现在看到一个非群集索引查找,因为SQL Server的只是需要找到其中行Dat = 'Test'IX_Table1索引,其中包括价值Oth,并为值ID列(主键),这是每一个非自动存在聚集索引。计划:

在此处输入图片说明


Dan*_*örk 7

键查找是因为引擎选择使用的索引不包含您尝试获取的所有列。所以索引没有覆盖 select 和 where 语句中的列。

要消除键查找,您需要包含缺失的列(键查找的输出列表中的列)= ProducerContactGuid、QuoteStatusID、PolicyTypeID 和 ProducerLocationID,或者另一种方法是强制查询改用聚集索引。

请注意,表上的 27 个非聚集索引可能对性能不利。运行更新、插入或删除时,SQL Server 必须更新所有索引。这种额外的工作可能会对性能产生负面影响。