План виконання НЕ використовує INDEX, він використовує сканування таблиць


9

Я знаю, коли мова йде про використання індексу або сканування таблиці, SQL Server використовує статистику, щоб побачити, який з них краще.

У мене є таблиця з 20 мільйонами рядків. У мене індекс на (SnapshotKey, Measure) і цей запит:

select Measure, SnapshotKey, MeasureBand
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

Запит повертає 500k рядків. Таким чином, запит вибирає лише 2,5% рядків таблиці.

Питання в тому, чому SQL Server не використовує наявний у мене некластеризований індекс, а замість цього використовує сканування таблиці?

Статистика оновлюється.

Хоча згадати, що ефективність запиту хороша.

Сканування таблиці

Сканування таблиці

Примусовий індекс

Індекс сили

Структура таблиці / індексу

CREATE TABLE [t1](
    [SnapshotKey] [int] NOT NULL,
    [SnapshotDt] [date] NOT NULL,
    [Measure] [nvarchar](30) NOT NULL,
    [MeasureBand] [nvarchar](30) NOT NULL,
    -- and many more fields
) ON [PRIMARY]

У таблиці немає ПК, оскільки це сховище даних.

CREATE NONCLUSTERED INDEX [nci_SnapshotKeyMeasure] ON [t1]
(
    [SnapshotKey] ASC,
    [Measure] ASC
)

Відповіді:


16

Шукання в індексах може бути не найкращим вибором, якщо ви повернете багато рядків та / або рядки дуже широкі. Пошук може бути дорогим, якщо ваш індекс не покриває. Дивіться №2 тут .

У вашому сценарії оптимізатор запитів підраховує, що виконання 50 000 окремих пошукових запитів буде дорожчим за одно сканування. Вибір оптимізатора між скануванням і пошуком (з пошуковими запитами RID для стовпців, необхідних для запиту, але відсутні в некластеризованому індексі) базується на оціночній вартості кожної альтернативи.

Оптимізатор завжди вибирає альтернативу з найнижчою вартістю, яку він вважає. Якщо ви подивитесь на властивість Estimated Subtree Cost у кореневому вузлі двох планів виконання, ви побачите, що план сканування має нижчу орієнтовну вартість, ніж план пошуку. В результаті оптимізатор вибрав сканування. Це по суті відповідь на ваше запитання.

Тепер модель витрат, що використовується оптимізатором, ґрунтується на припущеннях та "магічних числах", які навряд чи можуть відповідати характеристикам продуктивності вашої системи. Зокрема, одне припущення, зроблене в моделі, полягає в тому, що запит починає виконувати жодну з необхідних сторінок даних або покажчиків, які вже є в пам'яті. Інше полягає в тому, що послідовне введення / виведення (очікуване на сканування) дешевше, ніж випадкова схема вводу / виводу, яка передбачається для пошуку RID. Існує багато інших таких припущень і застережень, тут занадто багато, щоб тут детально деталізувати.

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

Обмеження моделі та інші фактори іноді означають, що оптимізатор вибирає план, який насправді не є "досить хорошим". Ви повідомляєте, що "ефективність хороша", так що, здається, це не так.


9

Насправді у вас 595 947 відповідних рядків, що становить приблизно 3% ваших даних. Таким чином, вартість пошуку швидко збільшується. Припустимо, у вас в таблиці 100 рядків на сторінку, це 200 000 сторінок для читання при скануванні таблиці. Це набагато дешевше, ніж робити 595 947 пошуку.

З GROUP BYпунктом у запитанні, я думаю, вам буде краще скласти клавішу (Measure, SnapshotKey, MeasureBand).

Подивіться на пропозицію "пропущений індекс". Він повідомляє вам включати стовпці, щоб уникнути пошуку. Загалом, якщо ви посилаєтесь на інші стовпці у вашому запиті, вони повинні бути в ключах або в INCLUDEпункті нового індексу. В іншому випадку для отримання цих значень все одно знадобиться провести 595 947 пошуку.

Наприклад, для запиту:

select Measure, SnapshotKey, MeasureBand, SUM(NumLoans), SUM(PrinBal)
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand

... вам знадобиться:

CREATE INDEX ixWhatever 
ON t1 (Measure, SnapshotKey, MeasureBand) 
INCLUDE (NumLoans,PrinBal);

6
  1. Поле у ​​вашому стані WHERE не є провідним полем індексу.

  2. Ви measureвизначені як NVARCHAR так префікс буквальний з N: where Measure = N'FinanceFICOScore'.

Подумайте про створення кластерного індексу на SnapshotKey. Якщо він унікальний, то він може бути ПК (і кластером). Якщо не унікальний, то він не може бути ПК, але все ж може бути не унікальним кластерним індексом. Тоді ваш некластеризований індекс був би лише у measureстовпці.

І, враховуючи, що першим полем у також GROUP BYє measure, це також виграло б від того, що воно measureбуде провідним.

Насправді для цієї операції вам може знадобитися замість цього визначити NonClustered Index Measure, SnapshotKey, MeasureBandу такому точному порядку, як він відповідає GROUP BYпункту. Розмірно розмір, який дійсно додається, MeasureBandоскільки індекс NonClustered вже заснований Measureі MeasureKeyвже включений в індекс, так як це зараз клавіша кластера Index (ні, Measureне буде дублюватися в індексі NonClustered).

@Rob в своєму видаленому коментарі згадував, що для вирішення цього питання потрібно лише визначити Індекс некластеризованих цих трьох полів у цьому порядку, а також створювати кластерний (не унікальний) індекс для SnapshotKeyцього не потрібно . Хоча він, мабуть, правильний (я сподівався, що менша кількість полів спрацює), я все-таки заперечую, що наявність індексу кластеру вигідна не тільки для цієї операції, але, ймовірно, для більшості інших.


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