Введение
В отличие от обычных представлений (статья 406), не хранящих данные физически, материализованное представление сохраняет результат запроса на момент его создания/последнего обновления, обращение к которому затем читает уже сохранённые, готовые данные, а не выполняет исходный запрос заново — в статье разберём этот механизм и связанный с ним компромисс между производительностью чтения и актуальностью данных.
Концепция
Материализованное представление физически хранит результат своего определяющего запроса (аналогично обычной таблице) и должно явно обновляться (REFRESH MATERIALIZED VIEW в PostgreSQL, или эквивалентный механизм в других СУБД) для отражения изменений в исходных таблицах — между обновлениями данные материализованного представления могут отставать от реального текущего состояния исходных таблиц, что и есть основной, сознательно принимаемый компромисс ради значительно более быстрого чтения по сравнению с повторным выполнением сложного запроса при каждом обращении.
Пример кода: Материализованные представления
-- Создание материализованного представления (синтаксис PostgreSQL)
CREATE MATERIALIZED VIEW monthly_sales_summary AS
SELECT DATE_TRUNC('month', order_date) AS month,
COUNT(*) AS order_count,
SUM(total_amount) AS total_revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date);
-- Обращение к материализованному представлению — читает УЖЕ ГОТОВЫЕ, сохранённые данные,
-- без повторного выполнения агрегации над всей таблицей orders при каждом запросе
SELECT * FROM monthly_sales_summary ORDER BY month DESC;
-- Обновление данных материализованного представления — должно выполняться явно
REFRESH MATERIALIZED VIEW monthly_sales_summary;
-- CONCURRENTLY — обновление без блокировки одновременного чтения представления
-- (требует наличия UNIQUE индекса на материализованном представлении)
CREATE UNIQUE INDEX ON monthly_sales_summary (month);
REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales_summary;
-- Автоматизация обновления через периодическую задачу (например, через pg_cron в PostgreSQL,
-- или эквивалентный планировщик задач на уровне приложения/ОС)
-- SELECT cron.schedule('refresh-monthly-sales', '0 1 * * *', -- каждый день в 01:00
-- 'REFRESH MATERIALIZED VIEW CONCURRENTLY monthly_sales_summary');
Пояснения к коду
CREATE MATERIALIZED VIEW синтаксически похож на обычное представление, но принципиально отличается по физическому поведению — данные действительно сохраняются на диске в момент создания, и SELECT * FROM monthly_sales_summary после создания читает эти сохранённые данные мгновенно, без повторного сканирования и агрегации потенциально большой таблицы orders. REFRESH MATERIALIZED VIEW CONCURRENTLY — важная деталь для production-использования: обычный REFRESH (без CONCURRENTLY) блокирует представление на чтение на время обновления, что для часто читаемого представления может создать заметную, хотя и временную, недоступность; CONCURRENTLY решает эту проблему за счёт необходимости иметь уникальный индекс на представлении и немного большей сложности самого процесса обновления внутри СУБД.
Подводные камни
- Забытое или слишком редкое обновление материализованного представления, приводящее к тому, что приложение показывает заметно устаревшие данные, не отражающие реальные, недавние изменения в исходных таблицах — в отличие от обычного представления, которое всегда показывает актуальные данные (просто медленнее), материализованное представление требует явного, осознанного решения о частоте обновления, согласованного с реальными требованиями приложения к актуальности этих конкретных данных (для большинства аналитических панелей актуальность с задержкой в час или день вполне приемлема, но для оперативных, действенных данных это может быть неприемлемо).
- Использование обычного
REFRESH(безCONCURRENTLY) для часто читаемого материализованного представления в production, что создаёт периодические, хотя и кратковременные, «окна недоступности» представления именно в момент его регулярного обновления — для представлений, активно используемых пользовательским интерфейсом в реальном времени (а не только для редких, фоновых аналитических отчётов),CONCURRENTLY(или аналогичный механизм неблокирующего обновления в других СУБД) практически обязателен, несмотря на дополнительное требование уникального индекса. - Создание материализованного представления для запроса, который и без материализации выполняется достаточно быстро, добавляя ненужную сложность поддержания актуальности (явные вызовы обновления, мониторинг их успешности) без реальной выгоды в производительности, которая оправдала бы эту сложность — материализация имеет смысл прежде всего для действительно дорогих, медленных запросов (агрегация над большим объёмом исторических данных), а не как универсальная «оптимизация по умолчанию» для любого представления.
- Неучёт растущего объёма хранения, занимаемого материализованным представлением, особенно для представлений с детальной, не агрегированной структурой над большими исходными таблицами — в отличие от обычного представления, не занимающего дополнительного места на диске сверх самого определения запроса, материализованное представление физически хранит весь свой результат, и для представлений над очень большими объёмами данных это может стать заметным дополнительным расходом дискового пространства, который стоит учитывать при планировании.