Чому мій пункт WHERE має перевагу з стовпця "включено"?


12

Відповідно до цієї відповіді , якщо індекс не побудований над стовпцями, які використовуються для обмеження, запит не матиме користі від індексу.

У мене таке визначення:

CREATE TABLE [dbo].[JobItems] (
    [ItemId]             UNIQUEIDENTIFIER NOT NULL,
    [ItemState]          INT              NOT NULL,
    [ItemPriority]       INT NOT NULL,
    [CreationTime]       DATETIME         NULL DEFAULT GETUTCDATE(),
    [LastAccessTime]     DATETIME         NULL DEFAULT GETUTCDATE(),
     -- other columns
 );

 CREATE UNIQUE CLUSTERED INDEX [JobItemsIndex]
    ON [dbo].[JobItems]([ItemId] ASC);
 GO

CREATE INDEX [GetItemToProcessIndex]
    ON [dbo].[JobItems]([ItemState], [ItemPriority], [CreationTime])
    INCLUDE (LastAccessTime);
GO

і цей запит:

UPDATE TOP (150) JobItems 
SET ItemState = 17 
WHERE 
    ItemState IN (3, 9, 10)
    AND LastAccessTime < DATEADD (day, -2, GETUTCDATE()) 
    AND CreationTime < DATEADD (day, -2, GETUTCDATE());

Я переглянув фактичний план, і є лише один індекс, який шукає з присудком точно так само, як і в WHERE- немає додаткових "пошукових запитів" закладки для отримання, LastAccessTimeхоча останній лише "включений" в індекс, а не частина індексу.

Мені здається, що така поведінка суперечить правилу, що стовпець повинен бути частиною індексу, а не просто "включений".

Чи правильна я поведінка, яку я спостерігаю? Як я можу дізнатися заздалегідь, якщо мої WHEREвигоди від включеного стовпця чи потрібен стовпець, що є частиною індексу?


Це все ще може шукати на основі ItemStateзначення, однак пошук не буде настільки ефективним, як якщо б ваш індекс був структурований так(ItemState, CreationTime, LastAccessTime)
Марк Сінкінсон

1
@MarkSinkinson або просто(ItemState, CreationTime) INCLUDE (LastAccessTime)
ypercubeᵀᴹ

@sharptooth пов'язаний з вами відповідь не говорить про це ("якщо індекс не побудований над стовпцями, які використовуються для обмеження запиту, не отримає користі від індексу"). Це говорить про те, що індекс на (a,b)не найкращий для запиту, SELECT a FROM t WHERE b=5;а індекс на (b) INCLUDE (a)набагато краще.
ypercubeᵀᴹ

Відповіді:


9

Ваш предикат відрізняється від вашого предикату на пошук.

Шукати предикат використовується для пошуку впорядкованих даних в індексі. У цьому випадку це буде робити три пошуки, по одному для кожного пункту, що вас цікавить. Крім того, дані в порядку "ItemPriority", тому більше не потрібно робити операцію "Шукати".

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

Будь-який включений стовпець не є частиною упорядкованих даних, але може бути використаний для задоволення Залишкового предиката, не потребуючи додаткового пошуку.

Ви можете бачити матеріал, про який я писав, навколо Sargability. Перевірте наявність сеансу в SQLBits, зокрема, за адресою http://bit.ly/Sargability

Редагувати: Щоб краще показати вплив Залишків, запустіть запит за допомогою недокументованого документа OPTION (QUERYTRACEON 9130), який відокремить Залишковий на окремому операторі фільтра (що фактично є більш ранньою версією плану до того, як залишок буде переміщений в оператор Seek). Це чітко показує вплив неефективного пошуку за кількістю рядків, переданих до фільтра.

Варто також зазначити, що через пункт IN про ItemState дані, передані ліворуч, фактично в порядку ItemState, а не в порядку ItemPriority. Складений індекс на ItemState, який слідує за однією з дат (наприклад, (ItemState, LastAccessTime)), може використовуватися для трьох шукань (примітка Seeicate предикат показує три пошуки в межах одного оператора Seek), кожен проти двох рівнів, створюючи дані, які є ще в порядку ItemState (наприклад, ItemState = 3 і LastAccessTime менше ніж щось, потім ItemState = 9 і LastAccessTime менше ніж щось, а потім ItemState = 10 і LastAccessTime менше ніж щось).

Індекс на (ItemState, LastAccesTime, CreationTime) не буде кориснішим, ніж один на (ItemState, LastAccessTime), оскільки рівень CreationTime корисний лише у тому випадку, якщо ваш Seek призначений для певної комбінації ItemState та LastAccessTime, а не діапазону. Як і те, як телефонна книга не в порядку FirstName, якщо вас цікавлять прізвища, що починаються з F.

Якщо ви хочете складений індекс, але ви ніколи не зможете використовувати пізніші стовпці в "Шукати предикати" через спосіб використання попередніх стовпців, то ви також можете мати їх як включені стовпці, де вони займають менше місця в індекс (тому що вони зберігаються лише на рівні аркушів індексу, а не на вищих рівнях), але все одно можуть уникнути пошуку і звикнути до залишкових предикатів.

За терміном Залишковий предикат - це мій власний термін для цього властивості Seek. Злиття приєднання явно називає його еквівалентом Залишковим предикатом, а Hash Match називає його зондом Залишком (який ви можете отримати від TSA, якщо ви відповідаєте на хеш). Але в Seek вони просто називають це предикатом, що робить його здається менш поганим, ніж це є.


3

GetItemToProcessIndex не повністю доступний, тому що ваш пункт де ввімкнено ItemState + LastAccessTime + CreationTime. Індексовані стовпці та пункт де не є ідеальним збігом.

Якщо ви створюєте індекс покриття на ItemState + LastAccessTime + CreationTimeкожен матч, отриманий від GetItemToProcessIndex, ви також отримуєте значення свого первинного ключа (ItemId). Потрібно лише переконатися, що 2-е побачення - збіг.

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

З вашим поточним індексом він може допомогти серверу знайти рядки з потрібним пунктом ItemState, але він все одно повинен прочитати їх усі з індексу, щоб знайти правильні збіги на LastAccessTime + CreationTime. Залежно від предикатів дати та розміру відповідного набору та того, що має бути виключено, це може призвести до набагато більше IO, ніж ідеально охоплюючий індекс лише на 3 стовпці, який шукатиме ItemState та другий стовпець (1-а індексована дата) . Друга дата в індексованому періоді може бути включена. Додаткові стовпці не повинні індексуватися між цими 3, хоча це може бути нормальним як 4-й стовпчик (див. Відповідь розбіжника про додаткові стовпці).

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