Усуньте оператор пошуку ключів (кластер), який уповільнює продуктивність


16

Як я можу усунути оператор пошуку ключів (кластерних) у своєму плані виконання?

У таблиці 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

План виконання:

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


Рядки "Оцінене та фактичне" показують помітну різницю. Можливо, вибір 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" у плані, ви побачите це:

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

"Список вихідних даних" містить стовпці, повернуті пошуком 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стовпцем, що нічого не говорить про те, які рядки містяться Testв Datстовпці. Інший варіант (і той, який вибрав SQL Server) полягає у пошуку в IX_Table1некластеризований індекс, щоб знайти рядок, де 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

Потім повторно запустіть запит:

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 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 після аналізу може бути покритий іншими неідентифікованими ІС.

Аналогічно вам доведеться створити індекс та індекс, що охоплює інші таблиці, що беруть участь у запиті.

Ви також входите Index SCANу свій план.

Це може статися тому, що в таблиці немає індексу або коли є великий обсяг даних, оптимізатор може вирішити сканувати, а не виконувати пошук індексів.

Це також може статися через High cardinality. Отримання більшої кількості рядків, ніж потрібно через несправне з'єднання. Це також можна виправити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.