8.3.26

Фильтрованные индексы: сравнение производительности с примерами (Часть 2)

Автор: Luca Biondi, Filtered Index vs Indexed View in SQL Server: Complete Performance Comparison with Real Examples

Эта статья является продолжением предыдущего глубокого погружения в настройку производительности SQL Server!

Если вы пропустили Часть 1, вы можете прочитать её здесь:

Как фильтрованные индексы кардинально улучшают производительность запросов (часть 1)

В первой статье мы проанализировали, как фильтрованные индексы могут кардинально сократить логические чтения и оптимизировать планы выполнения.

Сегодня мы углубляемся и сравниваем фильтрованный индекс с индексированным представлением, используя практические, воспроизводимые SQL-скрипты.

Вы найдёте конкретные сценарии, которые сможете протестировать в своей собственной лаборатории — потому что настройка производительности — это не теория, это экспериментирование.

Понимание ключевых различий

Фильтрованный индекс

  • Индексирует только подмножество строк с помощью предложения WHERE
  • Не выполняет предварительную агрегацию данных
  • Низкие накладные расходы на обслуживание
  • Идеален для сценариев с выборочной фильтрацией

Индексированное представление

  • Физически материализует результаты запроса
  • Может предварительно агрегировать данные
  • Требует уникального кластерного индекса
  • Более высокие накладные расходы на запись

Сценарий 1 – Агрегация на неравномерно распределённых данных

Давайте смоделируем типичную OLTP-таблицу с неравномерным распределением.

CREATE TABLE dbo.Sales
(
    SaleID INT IDENTITY PRIMARY KEY,
    CustomerID INT,
    SaleDate DATE,
    Status VARCHAR(10),
    Amount MONEY
);

WITH Numbers AS (
    SELECT TOP (1000000)
        ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n
    FROM sys.objects a CROSS JOIN sys.objects b
)
INSERT INTO dbo.Sales (CustomerID, SaleDate, Status, Amount)
SELECT 
    ABS(CHECKSUM(NEWID())) % 5000,
    DATEADD(DAY, -ABS(CHECKSUM(NEWID())) % 365, GETDATE()),
    CASE WHEN n % 20 = 0 THEN 'Active' ELSE 'Closed' END,
    RAND(CHECKSUM(NEWID())) * 1000
FROM Numbers;

Только 5% строк имеют статус 'Active'.

Оптимизируемый запрос

SELECT CustomerID, SUM(Amount) AS TotalAmount
FROM dbo.Sales
WHERE Status = 'Active'
GROUP BY CustomerID;

Этот запрос:

  • Фильтрует небольшое подмножество
  • Выполняет агрегацию
  • Может вызывать хеш-переполнения (hash spills)
  • Потребляет tempdb в условиях нагрузки

Вариант 1 – Фильтрованный индекс

CREATE NONCLUSTERED INDEX IX_Sales_Active
ON dbo.Sales (CustomerID)
INCLUDE (Amount)
WHERE Status = 'Active';

Результат:

  • Поиск по индексу (Index Seek) на фильтрованных строках
  • Агрегация всё ещё вычисляется во время выполнения
  • Сокращённые логические чтения

Вариант 2 – Индексированное представление

CREATE VIEW dbo.vw_ActiveSales
WITH SCHEMABINDING
AS
SELECT CustomerID,
       SUM(Amount) AS TotalAmount,
       COUNT_BIG(*) AS RecordCount
FROM dbo.Sales
WHERE Status = 'Active'
GROUP BY CustomerID;
GO

CREATE UNIQUE CLUSTERED INDEX IX_vw_ActiveSales
ON dbo.vw_ActiveSales(CustomerID);

Результат:

  • Агрегация предварительно вычислена
  • Нет затрат на GROUP BY во время выполнения
  • Минимальное использование CPU

Сценарий 1 – Результаты производительности

СценарийЛогические чтенияCPU (мс)Время выполнения (мс)
Без оптимизации180 00014502100
Фильтрованный индекс8 500220310
Индексированное представление1 2004060

Сценарий 2 – Рабочая нагрузка только с фильтрацией

Теперь протестируем запрос только с фильтрацией.

SELECT SaleID, CustomerID, Amount
FROM dbo.Sales
WHERE Status = 'Active'
AND SaleDate >= DATEADD(DAY, -30, GETDATE());

Фильтрованный индекс, оптимизированный для фильтрации

CREATE NONCLUSTERED INDEX IX_Sales_Active_Date
ON dbo.Sales (SaleDate)
INCLUDE (SaleID, CustomerID, Amount)
WHERE Status = 'Active';

План выполнения:

  • Чистый поиск по индексу
  • Нет поиска по ключу (Key Lookup)
  • Минимальный ввод-вывод

Альтернатива с индексированным представлением

CREATE VIEW dbo.vw_ActiveSalesDetail
WITH SCHEMABINDING
AS
SELECT SaleID, CustomerID, SaleDate, Amount
FROM dbo.Sales
WHERE Status = 'Active';
GO

CREATE UNIQUE CLUSTERED INDEX IX_vw_ActiveSalesDetail
ON dbo.vw_ActiveSalesDetail(SaleID);

Здесь:

  • Нет преимущества агрегации
  • Более высокое использование памяти для хранения
  • Более медленные операции INSERT/UPDATE

Сценарий 2 – Результаты производительности

СценарийЛогические чтенияCPU (мс)Время выполнения (мс)Накладные расходы на DML
Без оптимизации95 000600850Нет
Фильтрованный индекс3 10075110Низкие
Индексированное представление3 50090130Высокие

Матрица принятия решений

Если ваше узкое место...Лучший выбор
Выборочная фильтрацияФильтрованный индекс
Интенсивная агрегацияИндексированное представление
Высокая нагрузка на записьФильтрованный индекс
Частые отчётные запросыИндексированное представление

Заключительные мысли

Фильтрованный индекс и индексированное представление — не конкуренты; они решают разные проблемы производительности.

Если вам нужно уменьшить ввод-вывод, вызванный выборочными фильтрами, выбирайте фильтрованный индекс.

Если вам нужно устранить затраты на агрегацию в повторяющихся отчётных запросах, используйте индексированное представление.

Ключ в том, чтобы определить реальное узкое место перед применением решения.

Тестируйте оба подхода в своей лаборатории, анализируйте планы выполнения, измеряйте логические чтения и наблюдайте за влиянием на DML-операции.

Потому что настройка производительности — это не добавление объектов, а добавление правильного объекта.



Комментариев нет:

Отправить комментарий