# П17 · июль 2019 · SQL-индексы и планы PostgreSQL — тройное ревью
Статус: **принят в публикационный слой 31 июля 2026 года**. Registry
накладывает три ревизии по стабильным slug и сохраняет дату и автора базового
архива:
- editorial-2019-07-practice-sql-indexes;
- editorial-2019-07-mechanism-sql-indexes;
- editorial-2019-07-field-sql-indexes.
Созданы только пять файлов:
- web/scripts/upgrade-2019-07.mjs;
- web/public/assets/editorial/2019/sql-indexes-selectivity-2019.svg;
- web/public/assets/editorial/2019/sql-indexes-plan-reading-2019.svg;
- web/public/assets/editorial/2019/sql-indexes-predicate-shape-2019.svg;
- этот файл.
Модуль экспортирует ровно три revision без полей date и
author. При прямом вызове с --print-revisions он пишет
только JSON, совпадающий с import-safe export. SQL-фикстура находится внутри
контента как учебный, воспроизводимый сценарий; у модуля нет второго
исполняемого режима и он не меняет базу при audit.
## Проход 1. Факты и техника — пройдено
| Утверждение или решение | Первичный источник | Проверенная граница |
| --- | --- | --- |
| Планировщик выбирает план по структуре запроса и свойствам данных; при изменении селективности может выбрать другую стратегию | [PostgreSQL 11: Using EXPLAIN](https://www.postgresql.org/docs/11/using-explain.html) | Тексты не называют Seq Scan ошибкой. Один и тот же ключ допускает разные планы для ready и waiting |
| EXPLAIN ANALYZE исполняет statement и показывает фактические rows/time; rows и time на узле усреднены на один loops | [PostgreSQL 11: Using EXPLAIN](https://www.postgresql.org/docs/11/using-explain.html) | В статьях cost не превращается в миллисекунды, а маленький внутренний узел не оценивается без учёта loops |
| EXPLAIN (ANALYZE, BUFFERS) нужно запускать с той же осторожностью, что и исходный statement; изменяющий пример помещён в транзакцию с rollback | [PostgreSQL 11: EXPLAIN](https://www.postgresql.org/docs/11/sql-explain.html) | В материалах нет рекомендации безопасно запускать UPDATE «только ради плана» на production |
| Селективность определяется приблизительной статистикой; pg_stats является читаемым представлением, а ANALYZE обновляет статистику | [PostgreSQL 11: Statistics Used by the Planner](https://www.postgresql.org/docs/11/planner-stats.html), [PostgreSQL 11: ANALYZE](https://www.postgresql.org/docs/11/sql-analyze.html) | ANALYZE не обещает индексный узел: он обновляет входные данные планировщика, после чего план снимается заново |
| B-tree — кандидат для распространённых сравнений равенства и диапазона, но индекс имеет цену поддержки | [PostgreSQL 11: Index Types](https://www.postgresql.org/docs/11/indexes-types.html), [Introduction to Indexes](https://www.postgresql.org/docs/11/indexes-intro.html) | Условие «столбец проиндексирован» не выдаётся за доказательство выигрыша для массовой выборки |
| Запрос по выражению может использовать индекс на том же выражении; такой индекс вычисляется и поддерживается при записи | [PostgreSQL 11: Indexes on Expressions](https://www.postgresql.org/docs/11/indexes-expressional.html) | Предикат created_at::date не объявлен автоматически плохим: предложены два варианта — семантически верный range или обоснованный expression index |
| Partial index применим, когда WHERE запроса доказуемо включает его predicate; распознавание ограничено и идёт при планировании | [PostgreSQL 11: Partial Indexes](https://www.postgresql.org/docs/11/indexes-partial.html) | Prepared statement не объявлен «никогда не использующим partial index»; текст оставляет точную границу доказуемости параметризированного условия |
### Честная граница фикстуры
Фикстура создаёт изолированную схему p17_sql_index_fixture в
disposable-базе, миллион синтетических строк с распределением 99/1, два B-tree
индекса, ANALYZE и три запроса EXPLAIN (ANALYZE, BUFFERS).
Она не содержит ожидаемого дерева, вычисленных миллисекунд или фиктивных
actual rows: это должен вывести реальный сервер с его версией,
настройками стоимости и буферами.
В среде подготовки пакета psql не найден и подключение к
PostgreSQL не предоставлено. Поэтому автор **не заявляет запуск базы или
измерение плана**. Это ограничение прямо написано в каждой статье и не
заменено неподтверждённым скриншотом. Перед интеграцией fixture следует
выполнить только в отдельной disposable-базе PostgreSQL 11 и сохранить:
точный SQL и параметры, версию сервера, SHOW random_page_cost,
SHOW seq_page_cost, SHOW effective_cache_size, plan и
контекст нагрузки.
Вердикт прохода: **пройден**. Технические утверждения привязаны к первичной
документации PostgreSQL 11; места, зависящие от конкретного контура, названы
планом проверки, а не выполненным замером.
## Проход 2. Редактура и голос М2 / 2019 — пройдено
| Ревизия | Симптом и цена в начале | Главный вопрос | Практический артефакт и ограничение |
| --- | --- | --- | --- |
| Практика | Добавили индекс, но запрос не ускорился; цена — лишняя стоимость INSERT/UPDATE без выигрыша чтения | Как различить честный Seq Scan, плохую селективность и неверную статистику | SQL-фикстура 99/1, таблица причин, pg_stats и маршрут EXPLAIN; нет обещания одинакового plan на каждом сервере |
| Механизм | В тикете есть скриншот с Index/Seq Scan, но нет actual rows и параметров; цена — лечить не тот узел | Как читать estimate, actual, loops, Buffers, Index Cond и Filter как единое дерево | Безопасный EXPLAIN/rollback пример, схема потока и маршрут первого расхождения; нет выдуманного production-time |
| Полевой разбор | Индекс (created_at) есть, а created_at::date всё ещё читает таблицу; цена — раздутая схема без диагноза | Как отличить selectivity, statistics и predicate shape | Инвентаризация catalog, range versus expression index и partial predicate; timezone и PREPARE оставлены явными границами |
- Первые абзацы называют «симптом», «проблему» и цену решения. Далее каждый
текст держит одну цепочку: симптом → причина → проверка → действие →
ограничение. Вместо общих оценок названы rows,
actual rows, loops, Buffers,
pg_stats, Index Cond и Filter.
- Голос соответствует М2 / 2019: автор уверенно работает с SQL,
PostgreSQL, серверным планом, query shape и базовой доставкой данных, но не
приписывает себе управление платформой, SLO, распределённую трассировку,
Kubernetes или продуктовые метрики поздних лет.
- Тон краткий и прагматичный. Нет абсолютов «индекс всегда ускоряет» или
«Seq Scan всегда плох». Каждый совет требует наблюдаемой проверки до DDL.
- Длина, количество разделов, таблица с caption/thead
и scope, код, упорядоченный маршрут, figure с alt/caption и
два или больше первичных источника дополнительно проверяются draft gate.
Вердикт прохода: **пройден**. Тексты развивают автора от практической
диагностики к более системному чтению планов, но остаются на его правдоподобной
глубине 2019 года.
## Проход 3. Визуал и выпуск — пройдено в пределах автономного пакета
- sql-indexes-selectivity-2019.svg сопоставляет одинаковый
индекс с двумя распределениями результата: 990 000 ready и
10 000 waiting. Нижняя карточка явно говорит сравнить rows,
actual rows, loops и Buffers, а не ждать обязательный Index Scan.
- sql-indexes-plan-reading-2019.svg показывает вертикальный поток
данных от scan к результату и помечает первое расхождение estimate/actual
как точку расследования. Схема не подменяет настоящий plan и не содержит
цифр, объявленных измерением.
- sql-indexes-predicate-shape-2019.svg отделяет индексный ключ
(created_at), выражение created_at::date и
полуоткрытый range. Низ схемы оставляет обязательную проверку timezone и
EXPLAIN ANALYZE, чтобы скорость не сломала границу календарного дня.
- В каждом SVG есть title, desc,
role="img" и связка aria-labelledby. У картинок в
статьях есть самостоятельный содержательный alt и figcaption.
В SVG нет JavaScript, внешних URL или растровых вложений. Вертикальные
viewBox и короткие подписи дают масштабируемую композицию в контейнере
статьи; XML-проверка входит в выпускной набор.
- Автономный пакет не изменяет registry, поэтому не заявляет production build
или опубликованный browser-page. Реальный рендер страницы, strict audit
общего архива и build остаются задачами интегратора после подключения
revision по slug.
### Фактические проверки
```text
node --check web/scripts/upgrade-2019-07.mjs
cd web && npm run audit:draft -- scripts/upgrade-2019-07.mjs
xmllint --noout \
web/public/assets/editorial/2019/sql-indexes-selectivity-2019.svg \
web/public/assets/editorial/2019/sql-indexes-plan-reading-2019.svg \
web/public/assets/editorial/2019/sql-indexes-predicate-shape-2019.svg
```
Результат финального запуска 31 июля 2026 года:
| Проверка | Результат |
| --- | --- |
| node --check | PASS, синтаксис модуля корректен |
| --print-revisions | PASS, stdout — JSON, export import-safe и содержит ровно три revision |
| npm run audit:draft -- scripts/upgrade-2019-07.mjs | PASS: 11 230 / 10 655 / 10 608 знаков основного текста |
| xmllint --noout для трёх SVG | PASS, XML корректен |
| Scope/self-review | PASS: в рабочем дереве появились только пять разрешённых файлов; SVG не содержат script, внешних URL, foreignObject или растровых data URI |
После подключения registry основной редактор повторил strict audit: все три
slug прошли объём 11 230 / 10 655 / 10 608 знаков, figure, таблицу, код,
маршрут действий и источники. npm run build завершился с кодом 0
и сгенерировал 374 статические страницы.
Выпусковой вердикт: **принят к публикации**. articles.json не
менялся; registry заменяет только редакционные поля по стабильному slug.