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


16

如何在执行计划中消除键查找(集群)运算符?

tblQuotes已经有一个聚集索引(QuoteID)和27个非聚集索引,因此我尝试不再创建任何索引。

QuoteID在查询中放入了聚集索引列,希望对您有所帮助-但不幸的是还是一样。

执行计划在这里

或查看它:

在此处输入图片说明

这就是“关键点查找”运算符所说的:

在此处输入图片说明

查询:

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

执行计划:

在此处输入图片说明


估计行与实际行显示出显着差异。也许SQL选择了一个错误的计划,因为它没有足够的数据来进行良好的估算。您多久更新一次统计信息?
RDFozz

Answers:


23

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

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

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

我们将向表中插入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

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

SELECT *
FROM dbo.Table1
WHERE Table1ID = 500000;

查询计划显示:

在此处输入图片说明

该查询查看IX_Table1索引以查找行,Table1ID = 5000000因为查看该索引比扫描整个表以查找该值快得多。但是,为了满足查询结果,查询处理器还必须找到表中其他列的值。这是“ RID查找”的来源。它在表中查找与包含Table1ID500000 的行相关联的行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

以以下示例查询:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

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

在此处输入图片说明

如果我们修改了非聚集索引,以便它包含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

然后重新运行查询:

SELECT *
FROM dbo.Table1
WHERE Dat = 'Test';

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

在此处输入图片说明


6

由于引擎选择使用不包含您尝试获取的所有列的索引,因此导致了键查找。因此,索引未覆盖select和where语句中的列。

要消除键查找,您需要包括缺少的列(键查找的“输出”列表中的列)= ProducerContactGuid,QuoteStatusID,PolicyTypeID和ProducerLocationID,或者另一种方式是强制查询使用聚簇索引。

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


还要注意,太多的索引可能会使执行计划的编制混乱,并可能导致选择不佳。

4

您忘了提及此查询涉及的数据量。另外,为什么要插入到临时表中?如果仅需要显示,则不要运行插入语句。

出于此查询的目的,tblQuotes不需要27个非聚集索引。它需要1个聚集索引和5个非聚集索引,或者可能需要6个非聚集indexex。

该查询想要这些列上的索引:

QuoteStatusID
ProducerLocationID
ProducerContactGuid
EffectiveDate
LineGUID
OriginalQuoteGUID

我还注意到以下代码:

DATEDIFF(D, @EffDateFrom, tblQuotes.EffectiveDate) >= 0 AND 
DATEDIFF(D, @EffDateTo, tblQuotes.EffectiveDate) <= 0

NON Sargable即它不能利用索引。

要使该代码SARgable更改为此:

tblQuotes.EffectiveDate >= @EffDateFrom 
AND  tblQuotes.EffectiveDate <= @EffDateFrom

要回答您的主要问题,“为什么要查找键”:

你得到KEY Look up,因为其中有些是提到在查询中的列不存在于一个覆盖索引。

您可以在Google上搜索Covering IndexInclude index

在我的示例中,假设tblQuotes.QuoteStatusID为非聚集索引,那么我也可以介绍DisplayStatus。由于您想要在Displayset中使用DisplayStatus。可以覆盖索引中不存在但结果集中存在的任何列,以避免出现KEY Look Up or Bookmark lookup。这是一个覆盖索引的示例:

create nonclustered index tblQuotes_QuoteStatusID 
on tblQuotes(QuoteStatusID)
include(DisplayStatus);

**免责声明:**上面仅是我的示例,在分析后,DisplayStatus可能包含其他Non CI。

同样,您将必须在查询所涉及的其他表上创建索引和覆盖索引。

您也正在Index SCAN计划中。

之所以会发生这种情况,是因为表上没有索引,或者当有大量数据时,优化器可能决定扫描而不是执行索引查找。

这也可能是由于引起的High cardinality。由于错误的连接而导致的行数超出了要求。这也可以纠正。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.