Uma visao materializada e uma consulta cujo resultado fica armazenado fisicamente em disco como uma tabela. Voce paga o SELECT pesado uma unica vez, no refresh, e depois le a resposta pre-calculada em milissegundos.
Por que usar
Uma VIEW comum e apenas o texto da consulta guardado: cada acesso reexecuta o plano. Se ela agrega milhoes de linhas ou junta meia tabela, seu dashboard rasteja. A visao materializada calcula o resultado de antemao e o mantem guardado.
CREATE MATERIALIZED VIEW revenue_by_country AS
SELECT u.country,
count(*) AS orders_cnt,
sum(o.amount) AS revenue
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
GROUP BY u.country;
Agora SELECT * FROM revenue_by_country le linhas ja calculadas sem tocar em orders e users. O detalhe: os dados ficam congelados no momento do ultimo refresh.
Atualizando: REFRESH e CONCURRENTLY
Para puxar dados novos, voce recalcula a visao:
REFRESH MATERIALIZED VIEW revenue_by_country;
O problema: um REFRESH comum pega um lock ACCESS EXCLUSIVE, entao os leitores ficam travados enquanto ele roda. Em volumes grandes sao segundos de indisponibilidade. A solucao e CONCURRENTLY: os novos dados sao construidos em segundo plano e trocados de forma atomica sem bloquear o SELECT.
CREATE UNIQUE INDEX ON revenue_by_country (country);
REFRESH MATERIALIZED VIEW CONCURRENTLY revenue_by_country;
- Sem indice unico,
CONCURRENTLY retorna erro.
CONCURRENTLY e mais lento (compara linha por linha) mas nunca bloqueia as leituras.
- Dispare o refresh por agendamento (cron,
pg_cron) ou como passo apos o ETL.
Indexando a visao
Uma visao materializada e uma tabela, entao voce pode colocar indices normais nela e acelerar as leituras sobre ela.
CREATE INDEX idx_rev_revenue ON revenue_by_country (revenue DESC);
SELECT country, revenue
FROM revenue_by_country
ORDER BY revenue DESC
LIMIT 10;
Os indices sobrevivem a um REFRESH: o Postgres os reconstroi por voce. So lembre: quanto mais indices voce adiciona, mais lento fica cada refresh.
Dados defasados: o compromisso central
O preco da velocidade sao dados defasados. Entre dois REFRESH, a visao mostra uma foto antiga.
Gotcha: um usuario faz um pedido, mas ele nao aparece em revenue_by_country ate voce chamar REFRESH. Nunca use uma visao materializada onde precise de exatidao transacional (saldos de conta, estoque).
Ajuste a cadencia ao negocio: um dashboard analitico fica bem atualizando de hora em hora; quase tempo real precisa de cada par de minutos.
View, MV ou tabela resumo
| Opcao |
Dados |
Quando usar |
VIEW |
sempre frescos |
consulta leve, exige atualidade |
MATERIALIZED VIEW |
do ultimo refresh |
consulta pesada, tolera atraso |
| tabela resumo |
voce mantem |
precisa de logica incremental e UPSERT |
Uma tabela resumo e o equivalente manual: voce a preenche com INSERT ... SELECT e a atualiza de forma incremental por conta propria.
INSERT INTO revenue_summary (country, revenue, day)
SELECT u.country, sum(o.amount), current_date
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.created_at >= current_date
GROUP BY u.country
ON CONFLICT (country, day) DO UPDATE
SET revenue = EXCLUDED.revenue;
Diferencas por banco:
- MySQL nao tem visoes materializadas: voce as emula com tabelas resumo mais triggers ou o agendador de eventos.
- ClickHouse tem
MATERIALIZED VIEW, mas nao e um cache: e um gatilho incremental que atualiza agregados a cada insercao na tabela de origem.
A regra e simples: precisa de atualidade, use uma VIEW; a consulta e cara e o atraso e aceitavel, use uma MATERIALIZED VIEW; precisa de logica incremental fina, monte sua propria tabela resumo.
Uma visao materializada e uma consulta cujo resultado fica armazenado fisicamente em disco como uma tabela. Voce paga o
SELECTpesado uma unica vez, no refresh, e depois le a resposta pre-calculada em milissegundos.Por que usar
Uma
VIEWcomum e apenas o texto da consulta guardado: cada acesso reexecuta o plano. Se ela agrega milhoes de linhas ou junta meia tabela, seu dashboard rasteja. A visao materializada calcula o resultado de antemao e o mantem guardado.CREATE MATERIALIZED VIEW revenue_by_country AS SELECT u.country, count(*) AS orders_cnt, sum(o.amount) AS revenue FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'paid' GROUP BY u.country;Agora
SELECT * FROM revenue_by_countryle linhas ja calculadas sem tocar emorderseusers. O detalhe: os dados ficam congelados no momento do ultimo refresh.Atualizando: REFRESH e CONCURRENTLY
Para puxar dados novos, voce recalcula a visao:
REFRESH MATERIALIZED VIEW revenue_by_country;O problema: um
REFRESHcomum pega um lockACCESS EXCLUSIVE, entao os leitores ficam travados enquanto ele roda. Em volumes grandes sao segundos de indisponibilidade. A solucao eCONCURRENTLY: os novos dados sao construidos em segundo plano e trocados de forma atomica sem bloquear oSELECT.-- CONCURRENTLY exige um indice UNIQUE sobre as linhas do resultado CREATE UNIQUE INDEX ON revenue_by_country (country); REFRESH MATERIALIZED VIEW CONCURRENTLY revenue_by_country;CONCURRENTLYretorna erro.CONCURRENTLYe mais lento (compara linha por linha) mas nunca bloqueia as leituras.pg_cron) ou como passo apos o ETL.Indexando a visao
Uma visao materializada e uma tabela, entao voce pode colocar indices normais nela e acelerar as leituras sobre ela.
CREATE INDEX idx_rev_revenue ON revenue_by_country (revenue DESC); SELECT country, revenue FROM revenue_by_country ORDER BY revenue DESC LIMIT 10;Os indices sobrevivem a um
REFRESH: o Postgres os reconstroi por voce. So lembre: quanto mais indices voce adiciona, mais lento fica cada refresh.Dados defasados: o compromisso central
O preco da velocidade sao dados defasados. Entre dois
REFRESH, a visao mostra uma foto antiga.Ajuste a cadencia ao negocio: um dashboard analitico fica bem atualizando de hora em hora; quase tempo real precisa de cada par de minutos.
View, MV ou tabela resumo
VIEWMATERIALIZED VIEWUma tabela resumo e o equivalente manual: voce a preenche com
INSERT ... SELECTe a atualiza de forma incremental por conta propria.INSERT INTO revenue_summary (country, revenue, day) SELECT u.country, sum(o.amount), current_date FROM orders o JOIN users u ON u.id = o.user_id WHERE o.created_at >= current_date GROUP BY u.country ON CONFLICT (country, day) DO UPDATE SET revenue = EXCLUDED.revenue;Diferencas por banco:
MATERIALIZED VIEW, mas nao e um cache: e um gatilho incremental que atualiza agregados a cada insercao na tabela de origem.A regra e simples: precisa de atualidade, use uma
VIEW; a consulta e cara e o atraso e aceitavel, use umaMATERIALIZED VIEW; precisa de logica incremental fina, monte sua propria tabela resumo.