Индекс не ускоряет выполнение, а в некоторых случаях замедляет запрос. Почему это так?


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 Server предлагает индекс, почему он существенно замедляется?
  2. Что такое объединение Nested Loop, которое занимает большую часть времени и как улучшить время его выполнения?
  3. Есть ли что-то, что я делаю неправильно или пропустил?
  4. При использовании индекса по умолчанию (только для первичного ключа), почему это занимает меньше времени, и при наличии некластеризованного индекса для каждой строки в соединяемой таблице строка объединенной таблицы должна быть найдена быстрее, поскольку объединение выполняется в столбце Имя, для которого Индекс был создан. Это отражено в плане выполнения запроса, и стоимость поиска по индексу меньше, когда IndexA активен, но почему все еще медленнее? Кроме того, что в левом внешнем соединении Nested Loop вызывает замедление?

Использование SQL Server 2012

Ответы:


23

Несмотря на то, что SQL Server предлагает индекс, почему он существенно замедляется?

Индексные предложения составляются оптимизатором запросов. Если он сталкивается с логическим выбором из таблицы, которая плохо обслуживается существующим индексом, он может добавить предложение «отсутствующий индекс» к своим выводам. Эти предложения являются оппортунистическими; они не основаны на полном анализе запроса и не учитывают более широких соображений. В лучшем случае они указывают на то, что возможна более полезная индексация, и на это должен обратить внимание квалифицированный администратор баз данных.

Еще одна вещь, которую нужно сказать о пропущенных предложениях по индексам, заключается в том, что они основаны на модели затрат оптимизатора, и оптимизатор оценивает, насколько предложенный индекс может снизить предполагаемую стоимость запроса. Ключевыми словами здесь являются «модель» и «оценки». Оптимизатор запросов мало знает о конфигурации вашего оборудования или других параметрах конфигурации системы - его модель в значительной степени основана на фиксированных числах, которые в большинстве случаев дают разумные результаты плана для большинства людей в большинстве систем. Помимо проблем с точными числами затрат, результаты всегда являются оценочными - и оценки могут быть неправильными.

Что такое объединение Nested Loop, которое занимает большую часть времени и как улучшить время его выполнения?

Для улучшения производительности самой операции перекрестного соединения мало что можно сделать; Вложенные циклы - единственная возможная физическая реализация для перекрестного соединения. Катушка с таблицей на внутренней стороне соединения - это оптимизация, позволяющая избежать повторного сканирования внутренней стороны для каждого внешнего ряда. Является ли это полезной оптимизацией производительности, зависит от различных факторов, но в моих тестах запрос лучше без него. Опять же, это является следствием использования модели затрат - мой процессор и система памяти, скорее всего, имеют характеристики производительности, отличные от ваших. Для предотвращения спулинга в таблице нет специального указания на запрос, но есть недокументированный флаг трассировки (8690), который можно использовать для тестирования производительности выполнения с спулом и без него. Если бы это была настоящая проблема производственной системы, План без катушки можно было принудительно использовать с помощью руководства плана, основанного на плане, созданном с включенным TF 8690. Использование недокументированных флагов трассировки в производстве не рекомендуется, поскольку установка становится технически неподдерживаемой, а флаги трассировки могут иметь нежелательные побочные эффекты.

Есть ли что-то, что я делаю неправильно или пропустил?

Главное, что вам не хватает, - это то, что, хотя план, использующий некластеризованный индекс, имеет более низкую оценочную стоимость в соответствии с моделью оптимизатора, он имеет существенную проблему времени выполнения. Если вы посмотрите на распределение строк по потокам в плане с помощью кластерного индекса, вы, вероятно, увидите достаточно хорошее распределение:

План сканирования

В плане, использующем поиск некластеризованного индекса, работа завершается полностью одним потоком:

Искать план

Это является следствием того, как работа распределяется между потоками с помощью параллельных операций сканирования / поиска. Не всегда случай, когда параллельное сканирование будет распределять работу лучше, чем поиск по индексу, - но в этом случае это происходит. Более сложные планы могут включать перераспределение обменов для перераспределения работы между потоками. Этот план не имеет таких обменов, поэтому, как только строки назначены потоку, вся связанная работа выполняется в том же потоке. Если вы посмотрите на распределение работы для других операторов в плане выполнения, вы увидите, что вся работа выполняется в том же потоке, как показано для поиска по индексу.

Нет подсказок для запроса, влияющих на распределение строк между потоками, важно знать о возможности и уметь читать достаточно подробно в плане выполнения, чтобы определить, когда это вызывает проблему.

При использовании индекса по умолчанию (только для первичного ключа), почему это занимает меньше времени, и при наличии некластеризованного индекса для каждой строки в соединяемой таблице строка объединенной таблицы должна быть найдена быстрее, поскольку объединение выполняется в столбце Имя, для которого Индекс был создан. Это отражено в плане выполнения запроса, и стоимость поиска по индексу меньше, когда IndexA активен, но почему все еще медленнее? Кроме того, что в левом внешнем соединении Nested Loop вызывает замедление?

Теперь должно быть ясно, что план некластеризованного индекса потенциально более эффективен, как и следовало ожидать; это просто плохое распределение работы между потоками во время выполнения, что объясняет проблему производительности.

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

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;

Это приводит к плану, который использует более эффективный поиск индекса, не имеет буфера таблицы и хорошо распределяет работу по потокам:

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

В моей системе этот план выполняется значительно быстрее, чем версия Clustered Index Scan.

Если вы хотите узнать больше о внутренностях параллельного выполнения запросов, вы можете посмотреть мою запись сеанса 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 активен, но почему все еще медленнее? Кроме того, что в левом внешнем соединении Nested Loop вызывает замедление? Я отредактировал вопрос, чтобы добавить этот комментарий, чтобы сделать вопрос более ясным.
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.