Lição 9.6 · Tempo de leitura: ~9 min
Uma view materializada é uma view cujo resultado fica guardado em disco como uma tabela: a consulta roda uma vez, e as leituras devolvem as linhas salvas em vez de recalculá-las. Relatórios pesados ficam rápidos, em troca de dados que só estão atualizados até a última atualização da view. Nesta lição você vai criar, consultar e atualizar views materializadas no PostgreSQL, ver quais bancos de dados as suportam e montar o mesmo no MySQL com uma tabela de resumo.
Views Materializadas em SQL
Na lição anterior você criou views: uma view guarda apenas a consulta, e cada leitura a executa de novo. Isso é prático, mas um relatório que junta cinco tabelas e agrega milhares de linhas refaz todo esse trabalho a cada requisição.
A view materializada resolve isso guardando o resultado. Os exemplos usam o esquema Sakila com a sintaxe do PostgreSQL; o comportamento mostrado foi verificado no PostgreSQL 17, e a parte de MySQL no MySQL 8.0.
O Que É uma View Materializada?
Uma view materializada é uma consulta com nome mais uma cópia guardada do seu resultado. As leituras vêm da cópia, então são tão rápidas quanto ler uma tabela pequena. A cópia não se atualiza sozinha: quando as tabelas de origem mudam, a view materializada mantém os dados antigos até você atualizá-la.
| View | View materializada | Tabela | |
|---|---|---|---|
| Guarda | a consulta | a consulta e seu resultado | dados |
| Uma leitura | executa a consulta | lê o resultado guardado | lê os dados |
| Atualidade | sempre atual | da última atualização | o que você gravou |
| Pode ter índice | não | sim | sim |
INSERT / UPDATE | às vezes | não | sim |
Criando uma View Materializada
A sintaxe é a mesma de uma view, com a palavra MATERIALIZED:
CREATE MATERIALIZED VIEW category_revenue AS
SELECT c.name AS category, COUNT(*) AS rentals, SUM(p.amount) AS revenue
FROM payment AS p
JOIN rental AS r ON r.rental_id = p.rental_id
JOIN inventory AS i ON i.inventory_id = r.inventory_id
JOIN film_category AS fc ON fc.film_id = i.film_id
JOIN category AS c ON c.category_id = fc.category_id
GROUP BY c.name;
Resultado: o PostgreSQL executa a consulta uma vez e guarda 16 linhas, uma por categoria.
Depois ela é consultada como uma tabela:
SELECT category, rentals, revenue
FROM category_revenue
ORDER BY revenue DESC
LIMIT 5;
Resultado: Sports (1179 aluguéis, 5314.21), Sci-Fi (4756.98), Animation (4656.30), Drama (4587.39), Comedy (4383.58). Os cinco joins não são executados de novo: as linhas vêm do resultado guardado.
Para criar o objeto sem executar a consulta, acrescente WITH NO DATA. Essa view não pode ser lida antes da primeira atualização: um SELECT falha com "materialized view has not been populated".
Uma view materializada é somente leitura. INSERT INTO category_revenue … falha com "cannot change materialized view". Seus dados mudam apenas com uma atualização.
Atualizando os Dados
Quando chegam novos pagamentos, a view materializada continua mostrando os totais antigos. Para recalculá-la:
REFRESH MATERIALIZED VIEW category_revenue;
Resultado: a consulta roda de novo e substitui as linhas guardadas. Antes desse comando, um novo pagamento não aparece na view de forma alguma.
Um REFRESH simples bloqueia a view materializada: as consultas que a leem esperam a atualização terminar. Para uma view lida o tempo todo, use CONCURRENTLY. Assim o novo resultado é montado ao lado do antigo e só as linhas alteradas são trocadas, sem bloquear as leituras:
CREATE UNIQUE INDEX category_revenue_category_idx ON category_revenue (category);
REFRESH MATERIALIZED VIEW CONCURRENTLY category_revenue;
O CONCURRENTLY exige um índice único na view materializada; sem ele, o comando falha com "cannot refresh materialized view concurrently". Ele também é mais lento que uma atualização simples, então use-o só onde o bloqueio de leituras for um problema.
A frequência de atualização depende da tarefa: uma vez por noite para um relatório diário, a cada poucos minutos para um dashboard. A atualização é disparada por um agendador, como o cron ou a extensão pg_cron.
Índices e Exclusão
Uma view materializada é guardada como uma tabela, então pode ter índices comuns, o que uma view normal não pode. Você vai estudar índices em detalhe no módulo 10.
Para excluí-la:
DROP MATERIALIZED VIEW category_revenue;
A lista de views materializadas do PostgreSQL fica em pg_matviews; a coluna ispopulated mostra se a view já foi preenchida.
Suporte em Diferentes Bancos de Dados
| Banco de dados | Views materializadas | Atualização |
|---|---|---|
| PostgreSQL | CREATE MATERIALIZED VIEW | manual: REFRESH MATERIALIZED VIEW |
| Oracle | CREATE MATERIALIZED VIEW | sob demanda, agendada ou a cada commit; pode ser incremental |
| SQL Server | views indexadas (CREATE VIEW … WITH SCHEMABINDING + um índice clusterizado) | automática, a cada alteração das tabelas de origem |
| MySQL, MariaDB | não suportadas | tabela de resumo (veja abaixo) |
| SQLite | não suportadas | tabela de resumo |
No MySQL, CREATE MATERIALIZED VIEW é um erro de sintaxe.
Uma View Materializada no MySQL: a Tabela de Resumo
No MySQL, o mesmo resultado é guardado em uma tabela comum que você mesmo reconstrói:
CREATE TABLE category_revenue AS
SELECT c.name AS category, COUNT(*) AS rentals, SUM(p.amount) AS revenue
FROM payment AS p
JOIN rental AS r ON r.rental_id = p.rental_id
JOIN inventory AS i ON i.inventory_id = r.inventory_id
JOIN film_category AS fc ON fc.film_id = i.film_id
JOIN category AS c ON c.category_id = fc.category_id
GROUP BY c.name;
Para atualizá-la sem deixar quem lê com uma tabela vazia, preencha uma nova cópia e troque as duas em um único comando:
CREATE TABLE category_revenue_new LIKE category_revenue;
INSERT INTO category_revenue_new
SELECT c.name, COUNT(*), SUM(p.amount)
FROM payment AS p
JOIN rental AS r ON r.rental_id = p.rental_id
JOIN inventory AS i ON i.inventory_id = r.inventory_id
JOIN film_category AS fc ON fc.film_id = i.film_id
JOIN category AS c ON c.category_id = fc.category_id
GROUP BY c.name;
RENAME TABLE category_revenue TO category_revenue_old,
category_revenue_new TO category_revenue;
DROP TABLE category_revenue_old;
Resultado: o RENAME TABLE troca as duas tabelas de uma vez, então as consultas veem os dados antigos ou os novos, nunca uma tabela vazia.
Para uma tabela pequena, DELETE seguido de INSERT … SELECT em uma única transação também funciona. Não use TRUNCATE aqui: no MySQL ele faz um commit implícito, e quem lê veria a tabela vazia (veja a lição 8.4).
A atualização é agendada com o agendador de eventos do MySQL (CREATE EVENT … ON SCHEDULE EVERY 1 HOUR) ou com um job externo do cron.
Quando Usar uma View Materializada
Bons usos:
- relatórios e dashboards pesados que juntam muitas tabelas e agregam muitas linhas;
- dados que podem ter minutos ou horas de atraso: vendas do dia, estatísticas mensais, rankings;
- resultados que muitos usuários leem e poucos alteram.
Usos ruins:
- dados que precisam estar sempre atuais, como um saldo ou o estoque disponível;
- tabelas de origem que mudam o tempo todo, quando a view teria de ser atualizada sem parar;
- consultas simples e rápidas: uma view normal ou a própria consulta bastam.
Principais Conclusões:
- Uma view materializada guarda o resultado de sua consulta; as leituras devolvem as linhas guardadas sem executar a consulta.
- Os dados são da última
REFRESH MATERIALIZED VIEW; no PostgreSQL nada é atualizado automaticamente. REFRESH … CONCURRENTLYnão bloqueia leituras, mas exige um índice único na view materializada.- Uma view materializada é somente leitura e pode ter seus próprios índices.
- MySQL e MariaDB não têm views materializadas: use uma tabela de resumo e troque-a com
RENAME TABLE. - Use-as em relatórios pesados que toleram dados um pouco desatualizados, não em valores que precisam estar atuais.
Perguntas Frequentes
Qual é a diferença entre uma view e uma view materializada?
Uma view guarda apenas a consulta e a executa a cada leitura, então os dados estão sempre atuais. Uma view materializada guarda o resultado da consulta: as leituras são rápidas, mas os dados são da última atualização.
Uma view materializada se atualiza automaticamente?
No PostgreSQL, não: você executa REFRESH MATERIALIZED VIEW manualmente ou por agendamento. O Oracle pode atualizar a cada commit, e as views indexadas do SQL Server são mantidas automaticamente a cada alteração.
O MySQL suporta views materializadas?
Não. MySQL e MariaDB têm apenas views comuns. A substituição usual é uma tabela de resumo preenchida com INSERT … SELECT e reconstruída por agendamento.
Posso criar um índice em uma view materializada?
Sim. Uma view materializada é guardada como uma tabela, então pode ser indexada, e o REFRESH … CONCURRENTLY exige um índice único.
Perguntas de Entrevista
Quando você usaria uma view materializada em vez de uma view comum?
Quando a consulta é cara, seu resultado é lido com frequência e os dados podem ficar um pouco desatualizados: relatórios, dashboards, estatísticas agregadas. Uma view comum é melhor quando os dados precisam estar sempre atuais ou a consulta é barata.
O que faz o REFRESH MATERIALIZED VIEW CONCURRENTLY e o que ele exige?
Recalcula o resultado sem bloquear quem lê: o novo resultado é montado ao lado do antigo e só as diferenças são aplicadas. Exige um índice único na view materializada e é mais lento que uma atualização simples.
Como você implementaria uma view materializada no MySQL?
Criaria uma tabela de resumo com CREATE TABLE … AS SELECT. Para atualizá-la, preencheria uma nova cópia e trocaria as tabelas com RENAME TABLE, disparando a atualização pelo agendador de eventos ou pelo cron.