Чому SQL Server ігнорує індекс?


16

У мене є таблиця CustPassMasterз 16 стовпцями в ній, одна з яких є CustNum varchar(8), і я створив індекс IX_dbo_CustPassMaster_CustNum. Коли я запускаю свою SELECTзаяву:

SELECT * FROM dbo.CustPassMaster WHERE CustNum = '12345678'

Він повністю ігнорує індекс. Це плутає мене, оскільки у мене є ще одна таблиця CustDataMasterз набагато більшими стовпцями (55), одна з яких є CustNum varchar(8). Я створив індекс у цій колонці ( IX_dbo_CustDataMaster_CustNum) у цій таблиці і використовую практично той самий запит:

SELECT * FROM dbo.CustDataMaster WHERE CustNum = '12345678'

І він використовує створений я індекс.

Чи є за цим якісь конкретні міркування? Чому він би використовував індекс від CustDataMaster, а не той, з якого CustPassMaster? Це пов'язано з низькою кількістю стовпців?

Перший запит повертає 66 рядків. За другий повертається 1 ряд.

Також додаткова примітка: CustPassMasterмає 4991 запис та CustDataMasterмає 5376 записів. Чи може це бути мотивом ігнорування індексу? CustPassMasterтакож має копії записів, які мають однакові CustNumзначення. Це ще один фактор?

Я ґрунтую цю заяву на фактичних результатах плану виконання обох запитів.

Ось DDL для CustPassMaster(той із невикористаним індексом):

CREATE TABLE dbo.CustPassMaster(
    [CustNum] [varchar](8) NOT NULL,
    [Username] [char](15) NOT NULL,
    [Password] [char](15) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustPassMaster_CustNum] ON dbo.CustPassMaster
(
    [CustNum] ASC
) WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

І DDL для CustDataMaster(я пропустив безліч невідповідних полів):

CREATE TABLE dbo.CustDataMaster(
    [CustNum] [varchar](8) NOT NULL,
    /* more columns here */
    [VBTerminator] [varchar](1) NOT NULL
) ON [PRIMARY]

CREATE NONCLUSTERED INDEX [IX_dbo_CustDataMaster_CustNum] ON dbo.CustDataMaster
(
    [CustNum] ASC
)WITH (PAD_INDEX = OFF
    , STATISTICS_NORECOMPUTE = OFF
    , SORT_IN_TEMPDB = OFF
    , DROP_EXISTING = OFF
    , ONLINE = OFF
    , ALLOW_ROW_LOCKS = ON
    , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

У жодної з цих таблиць немає кластерного індексу, лише один некластеризований індекс.

Ігноруйте той факт, що типи даних не повністю відповідають типу даних, що зберігаються. Ці поля є резервною копією з бази даних IBM AS / 400 DB2, і це сумісні типи даних для неї. (Я повинен мати можливість запитувати цю резервну базу даних з точно такими ж запитами та отримувати такі самі результати.)

Ці дані використовуються лише для SELECTтверджень. Я не роблю на ньому жодних INSERT/ UPDATE/ DELETEзаяв, за винятком випадків, коли програма резервного копіювання копіює дані з AS / 400.


Можливо, варто прочитати цю статтю про переломну точку від NonClustered до Clustered. sqlskills.com/blogs/kimberly/the-tipping-point-query-answers
Марк Сінкінсон

3
Тож у цьому різниця. Якщо перший запит використовував ваш індекс, він повинен був виконати 65 пошукових запитів. Це дорого. Другий запит повинен виконувати лише один.
Аарон Бертран

Відповіді:


18

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

Здавалося б, оптимізатор, що базується на витратах, вважає, що реально використовувати індекс, про який йдеться, буде дорожче. Ви можете побачити, що він використовує індекс, якщо замість цього робити SELECT *просто SELECT T1Col1.

Коли ви SELECT *скажете SQL Server повернути всі стовпці таблиці. Щоб повернути ці стовпці, SQL Server повинен прочитати сторінки для рядків, які відповідають WHEREкритеріям оператора з самої таблиці (кластерний індекс або купа). SQL Server, ймовірно, думає про кількість прочитаних, необхідних для отримання решти стовпців із таблиці, що означає, що він може також сканувати таблицю безпосередньо. Було б корисно переглянути фактичний запит та фактичний план виконання, який використовується запитом.


3
Тож для мене більш очевидним та оптимальним рішенням буде обмежити вибрані стовпці та включити їх у INCLUDEпункт індексу?
Der Kommissar

1
Це могло б дуже змінити ситуацію. Додавання всіх стовпців, повернених запитом, до INCLUDEпункту, ймовірно, змусить SQL Server використовувати індекс. Сказавши це, що ви намагаєтеся оптимізувати? Мені здається, якщо ваша таблиця має середній розмір рядків 100 байт, то 5000 рядків - це лише близько 500 кб даних, і, можливо, не варто витрачати на це жодного часу.
Макс Вернон

1
Середній розмір рядка становить 0,30 КБ для Table1, а 0,53 КБ для Table2. Усі ці дані імпортуються з AS / 400 (IBM System i), а ПК ні на що. Я вручну створив усі індекси сьогодні після того, як люди згадували, що додаток часом досить повільний.
Der Kommissar

10

Для використання індексу, оскільки ви це робите select *, тоді SQL Server повинен спершу прочитати кожен рядок із індексу, який відповідає значенню, яке ви маєте в пункті "where". Виходячи з цього, він отримає значення кластерного індексу для кожного ряду, і тоді він повинен шукати кожне з них окремо від кластерного індексу (= пошук ключів). Оскільки ви сказали, що значення не є унікальними, SQL Server використовує статистику, щоб оцінити, скільки разів потрібно зробити цей пошук ключів.

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

Ви можете спробувати використовувати set statistics io on а потім використати підказку, щоб побачити, чи дійсно вартість вводу / виводу менша при використанні індексу чи ні. Якщо різниця велика, ви можете переглянути статистику, якщо вони застаріли.

Крім того, якщо ваш SQL фактично використовує змінні, а не точні значення, це також може бути викликано нюхуванням параметрів (= попереднє значення, яке було використано для створення плану, було в таблиці багато рядків).


1

Це може бути причиною. Оптимізатори ґрунтуються на витратах і вирішують, який шлях вибрати, виходячи з "вартості", яка має кожен шлях виконання. "Найбільша" вартість - це отримання даних з диска в пам'ять. Якщо оптимізатор підрахує, що для читання індексу та даних потрібно більше часу, то він може вирішити пропустити індекс. Чим більше рядків, тим більше блоків дисків вони займають.

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