Исключить оператор поиска ключей (кластеризованный), который снижает производительность

16

Как я могу исключить оператор Key Lookup (Clustered) в моем плане выполнения?

Таблица tblQuotesуже имеет кластеризованный индекс (on 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

План выполнения:

введите описание изображения здесь

Serdia
источник
Строки Оценки и Факты показывают заметную разницу. Возможно, SQL выбрал плохой план, потому что у него нет данных для правильной оценки. Как часто вы обновляете свою статистику?
RDFozz

Ответы:

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» входит. Он ищет в таблице идентификатор строки (RID в поиске RID), связанный со строкой, содержащей Table1IDзначение 500000, получая значения из Table1Dataстолбца. Если навести указатель мыши на узел «RID Lookup» в плане, вы увидите следующее:

введите описание изображения здесь

«Список вывода» содержит столбцы, возвращаемые поиском RID.

Таблица с кластеризованным индексом и некластеризованным индексом является интересным примером. Таблица ниже имеет три столбца; Идентификатор, который является ключом кластеризации, 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столбцу, что ничего не говорит о том, какие строки содержатся Testв Datстолбце. Другой вариант (и тот, который выбран 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 некластеризованных индексов.

Этот запрос хотел бы индексы для этих столбцов:

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потому что некоторые из столбцов, которые упоминаются в запросе, отсутствуют в покрывающем индексе.

Вы можете гуглить и изучать Covering Indexили Include index.

В моем примере предположим, что tblQuotes.QuoteStatusID является некластеризованным индексом, тогда я также могу покрыть DisplayStatus. Так как вы хотите DisplayStatus в Resultset. Любой столбец, который отсутствует в индексе и присутствует в наборе результатов, может быть закрыт, чтобы избежать KEY Look Up or Bookmark lookup. Это пример покрывающего индекса:

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

** Отказ от ответственности: ** Помните, что выше - только мой пример. DisplayStatus может быть покрыт другими Non CI после анализа.

Точно так же вам придется создать индекс и индекс покрытия для других таблиц, участвующих в запросе.

Вы получаете Index SCANтакже в своем плане.

Это может произойти из-за отсутствия индекса в таблице или из-за большого объема данных, который оптимизатор может решить сканировать, а не выполнять поиск по индексу.

Это также может произойти из-за High cardinality. Получение большего количества строк, чем требуется из-за неправильного соединения. Это также можно исправить.

KumarHarsh
источник