Індекс не робить виконання швидшим, а в деяких випадках уповільнює запит. Чому так?


34

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

Запит на створення тестової таблиці та заповнення її даними є:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])

Тепер запит 1, який вдосконалюється (лише незначно, але поліпшення є послідовним):

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

Статистика та план виконання без індексу (у цьому випадку в таблиці використовується кластерний індекс за замовчуванням):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 109 ms,  elapsed time = 294 ms.

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

Тепер із включеним індексом:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 94 ms,  elapsed time = 231 ms.

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

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

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name

При включеному кластерному індексі:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17207 ms,  elapsed time = 17337 ms.

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

Зараз з індексом вимкнено:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 17691 ms,  elapsed time = 9073 ms.

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

Питання:

  1. Навіть незважаючи на те, що індекс пропонується сервером SQL, чому він сповільнює істотні відмінності?
  2. Що таке з'єднання вкладеного циклу, яке займає більшу частину часу і як поліпшити час його виконання?
  3. Чи є щось, що я роблю неправильно або пропустив?
  4. Чому за індексом за замовчуванням (лише для первинного ключа) це займає менше часу, а при наявності некластеризованого індексу для кожного рядка таблиці приєднання слід з'єднувати рядок таблиці, що з'єднується, тому що приєднання знаходиться у стовпці Ім'я, на якому індекс створено. Це відображено в плані виконання запитів, і вартість пошуку індексу менша, коли IndexA активний, але чому все-таки повільніше? Крім того, що знаходиться в лівому зовнішньому з'єднанні, який викликає уповільнення?

Використання SQL Server 2012

Відповіді:


23

Навіть незважаючи на те, що індекс пропонується сервером SQL, чому він сповільнює істотні відмінності?

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

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

Що таке з'єднання вкладеного циклу, яке займає більшу частину часу і як поліпшити час його виконання?

Для покращення продуктивності самої поперечної операції мало що потрібно зробити; вкладені петлі - єдина фізична реалізація, можлива для перехресного з'єднання. Шпулька столу на внутрішній стороні з'єднання - це оптимізація, щоб уникнути перестановки внутрішньої сторони для кожного зовнішнього ряду. Чи корисна це оптимізація продуктивності, залежить від різних факторів, але в моїх тестах запит краще без нього. Знову ж таки, це наслідок використання вартісної моделі - мій процесор і система пам'яті, ймовірно, мають інші характеристики продуктивності, ніж ваша. Немає конкретного натяку на запит, щоб уникнути котушки таблиці, але є недокументований прапор сліду (8690), який можна використовувати для тестування продуктивності виконання з і без котушки. Якби це була справжня проблема виробничої системи, план без котушки може бути вимушений, використовуючи керівництво плану на основі плану, виготовленого з включеним TF 8690. Використовувати недокументовані прапорці слідів у виробництві не рекомендується, оскільки установка стає технічно непідтримуваною, а прапорці слідів можуть мати небажані побічні ефекти.

Чи є щось, що я роблю неправильно або пропустив?

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

План сканування

У плані з використанням некластеризованого пошуку індексу робота закінчується повністю одним потоком:

Шукайте план

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

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

Чому за індексом за замовчуванням (лише для первинного ключа) це займає менше часу, а при наявності некластеризованого індексу для кожного рядка таблиці приєднання слід з'єднувати рядок таблиці, що з'єднується, тому що приєднання знаходиться у стовпці Ім'я, на якому індекс створено. Це відображено в плані виконання запитів, і вартість пошуку індексу менша, коли IndexA активний, але чому все-таки повільніше? Крім того, що знаходиться в лівому зовнішньому з'єднанні, який викликає уповільнення?

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

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

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;

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

Оптимальний план

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

Якщо вам цікаво дізнатися більше про внутрішнє виконання паралельних запитів, ви можете переглянути моє записування сесії PASS Summit 2013 .


0

Це насправді не питання індексу, це більше погано написаний запит. У вас є лише 100 унікальних значень імені, це залишає унікальну кількість 5000 на ім’я.

Отже, для кожного рядка таблиці 1 ви приєднуєте 5000 до таблиці 2. Чи можете ви сказати 25020004 рядків.

Спробуйте це, зауважте, це лише 1 індекс, той, який ви перерахували.

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

І час:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
    Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

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

Ви не можете звинувачувати індекси SQL у погано сформованих запитах


1
Дякую за відповідь, і так, запит можна вдосконалити, але логіка мого запитання полягала в тому, що для індексу за замовчуванням (лише для первинного ключа), чому це займає менше часу, і при наявності некластеризованого індексу для кожного рядка в в таблиці приєднання, рядок таблиці з'єднаних слід знайти швидше, що відображається в плані виконання запитів, а вартість пошуку індексу менша, коли IndexA активний, але чому все-таки повільніше? Крім того, що знаходиться в лівому зовнішньому з'єднанні, який викликає уповільнення? Я відредагував питання, щоб додати цей коментар, щоб зробити питання більш зрозумілим.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.