Интуиция скорости: первый план и первый индекс
Курс 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
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
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, побочные ключи лежат в куче GOSQL
- logical reads — страниц из памяти: честная цена запроса, не зависящая от холодного кэша;
- physical reads — чтения с диска; при повторных прогонах падают до нуля и перестают быть мерой;
- время CPU и elapsed — шумные на загруженной машине; в курсе сравниваем по logical reads.
4. Первый индекс под запрос
Индекс — отдельная отсортированная структура по столбцу. Минимальная форма и доказательство:
Чистый код
CREATE INDEX IX_Orders_CustomerID
ON dbo.Orders (CustomerID);
GO
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
CREATE INDEX IX_Orders_CustomerID -- → некластерный индекс на столбец фильтрации ON dbo.Orders (CustomerID); -- → повтор замера «после»: logical reads падают в разы GOSQL
- индекс по одному столбцу помогает предикатам именно по этому столбцу;
- каждый индекс платит объёмом и скоростью 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
- Построчный разбор
Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с двух дефисов и стрелки вправо, содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.
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 GOSQL
5. Задачи и эталонные решения
Шесть задач на контексте TechStore.
- дешёвый и дорогой фильтры: поиск заказа по ключу против поиска по CustomerID без индекса; оба с SET STATISTICS IO, числа записаны;
- первый индекс по CustomerID и повторение запросов: падение logical reads зафиксировано парой «до/после»;
- Explain Plan до и после: Scan сменился Seek-ом, разница описана письменно;
- покрытие: версия индекса с INCLUDE, запрос по клиенту без Key Lookup;
- цена индекса: десять вставок с индексом и без, качественный вывод в журнал;
- приборка: индексы удалены, sys.indexes показывает только исходные ключи курса.
Куда дальше
мост в продвинутый курс
- почему Seek выбирается не всегда и что смотрит оптимизатор — уроки 4 и 5 продвинутого курса;
- как индекс покрывает запрос без дочитки и что такое статистика — там же;
- почему функция над столбцом в WHERE отменяет поиск — урок 4;
- как план меняется под значением параметра первого вызова — урок 6.
Курс-продолжение работает на этой же базе TechStore: переустанавливать ничего не нужно.
6. Типичные ошибки урока
- Сравнение по времени — кэш принимает решение за вас; logical reads выравнивает условия.
- Индекс «на всякий случай» — плата записью и объёмом за сомнительную пользу; индексы ставятся под реальные запросы.
- Ожидание Seek при любых фильтрах — при выборе половины таблицы сканирование честнее; решение оптимизатора — стоимость, не сбой.
- Оценочный за фактический — DataGrip показывает прогноз; на грязной статистике прогноз расходится с фактом, это тема продвинутого урока.
- Дублирующие индексы — индекс (CustomerID) целиком перекрыт индексом (CustomerID) INCLUDE: старый становится балластом.
- Функция над столбцом — WHERE CONVERT(...) по индексному столбцу отучает сервер от поиска; предикат пишется над голым столбцом.
7. Чек-лист практики и источники
Чек-лист урока 12
отметки делаются по факту
Источники для углубления:
Проверено: 23.10.2026
↑ К оглавлению