Перейти к содержимому

Интуиция скорости: первый план и первый индекс

Курс MS SQL с нуля · Урок 12 из 12 · Часть 4. Запросы как инструмент

Финальный урок начального уровня: понять, как сервер исполняет запрос, и убедиться на цифрах, что индекс меняет цену запроса на порядки. Цель скромная: научиться читать два числа и знать, куда расти.

Глубокая теория планов и устройство индексов — первая половина продвинутого курса. Здесь нужен минимум: план, метрика STATISTICS IO и один созданный индекс с доказательством.

Содержание

1. Что вы освоите

К концу урока вы:

  • открываете оценочный план запроса и называете Scan, Seek и Lookup;
  • сравниваете запросы по logical reads, а не по шумному времени;
  • создаёте индекс под конкретный запрос и доказываете эффект числами;
  • узнаёте покрытие запроса индексом по исчезновению Key Lookup;
  • сформулировали вопросы, с которыми пойдёте в продвинутый курс.
↑ К оглавлению

2. План запроса в DataGrip

Ctrl+Enter выполняет запрос, а кнопка Explain Plan (или контекстное меню) показывает план: дерево операторов, которым сервер собирается дойти до данных. DataGrip для SQL Server показывает оценочный план — прогноз до выполнения.

Фигура планаЧто означаетКогда нормально
Clustered Index Scanпрочитать таблицу целикомотчёт по всей таблице
Clustered Index Seekспуск по ключу к нужным строкамточечные фильтры по ключу
Index Seek по некластерномуспуск по своему индексуфильтры по индексному столбцу
Key Lookupдочитка недостающих столбцовмалые объёмы; исчезает при покрытии
Толщина стрелок: чем толще стрелка между операторами, тем больше строк идёт по ней: глаз сразу видит, где план «волочёт миллионы» к крошечному результату — это и есть место для первого индекса.
↑ К оглавлению

3. Честные метрики: logical reads

Реальные метрики включаются перед запросом и читаются во вкладке сообщений результата:

Чистый код

SET STATISTICS IO, TIME ON;
GO

SELECT o.OrderID, o.OrderDate, o.Status, o.TotalAmount
FROM dbo.Orders AS o
WHERE o.CustomerID = 42;
GO
SQL
  • Построчный разбор

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

    SET STATISTICS IO, TIME ON;
    -- → включить приборы: число логических чтений и время на сеанс
    GO
    
    SELECT o.OrderID, o.OrderDate, o.Status, o.TotalAmount
    FROM dbo.Orders AS o
    WHERE o.CustomerID = 42;
    -- → первый замер «до»: план покажет Scan, побочные ключи лежат в куче
    GO
    SQL
  • logical reads — страниц из памяти: честная цена запроса, не зависящая от холодного кэша;
  • physical reads — чтения с диска; при повторных прогонах падают до нуля и перестают быть мерой;
  • время CPU и elapsed — шумные на загруженной машине; в курсе сравниваем по logical reads.
Хронометраж лжёт: первый прогон с диска, второй из кэша: сравнение по миллисекундам приводит к ложным выводам. Сравнение по logical reads ставит оба запроса в равные условия.
↑ К оглавлению

4. Первый индекс под запрос

Индекс — отдельная отсортированная структура по столбцу. Минимальная форма и доказательство:

Чистый код

CREATE INDEX IX_Orders_CustomerID
ON dbo.Orders (CustomerID);
GO
SQL
  • Построчный разбор

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

    CREATE INDEX IX_Orders_CustomerID
    -- → некластерный индекс на столбец фильтрации
    ON dbo.Orders (CustomerID);
    -- → повтор замера «после»: logical reads падают в разы
    GO
    SQL
  • индекс по одному столбцу помогает предикатам именно по этому столбцу;
  • каждый индекс платит объёмом и скоростью INSERT и UPDATE: «про запас» не ставят;
  • удаляется через DROP INDEX, таблица остаётся целой.

Покрытие: исчезновение дочитки

INCLUDE докладывает столбцы на листовой уровень индекса: фильтру хватает поиска, а списку выборки — листа; Key Lookup исчезает из плана:

Чистый код

DROP INDEX IX_Orders_CustomerID ON dbo.Orders;
GO

CREATE INDEX IX_Orders_CustomerID_inc
ON dbo.Orders (CustomerID)
INCLUDE (OrderDate, Status, TotalAmount);
GO

SELECT o.OrderID, o.OrderDate, o.Status, o.TotalAmount
FROM dbo.Orders AS o
WHERE o.CustomerID = 42;
GO
SQL
  • Построчный разбор

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

    DROP INDEX IX_Orders_CustomerID ON dbo.Orders;
    -- → первый вариант заменяется покрывающим
    GO
    
    CREATE INDEX IX_Orders_CustomerID_inc
    ON dbo.Orders (CustomerID)
    -- → ключ поиска
    INCLUDE (OrderDate, Status, TotalAmount);
    -- → три неключевых столбца привезены к ключу: обращение в таблицу лишнее
    GO
    
    SELECT o.OrderID, o.OrderDate, o.Status, o.TotalAmount
    FROM dbo.Orders AS o
    WHERE o.CustomerID = 42;
    -- → план: чистый Seek без Key Lookup
    GO
    SQL
↑ К оглавлению

5. Задачи и эталонные решения

Шесть задач на контексте TechStore.

  1. дешёвый и дорогой фильтры: поиск заказа по ключу против поиска по CustomerID без индекса; оба с SET STATISTICS IO, числа записаны;
  2. первый индекс по CustomerID и повторение запросов: падение logical reads зафиксировано парой «до/после»;
  3. Explain Plan до и после: Scan сменился Seek-ом, разница описана письменно;
  4. покрытие: версия индекса с INCLUDE, запрос по клиенту без Key Lookup;
  5. цена индекса: десять вставок с индексом и без, качественный вывод в журнал;
  6. приборка: индексы удалены, sys.indexes показывает только исходные ключи курса.

Куда дальше

мост в продвинутый курс

  • почему Seek выбирается не всегда и что смотрит оптимизатор — уроки 4 и 5 продвинутого курса;
  • как индекс покрывает запрос без дочитки и что такое статистика — там же;
  • почему функция над столбцом в WHERE отменяет поиск — урок 4;
  • как план меняется под значением параметра первого вызова — урок 6.

Курс-продолжение работает на этой же базе TechStore: переустанавливать ничего не нужно.

↑ К оглавлению

6. Типичные ошибки урока

  1. Сравнение по времени — кэш принимает решение за вас; logical reads выравнивает условия.
  2. Индекс «на всякий случай» — плата записью и объёмом за сомнительную пользу; индексы ставятся под реальные запросы.
  3. Ожидание Seek при любых фильтрах — при выборе половины таблицы сканирование честнее; решение оптимизатора — стоимость, не сбой.
  4. Оценочный за фактический — DataGrip показывает прогноз; на грязной статистике прогноз расходится с фактом, это тема продвинутого урока.
  5. Дублирующие индексы — индекс (CustomerID) целиком перекрыт индексом (CustomerID) INCLUDE: старый становится балластом.
  6. Функция над столбцом — WHERE CONVERT(...) по индексному столбцу отучает сервер от поиска; предикат пишется над голым столбцом.
↑ К оглавлению

7. Чек-лист практики и источники

Чек-лист урока 12

отметки делаются по факту

Источники для углубления:

  1. Планы выполнения: чтение операторов;
  2. SET STATISTICS IO: поля вывода;
  3. CREATE INDEX: ключ и INCLUDE.
Итог курса: двенадцать уроков пройдено. Дальше — финальный проект этой программы, затем продвинутый курс MS SQL на той же базе: оконные функции, рекурсия, планы вглубь, процедуры, конкурентность и секционирование.

Проверено: 23.10.2026

↑ К оглавлению
Конспект:
Четверг, 22 октября 2026