ANALYZE, SHOW STATS и поставка статистики коннекторами
В прошлом уроке мы выяснили: CBO нужна статистика — row count, NDV, nulls fraction, data size, min/max — и качество планов прямо зависит от её актуальности. Остался практический вопрос: откуда статистика берётся, как её посмотреть и как обновить, когда данные изменились. Этот урок — про три вещи: команду ANALYZE для сбора статистики, команду SHOW STATS для её просмотра, и про то, почему доступность статистики зависит от коннектора.
SHOW STATS: посмотреть, что знает оптимизатор
Начнём с диагностики. SHOW STATS FOR <таблица> показывает статистику, которой CBO располагает по таблице прямо сейчас. Это первое, что смотрят, когда запрос работает медленно: если статистики нет, CBO оптимизирует вслепую.
SHOW STATS FOR tpch.sf1.orders;
column_name | data_size | distinct_values_count | nulls_fraction | row_count
---------------+-----------+-----------------------+----------------+-----------
orderkey | NULL | 1500000.0 | 0.0 | NULL
custkey | NULL | 99996.0 | 0.0 | NULL
orderstatus | 1500000.0| 3.0 | 0.0 | NULL
totalprice | NULL | 1462416.0 | 0.0 | NULL
orderdate | NULL | 2406.0 | 0.0 | NULL
NULL | NULL | NULL | NULL | 1500000.0
Читается так. Каждая строка — столбец таблицы; последняя строка с NULL в column_name несёт табличную метрику row_count. distinct_values_count — это NDV. У orderstatus NDV равен 3, у orderkey — полтора миллиона (близко к row count, ключ почти уникален) — ровно различие из прошлого урока. data_size для числовых столбцов часто NULL — фиксированную ширину CBO выводит из типа, отдельная метрика не нужна; для orderstatus (строковый) размер указан.
SHOW STATS принимает и запрос, а не только таблицу — тогда покажет оценочную статистику для результата запроса:
SHOW STATS FOR (SELECT * FROM tpch.sf1.orders WHERE orderstatus = 'F');
Это уже не «что хранится», а «что CBO оценил бы» — полезно, чтобы увидеть, как оптимизатор предсказывает селективность предиката.
ANALYZE: собрать статистику
Статистика не появляется сама — её надо собрать. Команда ANALYZE сканирует таблицу и вычисляет метрики:
ANALYZE tpch.sf1.orders;
ANALYZE проходит данные и считает row count, NDV (через HyperLogLog), nulls fraction, data size, min/max — и записывает результат туда, где коннектор хранит статистику. После этого SHOW STATS покажет свежие числа, а CBO будет планировать на их основе.
ANALYZE — операция не бесплатная: чтобы посчитать метрики, надо прочитать данные. На большой таблице это полноценный скан. Поэтому ANALYZE не запускают на каждый чих — его место в регламенте обслуживания данных. Можно сузить сбор до конкретных столбцов, если интересны не все:
ANALYZE tpch.sf1.orders WITH (columns = ARRAY['orderstatus', 'orderdate']);
Это дешевле полного ANALYZE: метрики считаются только для перечисленных столбцов. Разумно, когда джойны и фильтры запросов идут по известному узкому набору столбцов.
Когда статистика устаревает
Статистика — снимок состояния данных на момент ANALYZE. Данные меняются — статистика отстаёт. Ключевой вопрос: успевает ли она за изменениями.
Здесь важное различие между двумя путями изменения данных.
Данные меняются через Trino. Когда вы делаете INSERT, UPDATE, DELETE, MERGE в таблицу Iceberg, Delta или Hive через сам Trino, движок знает об изменении и может обновлять статистику инкрементально — поддерживать её свежей по ходу записи. В этом случае ручной ANALYZE после каждой записи обычно не требуется: статистика и так не отстаёт.
Данные меняются в обход Trino. Если в таблицу пишет другой инструмент — Spark-джоба, прямая дозапись файлов в S3, внешний загрузчик, — Trino об этом не уведомлён. Статистика, которую он хранит, продолжает описывать старое состояние таблицы. Вот тут ANALYZE обязателен: он единственный способ синхронизировать представление CBO с реальностью. Это и есть основной сценарий ручного ANALYZE — данные изменились мимо Trino.
Самый частый источник плохих планов — устаревшая статистика после загрузки данных в обход Trino. Spark-пайплайн дописал в таблицу за ночь сто миллионов строк, а статистика в Trino всё ещё говорит про вчерашний объём. CBO оценивает таблицу как маленькую, выбирает broadcast join, рассылает на каждую ноду сто миллионов строк вместо вчерашней тысячи — и запрос либо ползёт, либо падает по памяти. Правило: после любой массовой загрузки данных в обход Trino запускайте ANALYZE на затронутых таблицах. Сделайте это частью пайплайна загрузки, а не ручной операцией по факту проблемы.
Поставка статистики зависит от коннектора
Теперь принципиальное. Trino — движок без хранилища, и статистику он сам нигде не держит. Статистику поставляет коннектор. CBO спрашивает у коннектора через SPI: «дай мне статистику для этой таблицы», а уж сможет ли коннектор ответить и насколько полно — зависит от коннектора и источника.
Возможны разные уровни поддержки:
| Тип источника | Как обычно со статистикой |
|---|---|
| Iceberg, Delta Lake | Статистика хранится в метаданных формата таблицы; обновляется при DML через Trino; ANALYZE дособирает расширенную статистику |
| Hive | Статистика — в Hive Metastore; исторически Hive мог не давать data size; ANALYZE собирает метрики в HMS |
| JDBC (PostgreSQL, MySQL и др.) | Коннектор может транслировать запрос к статистике в системные каталоги самой СУБД |
| Простые коннекторы | Могут не отдавать статистику вовсе — тогда CBO работает на эвристиках |
Из этого следуют практические выводы. Iceberg и Delta — благодатная для CBO среда: формат таблицы сам несёт метаданные, и статистика во многом поддерживается актуальной. Hive исторически беднее статистикой — отдельные метрики могли быть недоступны, и ANALYZE для Hive-таблиц особенно важен. А если коннектор статистику не отдаёт совсем, никакой ANALYZE не поможет — её просто негде взять и негде хранить, и CBO для такого источника опирается на грубые эвристики.
Что делать, когда статистики нет
Если SHOW STATS показывает NULL там, где ожидались числа, у CBO нет опоры, и план будет так себе. Действия по порядку.
Первое — запустить ANALYZE и проверить SHOW STATS ещё раз. Часто статистики нет просто потому, что её не собирали или данные залили мимо Trino.
Второе — если после ANALYZE статистика всё равно не появилась, проверить, отдаёт ли её коннектор в принципе. Для некоторых источников это ожидаемо: статистики нет и не будет.
Третье — когда статистики получить нельзя, понимать, что CBO деградирует к rule-based эвристикам, и часть решений за него придётся принимать вручную — например, явно задавать тип распределения join’а сессионными свойствами. Эти ручные рычаги — тема следующих уроков модуля; здесь важно зафиксировать: ручное управление нужно ровно тогда, когда автоматике CBO не на что опереться.
Попробуй сам
На песочнице курса (Trino 481):
-
Выполните
SHOW STATS FOR tpch.sf1.customer;. Найдите строку сrow_countи столбцы сdistinct_values_count. ЗатемSHOW STATS FOR (SELECT * FROM tpch.sf1.customer WHERE nationkey = 1);— сравните оценочныйrow_countрезультата с полным и объясните, как CBO вывел эту оценку. -
Рассуждение в двух абзацах. Первый: команда загрузила в Iceberg-таблицу данные через Spark, минуя Trino, и запросы к ней стали медленными. Почему статистика устарела и какая команда это исправит. Второй: почему та же проблема реже возникает, когда данные пишутся в таблицу через сам Trino командами
INSERT/MERGE. -
Объясните своими словами, почему
ANALYZEнельзя считать бесплатной операцией и почему его место — в регламенте обслуживания данных, а не «после каждого запроса». Свяжите ответ с тем, что именноANALYZEделает с таблицей.