$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогSQL-диагностика на практике: как понять, почему запрос медленный, по статистике, плану и индексовому покрытию

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

$ sudo teach IT
·14 августа 2026 г.·11 мин·65
SQL-диагностика на практике: как понять, почему запрос медленный, по статистике, плану и индексовому покрытию

Покажем пошаговый подход: от EXPLAIN/ANALYZE до проверки селективности и покрытия индексами. На реальных типовых ошибках разберём, что менять в запросе и схеме, чтобы ускоряться стабильно.

Содержание
1) Начинаем с правильных артефактов: что и почему измеряем1.1. Сначала фиксируем контекст1.2. Делаем замер не только по времени2) EXPLAIN/ANALYZE: как понять, что оптимизатор делает на самом деле2.1. План без исполнения: что смотреть в EXPLAIN2.2. План с исполнением: ключ к реальной проблеме2.3. Сигналы «план неверен»3) Разбор по слоям: запрос → селективность → индексы → покрытие3.1. Слой A: селективность фильтров3.2. Слой B: выражения и типы (почему индекс «не видят»)3.3. Слой C: join-условия и порядок4) Индексы: не просто «есть/нет», а «поддерживают ли нужный доступ»4.1. Проверяем, какой тип индекса действительно подходит4.2. Покрытие индексом (index coverage): ускорение без доступа к таблице5) Практический сценарий: методика «от плана к исправлениям»5.1. Шаг 1: воспроизвести EXPLAIN ANALYZE и понять главную цену5.2. Шаг 2: проверить, почему индекс не выбран5.3. Шаг 3: предложить индексы под запрос, а не «в общем»5.4. Шаг 4: оценить покрытие6) Статистика: когда всё вроде правильно, но план всё равно плохой6.1. Что значит «неверная статистика» в терминах оптимизатора6.2. Когда обновлять статистику и как понимать эффект6.3. Подводные камни статистики7) Типовые ошибки, которые ломают производительность (и что менять)7.1. Ошибка: условие в WHERE построено так, что ломает sargability7.2. Ошибка: join по типам с приведением7.3. Ошибка: порядок колонок в составном индексе не соответствует условиям7.4. Ошибка: сортировка перед LIMIT7.5. Ошибка: «погоня за одним индексом» вместо понимания плана8) Как оценить индексовое покрытие и понять, стоит ли делать INCLUDE8.1. Что именно считать «покрытием»8.2. Стоимость индексов: не делайте «сверхширокие» без измерения9) Репродуцируемый чеклист: что делать при следующем медленном запросе10) Как углубиться: когда нужна системная практикаВывод

Медленный запрос — почти всегда симптом. Причина может лежать в статистике оптимизатора, в том, как сформулирован SQL, в выборе индексов, в кардинальности (сколько реально строк проходит на каждом шаге) или в «скрытых» факторах вроде типов данных и коллаторации. В этой статье разберём практический и воспроизводимый подход к диагностике: как пройти путь от EXPLAIN/ANALYZE до проверки селективности и индексового покрытия, а затем понять, что именно менять — в запросе или в схеме — чтобы ускоряться стабильно.

Материал ориентирован на типичную линейку реляционных СУБД (PostgreSQL / MySQL / аналоги). Термины и команды могут отличаться деталями, но логика диагностики — одна.


1) Начинаем с правильных артефактов: что и почему измеряем

1.1. Сначала фиксируем контекст

Прежде чем смотреть планы, уточните:

  • Какая СУБД и версия (оптимизатор, форматы EXPLAIN, поддержка покрывающих индексов).
  • Тип запроса: SELECT, UPDATE, DELETE, INSERT ... SELECT, агрегаты, оконные функции.
  • Размер данных: порядок величин таблиц и распределение по фильтрам.
  • Частота и окно времени: «медленно иногда» может означать промахи по кэшу, рост статистики, блокировки или план, который устарел.

1.2. Делаем замер не только по времени

Обычно полезны два вида метрик:

  • Время выполнения (wall time).
  • Сколько строк реально обработал план (по rows, actual rows и т. п.).

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


2) EXPLAIN/ANALYZE: как понять, что оптимизатор делает на самом деле

2.1. План без исполнения: что смотреть в EXPLAIN

EXPLAIN (без ANALYZE) покажет предполагаемые оценки:

  • Типы шагов: Index Scan, Seq Scan, Nested Loop, Hash Join, Sort, Aggregate.
  • Порядок соединений (join order).
  • Оценки строк: rows (например, ожидаемые 1 000 при реальных 10 млн).
  • Используемые индексы и условия Filter / Index Cond.

Важное правило: план — это гипотеза оптимизатора. Он может ошибаться, особенно при устаревшей статистике, неравномерном распределении или сложных выражениях.

2.2. План с исполнением: ключ к реальной проблеме

EXPLAIN ANALYZE (или аналог) покажет:

  • actual rows — фактическое число строк.
  • loops — сколько раз выполнялся шаг.
  • время по узлам плана (зависит от СУБД).

И вот тут часто выясняется, что узкое место — не тот шаг, который «кажется логическим». Пример сценария:

  • Вы думаете, что тормозит сортировка.
  • Но EXPLAIN ANALYZE показывает, что сортировка делается уже по 200 строкам, а проблема — в Seq Scan на 200 млн строк из-за неиспользования индекса.

2.3. Сигналы «план неверен»

На практике красные флаги:

  • expected rows сильно отличается от actual rows (на порядки).
  • В плане есть Seq Scan по большим таблицам, хотя очевидные фильтры существуют.
  • Join выполняется Nested Loop, но ожидаемо должны быть более «масштабные» стратегии (или наоборот).
  • Много Sort / Hash по промежуточным результатам из-за неправильных индексов или формулировки запроса.

3) Разбор по слоям: запрос → селективность → индексы → покрытие

Дальше удобно мыслить слоями, как инженер, который не «угадывает», а проверяет гипотезы.

3.1. Слой A: селективность фильтров

Селективность — доля строк, которую «оставляют» условия WHERE. Оптимизатор выбирает план, ориентируясь на оценки селективности.

Типичная проблема

Фильтр вида:

code
WHERE status = 'ACTIVE'

может быть либо суперселективным (1%), либо почти неразборчивым (90%) — зависит от данных.

Если статистика устарела, оптимизатор может думать, что status='ACTIVE' редкое значение, и выбирать индексный план, который превращается в чтение миллионов строк.

Как проверить

  • Посмотрите оценки в EXPLAIN.
  • Посмотрите реальные пропуски — либо через отдельные COUNT(*) с теми же условиями (временно), либо через анализ распределения (в зависимости от СУБД).

Пример «приближённой диагностики»:

code
SELECT
  status,
  COUNT(*) AS cnt
FROM orders
GROUP BY status
ORDER BY cnt DESC;

3.2. Слой B: выражения и типы (почему индекс «не видят»)

Очень частая причина медленности: индекс есть, но не применяется из‑за преобразований выражений.

Например, если столбец created_at типа timestamp, а условие написано так, что приводит к вычислению функции от колонки:

code
WHERE DATE(created_at) = '2026-08-01'

Оптимизатор часто не сможет использовать обычный btree по created_at, потому что индекс по created_at не соответствует выражению DATE(created_at).

Вместо этого лучше писать:

code
WHERE created_at >= TIMESTAMP '2026-08-01'
  AND created_at <  TIMESTAMP '2026-08-02'

То же относится к:

  • приведениям типов (неявные CAST);
  • преобразованиям строк (например, LOWER(email) = ...);
  • применениям функций к полям в join/where.

3.3. Слой C: join-условия и порядок

Join может внезапно стать «дорогим», если:

  • соединение выполняется по неиндексированному ключу;
  • условие join содержит вычисления;
  • выбран не тот ключ для соединения;
  • кардинальность оценивается неправильно.

Схема мышления:

  1. Для каждого join посмотрите, какой стороне доступен индекс.
  2. В плане посмотрите, есть ли Index Cond по ключу join.
  3. Убедитесь, что соединение не «взорвало» кардинальность из-за неправильной логики (например, join на неуникальные поля без дополнительных условий).

4) Индексы: не просто «есть/нет», а «поддерживают ли нужный доступ»

4.1. Проверяем, какой тип индекса действительно подходит

Для btree полезны:

  • равенства (=, IN);
  • диапазоны (>=, <, BETWEEN);
  • сортировка и группировка по левому префиксу составного индекса.

Для полнотекстового поиска нужны специализированные индексы (GIN/GiST, FULLTEXT и т. п.).

Для LIKE с префиксом LIKE 'abc%' обычно подходит btree (в зависимости от колляции/настроек). Но LIKE '%abc%' обычно требует другие подходы.

4.2. Покрытие индексом (index coverage): ускорение без доступа к таблице

Покрытие — это когда СУБД может получить нужные колонки только из индекса, без обращения к heap/кластеру таблицы.

Результат: меньше I/O и ускорение, особенно на больших таблицах.

Простой пример:

  • есть индекс (user_id, created_at)
  • запрос делает выборку только user_id и created_at (и фильтрует по user_id)
  • оптимизатор может сделать Index Only Scan (в PostgreSQL) или аналогичный сценарий.

В терминах диагностики:

  • в плане ищите узлы типа Index Only Scan (PostgreSQL) или признаки «покрытия».
  • смотрите, есть ли Heap Fetches (для PostgreSQL), которые означают обращения к таблице даже при наличии покрытия.

5) Практический сценарий: методика «от плана к исправлениям»

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

5.1. Шаг 1: воспроизвести EXPLAIN ANALYZE и понять главную цену

Допустим, запрос:

code
SELECT
  o.id,
  o.user_id,
  o.total_amount
FROM orders o
WHERE o.status = 'ACTIVE'
  AND o.created_at >= '2026-08-01'
ORDER BY o.created_at DESC
LIMIT 50;

План без ANALYZE может быть оптимистичным. Нас интересуют узлы:

  • где самый большой вклад во время;
  • сколько фактически строк отфильтровано.

Если видим Seq Scan по orders, либо индекс не используется для status/created_at, то идём дальше.

5.2. Шаг 2: проверить, почему индекс не выбран

Типовые причины:

  1. Фильтр не соответствует индексу (функция от колонки, неправильные типы).
  2. Низкая селективность (оптимизатор считает, что индекс не выгоден).
  3. Устаревшая статистика.
  4. Слишком много сортировки/скачков из-за порядка ORDER BY.

В нашем запросе важны два аспекта:

  • фильтрация по status и диапазону по created_at;
  • сортировка по created_at DESC + LIMIT.

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

5.3. Шаг 3: предложить индексы под запрос, а не «в общем»

Для btree классическая стратегия — составной индекс, который поддерживает и фильтр, и порядок сортировки.

Если часто делаете status = ... и ORDER BY created_at DESC LIMIT ..., то кандидат:

  • (status, created_at DESC).

Например:

code
CREATE INDEX idx_orders_status_created_at
ON orders (status, created_at DESC);

Что это даст:

  • поиск по status;
  • затем уже отсортированный доступ по created_at без отдельного Sort.

5.4. Шаг 4: оценить покрытие

Если запрос ещё выбирает id и total_amount, а таблица не кластеризована под индекс, может остаться обращение к строкам таблицы.

В PostgreSQL вы увидите Heap Fetches. Если их много — покрытие неполное.

Варианты:

  • добавить остальные колонки в индекс (создать «covering index» ценой увеличения размера индекса);
  • или переписать запрос так, чтобы получать минимум колонок до следующей операции;
  • иногда выгодно хранить производные/ключевые колонки в отдельной таблице, чтобы уменьшить ширину.

Пример «приближения к покрытию» (идея, а не универсальный рецепт):

code
CREATE INDEX idx_orders_cover
ON orders (status, created_at DESC)
INCLUDE (id, total_amount);

Если СУБД не поддерживает INCLUDE, придётся либо:

  • добавить колонки в ключ индекса (но это может ухудшить использование из-за изменения структуры),
  • либо оставить как есть и принять heap fetch как неизбежность.

6) Статистика: когда всё вроде правильно, но план всё равно плохой

6.1. Что значит «неверная статистика» в терминах оптимизатора

Оптимизатор оценивает:

  • число строк после фильтров (WHERE);
  • число строк после join;
  • распределение значений (histogram, MCV — most common values);
  • селективность диапазонов.

Если данные сильно меняются (например, статусы часто переключаются), но статистика не обновляется, оценки начинают «врать».

6.2. Когда обновлять статистику и как понимать эффект

В PostgreSQL есть ANALYZE, в MySQL — ANALYZE TABLE, в других — аналогичные команды.

Практический подход:

  1. Выполнить EXPLAIN ANALYZE до обновления статистики.
  2. Обновить статистику для таблиц, участвующих в запросе.
  3. Повторить EXPLAIN ANALYZE и сравнить:
    • изменились ли оценки rows;
    • поменялся ли тип скана/джойна;
    • сократилось ли число обработанных строк.

Типичный «симптом» устаревшей статистики:

  • план выглядит логично, но фактические строки отличаются на порядки;
  • оптимизатор выбирает индексный план, который на практике делает почти то же самое, что Seq Scan.

6.3. Подводные камни статистики

  • Композиции условий: отдельные столбцы статистики могут быть корректными, но их совместное распределение — нет. Например, status='ACTIVE' встречается часто, но сочетание status='ACTIVE' AND region='EU' — редкое. Если оптимизатор не умеет достаточно точно оценивать корреляции, план может быть неверным.
  • Сложные выражения: если фильтр вычисляется функциями, статистика может не применяться напрямую.
  • Низкая частота некоторых значений: редкие значения трудно оценивать точно.

Иногда решение не в «обнови статистику», а в переписывании условия так, чтобы оно выглядело «индексируемо».


7) Типовые ошибки, которые ломают производительность (и что менять)

7.1. Ошибка: условие в WHERE построено так, что ломает sargability

Классический пример:

code
WHERE DATE(created_at) = '2026-08-01'

Исправление:

code
WHERE created_at >= TIMESTAMP '2026-08-01'
  AND created_at <  TIMESTAMP '2026-08-02'

7.2. Ошибка: join по типам с приведением

Если один ключ user_id — INT, а другой — VARCHAR, то сравнение может вызвать преобразования. В плане вы можете увидеть, что индекс не применяется.

Исправление:

  • привести типы на уровне схемы;
  • или привести в запросе так, чтобы преобразование делалось на «константу», а не на колонку.

Правило: если приходится CAST(column AS ...), это часто красный флаг.

7.3. Ошибка: порядок колонок в составном индексе не соответствует условиям

Составной btree использует левый префикс. Индекс (created_at, status) не поможет так же хорошо, как (status, created_at), если фильтр по status равенство, а потом диапазон по created_at.

Исправление:

  • пересобрать индекс под наиболее частый паттерн фильтрации и сортировки;
  • подтвердить EXPLAIN ANALYZE.

7.4. Ошибка: сортировка перед LIMIT

Если запрос требует ORDER BY и LIMIT, но индекс не поддерживает нужный порядок, СУБД может отсортировать огромный набор.

Исправление:

  • обеспечить индекс, который покрывает и фильтры, и порядок ORDER BY;
  • минимизировать ширину выбираемых колонок до момента, где это критично.

7.5. Ошибка: «погоня за одним индексом» вместо понимания плана

Иногда создают индекс, но всё равно не ускоряется: причина в другой части плана — например, в дорогом join или в aggregate.

Метод:

  • по EXPLAIN ANALYZE определить самый дорогой узел;
  • индексировать и переписывать именно его входные данные.

8) Как оценить индексовое покрытие и понять, стоит ли делать INCLUDE

8.1. Что именно считать «покрытием»

Покрытие — это не только «есть нужный индекс». Это:

  • нужные колонки для SELECT;
  • плюс условия фильтра и сортировка/группировка (в идеале);
  • плюс минимизация «обращений к таблице».

Проверка (на примере PostgreSQL):

  • узел Index Only Scan означает потенциальное покрытие;
  • Heap Fetches показывает, что покрытие неполное (например, из-за видимости/хранения MVCC).

8.2. Стоимость индексов: не делайте «сверхширокие» без измерения

Индексы ускоряют чтение, но:

  • увеличивают время вставок/обновлений;
  • увеличивают размер базы;
  • могут ухудшить работу кэшей.

Подход:

  • создайте один разумный индекс под критичный запрос;
  • проверьте EXPLAIN ANALYZE;
  • если проблема в heap fetch — добавляйте покрытие точечно.

9) Репродуцируемый чеклист: что делать при следующем медленном запросе

  1. Снимаем EXPLAIN ANALYZE и находим узел с максимальным вкладом во время.
  2. Сравниваем expected vs actual rows на ключевых узлах. Если разница огромная — думайте о статистике или непредвиденной селективности.
  3. Проверяем, применяется ли индекс:
    • есть ли Index Cond по ключевым условиям;
    • не ломает ли sargability функции/CAST;
    • не конфликтует ли тип данных.
  4. Проверяем порядок индекса и ORDER BY/GROUP BY:
    • поддерживает ли индекс нужный порядок?
    • не превращается ли ORDER BY в дорогую Sort?
  5. Если LIMIT, убеждаемся, что СУБД не сортирует миллионы строк.
  6. Оцениваем индексовое покрытие:
    • есть ли Index Only Scan;
    • есть ли дорогие heap fetch.
  7. Обновляем статистику и смотрим, меняется ли план/оценки.
  8. Только после этого фиксируем «узкое место»:
    • переписываем запрос (sargability, join condition);
    • или меняем схему (составные индексы, INCLUDE/cоверы, возможно — перепланировка хранения).

10) Как углубиться: когда нужна системная практика

Этот подход хорошо работает как «операционная инструкция», но практика диагностирования быстро приводит к более глубоким темам: как оптимизатор оценивает кардинальность, как читаются планы в конкретной СУБД, как корректно моделировать индексы под реальные паттерны запросов и как избежать ложных оптимизаций, которые ломают другие сценарии. Если хотите углубиться именно в систематизацию навыков (а не только в разовые советы), можно посмотреть раздел «Производительность и надёжность» в курсе «Продвинутый SQL».


Вывод

SQL-диагностика — это не гадание по «похоже, тут индекс нужен». Это инженерная процедура: фиксируем факты (EXPLAIN/ANALYZE), ищем расхождения между оценками и реальностью, проверяем селективность, убеждаемся, что условия индексируемы (sargability), затем проверяем индексовое покрытие и соответствие индекса WHERE/ORDER BY/LIMIT. Большинство ускорений получается не от «добавить любой индекс», а от точечного изменения того, что мешает оптимизатору (устаревшая статистика, неверные выражения, несоответствие порядка составного индекса) и того, что заставляет СУБД делать лишнюю работу (сортировки, heap fetch, дорогие join-стратегии).

Если выстроить эту диагностику как чеклист и регулярно сравнивать план до/после, скорость вы начнете повышать предсказуемо — без магии и без риска случайно ухудшить другие запросы.

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продвинутый SQL

Курс по теме

Продвинутый SQL

Базы данных - проще простого! Станьте профессионалом и создавайте структуры для высоконагруженных проектов. Подходит всем.

Открыть курс

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Userbot на Telethon и Pyrogram: автоматизация своего Telegram

Userbot на Telethon и Pyrogram: автоматизация своего Telegram

Соберите личного ассистента в своём аккаунте Telegram: сводки, команды, бэкап переписки и медиа, автоответ и напоминания по правилам Telegram.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Локальные LLM и дообучение: Ollama, vLLM, квантование и LoRA

Локальные LLM и дообучение: Ollama, vLLM, квантование и LoRA

Запустите открытую LLM на своём железе и дообучите её под свою задачу: от Ollama и квантования до LoRA, оценки качества и выпуска модели.

Перейти к курсу

Другие статьи

Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
2460
Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1880
Что такое переменная простыми словами: примеры из жизни и первый код
такое

Что такое переменная простыми словами: примеры из жизни и первый код

Разберём, что такое переменная без терминов: как “хранить” значение в памяти и как читать/менять его в программе. Дальше — мини-примеры на вводе/выводе и задания для новичка, чтобы закрепить понимание прямо в коде.

25 сентября 2026 г.
2630
С чего начать учиться в IT, если вы вообще не понимаете, как устроен код: простая схема на 7 дней
учиться

С чего начать учиться в IT, если вы вообще не понимаете, как устроен код: простая схема на 7 дней

Разберём, что учить новичку в правильном порядке: от первых понятий до мини-практики. Дадим понятный план на неделю и подскажем, какой язык выбрать под ваши цели.

11 ноября 2026 г.
200
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

Разберём понятный маршрут для новичка: как выбрать первый язык, что учить в первую очередь, какие мини-проекты делать и как не забросить занятия. Дам бесплатные ориентиры и план, чтобы знания складывались в портфолио.

21 сентября 2026 г.
2630
Как новичку собрать первый “маленький” проект на Python за вечер: с чего начать и что сделать
собрать

Как новичку собрать первый “маленький” проект на Python за вечер: с чего начать и что сделать

Разберём понятный план: какую задачу выбрать, как разбить её на шаги, какие файлы создать и как получить работающий результат без перегруза. В конце будет мини-проект, который можно показать в портфолио и расширять дальше.

9 ноября 2026 г.
360

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

1) Начинаем с правильных артефактов: что и почему измеряем1.1. Сначала фиксируем контекст1.2. Делаем замер не только по времени2) EXPLAIN/ANALYZE: как понять, что оптимизатор делает на самом деле2.1. План без исполнения: что смотреть в EXPLAIN2.2. План с исполнением: ключ к реальной проблеме2.3. Сигналы «план неверен»3) Разбор по слоям: запрос → селективность → индексы → покрытие3.1. Слой A: селективность фильтров3.2. Слой B: выражения и типы (почему индекс «не видят»)3.3. Слой C: join-условия и порядок4) Индексы: не просто «есть/нет», а «поддерживают ли нужный доступ»4.1. Проверяем, какой тип индекса действительно подходит4.2. Покрытие индексом (index coverage): ускорение без доступа к таблице5) Практический сценарий: методика «от плана к исправлениям»5.1. Шаг 1: воспроизвести EXPLAIN ANALYZE и понять главную цену5.2. Шаг 2: проверить, почему индекс не выбран5.3. Шаг 3: предложить индексы под запрос, а не «в общем»5.4. Шаг 4: оценить покрытие6) Статистика: когда всё вроде правильно, но план всё равно плохой6.1. Что значит «неверная статистика» в терминах оптимизатора6.2. Когда обновлять статистику и как понимать эффект6.3. Подводные камни статистики7) Типовые ошибки, которые ломают производительность (и что менять)7.1. Ошибка: условие в WHERE построено так, что ломает sargability7.2. Ошибка: join по типам с приведением7.3. Ошибка: порядок колонок в составном индексе не соответствует условиям7.4. Ошибка: сортировка перед LIMIT7.5. Ошибка: «погоня за одним индексом» вместо понимания плана8) Как оценить индексовое покрытие и понять, стоит ли делать INCLUDE8.1. Что именно считать «покрытием»8.2. Стоимость индексов: не делайте «сверхширокие» без измерения9) Репродуцируемый чеклист: что делать при следующем медленном запросе10) Как углубиться: когда нужна системная практикаВывод