# П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.