В этом материале мы подробно разбираем, как использовать материализованные представления (материализованное представление — далее «MV») для переписывания запросов (query rewrite). Рассмотрим базовые принципы, ключевые возможности, примеры, типовые сценарии, кодовые детали и краткое сравнение подходов в доминирующих системах больших данных.
Зачем нужны MV в StarRocks
Материализованные представления в StarRocks — ключевой инструмент моделирования и ускорения аналитических запросов. Особенно в BI‑сценариях, где предварительное вычисление Join и Aggregation позволяет:
значительно ускорить выполнение запросов;
заметно снизить стоимость хранения.
Практически любой процесс использования MV проходит три этапа:
Проектирование и создание: анализируем профиль запросов и выбираем оптимальные MV.
Обслуживание: своевременно обновляем MV при изменении данных в базовых таблицах.
Переписывание запросов (query rewrite): используем предвычисленные данные MV для ускорения выполнения.

В этой статье фокус — на этапе №3: автоматическое переписывание запросов на MV.
StarRocks для асинхронных MV применяет признанный алгоритм SPJG (Select–Projection–Join–Group By). Это позволяет движку без изменения пользовательского SQL автоматически транслировать исходный запрос в обращение к MV. За счёт предвычисленных результатов вычислительные затраты снижаются, а ответы приходят быстрее.
Практический эффект (на типовых OLAP‑бенчмарках):
SSB 100GB: суммарное время выполнения запросов сокращается примерно до 1/3 от классической звёздной схемы.
TPC‑H 100GB: ускоряется ≈ половина запросов, среднее время снижается примерно до 1/5.

Базовые принципы и конвейер переписывания

Ниже — упрощённый конвейер переписывания запросов на MV:
Предобработка
Анализ используемых таблиц в запросе: по зависимостям с MV выявляются кандидаты.
Отбор кандидатов: для сложных запросов множество MV может быть велико; ранжируем по «применимости» и выбираем подмножество.
Проверка свежести: отбрасываем MV, чья свежесть (staleness) не удовлетворяет требованиям запроса.
SPJG‑переписывание (cost‑based)
Применяем набор правил сопоставления поддеревьев плана запроса с MV.
Формируем альтернативные варианты переписывания.
Выбираем лучший по Cost Model.
Постобработка
Применяем дополнительные оптимизации:
сокращение набора столбцов (column pruning);
проталкивание предикатов (predicate pushdown);
отсечение партиций (partition pruning).
Пример исходного запроса (SSB‑подобный профиль):
SELECT SUM(lo_revenue) AS lo_revenue, d_year, p_brand
FROM lineorder
JOIN dates ON lo_orderdate = d_datekey
JOIN part ON lo_partkey = p_partkey
JOIN supplier ON lo_suppkey = s_suppkey
WHERE p_category = 'MFGR#12'
AND s_region = 'AMERICA'
GROUP BY d_year, p_brand
ORDER BY d_year, p_brand;
Также поддерживается текстовое переписывание: если запрос и определение MV совпадают на уровне AST/синтаксического дерева, движок напрямую заменяет запрос обращением к MV.
Ключевые возможности StarRocks MV rewrite
Согласованность данных
Строгая согласованность: результаты переписывания эквивалентны результату по базовым таблицам.
Работа с устаревшими данными: настраиваемая допустимая устарелость (staleness), поддержка переписывания с учётом «несвежих» фрагментов.
Поддержка сложных запросов
Многотабличные JOIN: включая query delta join, view delta join и join derivability rewrite — полезно для «широких» денормализованных таблиц.
Ускорение агрегатов: aggregate rewriting, в том числе rollup (роллап) и специальные функции (bitmap_union / hll_union / percentile_union).
Вложенные MV: поддержка переписывания на основе цепочек MV.
Сложные выражения: функции, арифметика, CASE WHEN, OR‑предикаты и т. п.
Слияние «горячих» и «холодных» данных
Ускорение по свежим данным: union rewrite + TTL, с автоматическим «дочётом» истории из исходных таблиц.
Несколько источников данных
MV на логических представлениях (VIEW): ускорение в архитектурах с view‑моделированием.
Внешние источники: MV на Hive, Iceberg, Hudi, DeltaLake, Paimon, JDBC (MySQL dialect) и др.
Примеры применения

На BI‑платформе Ctrip внедрение MV в StarRocks дало следующие эффекты:
Projection MV на базе Hive‑таблиц: задействование производительности внутреннего движка StarRocks даёт кратный прирост скорости отчётов; автообновление MV по Hive снижает расходы на ETL‑сопровождение.
Вложенные представления: поверх MV строятся дополнительные VIEW/MV для ускорения сложных отчётов с несколькими JOIN/AGG слоями.
AutoMV: автоматический анализ «медленных» запросов и рекомендации подходящих MV снижают ручные трудозатраты.
Сценарии использования и примеры
1. Join rewrite
Поддерживаются типы: INNER JOIN, CROSS JOIN, LEFT/RIGHT/FULL OUTER JOIN, SEMI JOIN, ANTI JOIN.
Создадим исходные таблицы:
CREATE TABLE `customer` (
`c_custkey` INT(11) NOT NULL,
`c_name` VARCHAR(26) NOT NULL,
`c_address` VARCHAR(41) NOT NULL,
`c_city` VARCHAR(11) NOT NULL,
`c_nation` VARCHAR(16) NOT NULL,
`c_region` VARCHAR(13) NOT NULL,
`c_phone` VARCHAR(16) NOT NULL,
`c_mktsegment` VARCHAR(11) NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`c_custkey`)
DISTRIBUTED BY HASH(`c_custkey`) BUCKETS 12
PROPERTIES ("replication_num" = "1");
CREATE TABLE `lineorder` (
`lo_orderkey` INT(11) NOT NULL,
`lo_linenumber` INT(11) NOT NULL,
`lo_custkey` INT(11) NOT NULL,
`lo_partkey` INT(11) NOT NULL,
`lo_suppkey` INT(11) NOT NULL,
`lo_orderdate` INT(11) NOT NULL,
`lo_orderpriority` VARCHAR(16) NOT NULL,
`lo_shippriority` INT(11) NOT NULL,
`lo_quantity` INT(11) NOT NULL,
`lo_extendedprice` INT(11) NOT NULL,
`lo_ordtotalprice` INT(11) NOT NULL,
`lo_discount` INT(11) NOT NULL,
`lo_revenue` INT(11) NOT NULL,
`lo_supplycost` INT(11) NOT NULL,
`lo_tax` INT(11) NOT NULL,
`lo_commitdate` INT(11) NOT NULL,
`lo_shipmode` VARCHAR(11) NOT NULL
) ENGINE=OLAP
DUPLICATE KEY(`lo_orderkey`)
DISTRIBUTED BY HASH(`lo_orderkey`) BUCKETS 48
PROPERTIES ("replication_num" = "1");Создадим MV:
-- MV
CREATE MATERIALIZED VIEW join_mv1
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey, lo_linenumber, lo_revenue, lo_partkey, c_name, c_address
FROM lineorder
INNER JOIN customer
ON lo_custkey = c_custkey;Теперь следующий запрос переписывается на join_mv1:
-- Query
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name, c_address
FROM lineorder
INNER JOIN customer
ON lo_custkey = c_custkey;
Также поддерживаются сложные выражения в SELECT:
SELECT
lo_orderkey,
lo_linenumber,
(2 * lo_revenue + 1) * lo_linenumber AS revenue_score,
UPPER(c_name) AS c_name_upper,
SUBSTR(c_address, 3) AS addr_trimmed
FROM lineorder
INNER JOIN customer
ON lo_custkey = c_custkey;
1.1 Query delta join rewrite
Query delta join: набор таблиц в JOIN запроса — супермножество набора в MV.
Например, запрос соединяет три таблицы, а MV — две:
SELECT lo_orderkey, lo_linenumber, lo_revenue, c_name, c_address, p_name
FROM lineorder
INNER JOIN customer ON lo_custkey = c_custkey
INNER JOIN part ON lo_partkey = p_partkey;
Движок переписывает часть запроса на join_mv1, а недостающие столбцы/условия — дополняет.

1.2 View delta join rewrite
View delta join: набор таблиц запроса — подмножество набора MV.
Характерно для «широких» денормализованных слоёв: строим MV, объединяющее все таблицы SSB, и дальше большинство SSB‑запросов будет прозрачно переписываться на это MV.

Чтобы это было корректно, требуется сохранение кардинальности (cardinality preservation, 1:1) на соответствующих JOIN‑связях. В StarRocks можно явно задать PK/FK‑отношения:
CREATE TABLE `customer` (...)
PROPERTIES (
"unique_constraints" = "c_custkey"
);
CREATE TABLE `lineorder` (...)
PROPERTIES (
"foreign_key_constraints" =
"(lo_custkey) REFERENCES customer(c_custkey);"
"(lo_partkey) REFERENCES part(p_partkey);"
"(lo_suppkey) REFERENCES supplier(s_suppkey)"
);
Пример «плоского» MV (SSB‑подобная модель):
-- MV
CREATE MATERIALIZED VIEW lineorder_flat_mv
DISTRIBUTED BY HASH(LO_ORDERDATE, LO_ORDERKEY) BUCKETS 48
PARTITION BY LO_ORDERDATE
REFRESH MANUAL
PROPERTIES ("replication_num" = "1") AS
SELECT *
FROM lineorder AS l
INNER JOIN customer AS c ON c.C_CUSTKEY = l.LO_CUSTKEY
INNER JOIN supplier AS s ON s.S_SUPPKEY = l.LO_SUPPKEY
INNER JOIN part AS p ON p.P_PARTKEY = l.LO_PARTKEY
INNER JOIN dates AS d ON l.LO_ORDERDATE = d.D_DATEKEY;
Запрос:
SELECT SUM(lo_revenue) AS lo_revenue, d_year, p_brand
FROM lineorder
JOIN dates ON lo_orderdate = d_datekey
JOIN part ON lo_partkey = p_partkey
JOIN supplier ON lo_suppkey = s_suppkey
WHERE p_category = 'MFGR#12'
AND s_region = 'AMERICA'
GROUP BY d_year, p_brand
ORDER BY d_year, p_brand;

1.3 Join derivability rewrite
Когда тип JOIN в MV и запросе различается, но результат MV покрывает результат запроса (например, MV использует OUTER JOIN, а запрос — INNER JOIN), возможна «производная» замена.
Для 2‑табличных JOIN перебираются варианты порядка/типа соединений и проверяется совместимость (INNER/SEMI/ANTI/OUTER). Для ≥3 таблиц применяются более строгие проверки.
Если прямое переписывание добавило бы строки с NULL, StarRocks автоматически добавит компенсирующий предикат IS NOT NULL.
-- MV
CREATE MATERIALIZED VIEW join_mv3
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey,
lo_linenumber,
c_name,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
LEFT JOIN customer
ON lo_custkey = c_custkey
GROUP BY lo_orderkey, lo_linenumber, c_name;
-- Query
SELECT
lo_orderkey,
lo_linenumber,
c_name,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
JOIN customer
ON lo_custkey = c_custkey
GROUP BY lo_orderkey, lo_linenumber, c_name;
2. Aggregation rewrite
Поддерживается переписывание многотабличных агрегатов, включая функции bitmap_union, hll_union, percentile_union и др.
-- MV
CREATE MATERIALIZED VIEW agg_mv1
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey,
lo_linenumber,
c_name,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
INNER JOIN customer
ON lo_custkey = c_custkey
GROUP BY lo_orderkey, lo_linenumber, c_name;
-- Query
SELECT
lo_orderkey,
lo_linenumber,
c_name,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
INNER JOIN customer
ON lo_custkey = c_custkey
GROUP BY lo_orderkey, lo_linenumber, c_name;

2.1 Rollup (роллап агрегации)
Если GROUP BY в запросе «грубее», чем в MV (содержит меньше столбцов), система может переиспользовать MV с дополнительной (вторичной) агрегацией для получения итогового результата.

2.2 Count distinct
Точное дедуплицирование через COUNT(DISTINCT ...) обычно не «поднимается» роллапом. В StarRocks это решается через bitmap‑подход:
При создании MV:
bitmap_union(to_bitmap(lo_custkey)).В запросе: обычный
COUNT(DISTINCT lo_custkey).
-- MV
CREATE MATERIALIZED VIEW distinct_mv
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey,
bitmap_union(to_bitmap(lo_custkey)) AS distinct_customer
FROM lineorder
GROUP BY lo_orderkey;
-- Query
SELECT lo_orderkey, COUNT(DISTINCT lo_custkey)
FROM lineorder
GROUP BY lo_orderkey;
3. Nested MV rewrite
Поддерживается переписывание по цепочке MV: например, agg_mv2 построено поверх join_mv2, а agg_mv3 — поверх agg_mv2. Это помогает эффективно обрабатывать сложные многоуровневые подзапросы.

Переписывание с использованием UNION применяется, когда MV содержит подмножество данных, необходимых запросу:
Partial Predicate: предикат MV — подмножество предиката запроса; недостающую часть добираем из базовой таблицы и объединяем
UNION ALL, при необходимости с повторной агрегацией.Partial Partition: MV покрывает не все партиции; для «дыр» читаем базовую таблицу, объединяем
UNION ALL.
4.1 Partial predicate
-- MV (предикат: lo_orderkey < 300000000)
CREATE MATERIALIZED VIEW agg_mv4
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
WHERE lo_orderkey < 300000000
GROUP BY lo_orderkey;
Запрос переписывается так, что часть диапазона берётся из agg_mv4, а «хвост» — из lineorder с последующим UNION ALL и финальной агрегацией:
-- Query
SELECT lo_orderkey,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
GROUP BY lo_orderkey;

4.2 Partial partition
Если базовая таблица партиционирована и MV — тоже, а диапазон партиций запроса — супермножество актуальных партиций MV, запрос будет переписан с UNION ALL: покрытая часть — из MV, остальное — из базы.
-- MV, покрывающий партиции p1–p7
CREATE MATERIALIZED VIEW agg_mv5
DISTRIBUTED BY HASH(`lo_orderkey`)
PARTITION BY RANGE(`lo_orderdate`)
REFRESH MANUAL AS
SELECT
lo_orderdate,
lo_orderkey,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
GROUP BY lo_orderdate, lo_orderkey; -- включаем все неагрегированные поля
Если в lineorder добавлена партиция p8: [(‘19990101’), (‘20000101’)), то запрос:
SELECT
lo_orderdate,
lo_orderkey,
SUM(lo_revenue) AS total_revenue,
MAX(lo_discount) AS max_discount
FROM lineorder
GROUP BY lo_orderdate, lo_orderkey; -- согласовано с SELECT

будет переписан как UNION ALL(MV_part, base_part) с последующей агрегацией.
5. MV на представлениях (MV on views)
Поддерживается создание MV поверх представлений (VIEW) с прозрачным переписыванием запросов к VIEW. Два подхода:
VIEW‑разворачивание: инлайн содержимого VIEW и далее обычное переписывание.
VIEW‑как оператор: рассматривать VIEW как чёрный ящик и переписывать выше.
Пример:
-- View
CREATE VIEW customer_view1 AS
SELECT c_custkey, c_name, c_address
FROM customer;
-- View
CREATE VIEW lineorder_view1 AS
SELECT lo_orderkey, lo_linenumber, lo_custkey, lo_revenue
FROM lineorder;
-- MV на базе VIEW
CREATE MATERIALIZED VIEW join_mv1
DISTRIBUTED BY HASH(`lo_orderkey`) AS
SELECT
lo_orderkey, lo_linenumber, lo_revenue, c_name
FROM lineorder_view1
INNER JOIN customer_view1
ON lo_custkey = c_custkey;
При переписывании соответствующие VIEW разворачиваются до базовых таблиц и выполняется сопоставление.
6. MV на внешних источниках (External catalog)
StarRocks поддерживает MV поверх внешних таблиц в Hive / Hudi / Iceberg / Paimon / DeltaLake / JDBC (MySQL dialect) с прозрачным переписыванием. Большинство возможностей, описанных выше, доступны и для «озёрных» сценариев (lakehouse). Подробности — в документации StarRocks.
Ограничения текущей реализации
Не поддерживаются недетерминированные функции:
RAND,RANDOM,UUID,SLEEPи т. п.В режиме SPJG‑переписывания не поддерживаются оконные (window) функции. Текстовое переписывание ограничением не затрагивается.
В режиме SPJG‑переписывания MV, в определении которого есть
LIMIT / ORDER BY / UNION / EXCEPT / INTERSECT / MINUS / GROUPING SETS / WITH CUBE / WITH ROLLUP, не используется для rewrite. Текстовое переписывание возможно.Для части внешних источников (например, Hudi / DeltaLake) пока отсутствует строгая согласованность результата.
Feature
StarRocks
Snowflake
Calcite
Multi-table Join Rewrite
Supports all types of join rewriting
Does not support join rewriting
Only supports inner join rewriting
View Delta Join Rewrite
Supports view delta join rewriting for inner join and left outer join
Not supported
Only supports view delta join rewriting for inner join
Join Derivation Rewrite
Supports derivation rewriting between multiple join types
Not supported
Not supported
Aggregation Rewrite
Supports single-table & multi-table aggregation rewriting; also supports capabilities like rewriting
count(distinct)to bitmap typeOnly supports single-table aggregation rewriting
Supports single-table & multi-table aggregation rewriting, but does not support rewriting
count(distinct)to bitmap typeOR Predicate Support
Supports complex OR predicate processing
Unknown
Only supports simple OR predicate processing
Complex Expression Rewrite
Supported
Unknown
Supported
Union Rewrite
Supports union rewriting for partial statements and partitioned union rewriting
Not supported
Only supports union rewriting for partial statements
Nested Materialized View Rewrite
Supported
Unknown
Supported
External Table Materialized View Rewrite
Supports Hive/Iceberg/Hudi
Unknown
Supported; natively supports multi-data source federation query capabilities
Итоги
Мы рассмотрели:
конвейер переписывания запросов на MV в StarRocks;
техники для разных классов запросов (Join, Aggregation, Nested, Union);
приёмы работы со свежестью и партиционированием;
применение MV на VIEW и внешних каталогах.
Надеемся, обзор проясняет технические принципы и даёт отправную точку для повышения производительности BI‑систем и аналитических витрин на StarRocks.
Ссылки
Optimizing Queries Using Materialized Views: A Practical, Scalable Solution (GL01)
Materialized Views in Apache Calcite: https://calcite.apache.org/docs/materialized_views.html
Oracle Advanced Query Rewrite for Materialized Views: https://docs.oracle.com/en/database/oracle/oracle-database/12.2/dwhsg/advanced-query-rewrite-materialized-views.html#GUID-0906CA6B-7EE3-42E1-A598-C6541BCD9B36
