Una vista materializada es una consulta cuyo resultado se almacena fisicamente en disco como una tabla. Pagas el SELECT pesado una sola vez, al refrescar, y luego lees la respuesta precalculada en milisegundos.
Para que sirve
Una VIEW normal es solo texto de consulta guardado: cada acceso vuelve a ejecutar el plan. Si agrega millones de filas o une media tabla, tu panel se arrastra. Una vista materializada calcula el resultado por adelantado y lo conserva.
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;
Ahora SELECT * FROM revenue_by_country lee filas ya calculadas sin tocar orders ni users. La pega: los datos quedan congelados en el momento del ultimo refresco.
Refrescar: REFRESH y CONCURRENTLY
Para traer datos frescos, recalculas la vista:
REFRESH MATERIALIZED VIEW revenue_by_country;
El problema: un REFRESH normal toma un bloqueo ACCESS EXCLUSIVE, asi que los lectores se bloquean mientras corre. En conjuntos grandes son segundos de parada. La solucion es CONCURRENTLY: los nuevos datos se construyen en segundo plano y se intercambian de forma atomica sin bloquear SELECT.
CREATE UNIQUE INDEX ON revenue_by_country (country);
REFRESH MATERIALIZED VIEW CONCURRENTLY revenue_by_country;
- Sin indice unico,
CONCURRENTLY lanza un error.
CONCURRENTLY es mas lento (compara fila por fila) pero nunca bloquea las lecturas.
- Lanza el refresco con un planificador (cron,
pg_cron) o como paso posterior al ETL.
Indexar la vista
Una vista materializada es una tabla, asi que puedes ponerle indices normales y acelerar las lecturas sobre ella.
CREATE INDEX idx_rev_revenue ON revenue_by_country (revenue DESC);
SELECT country, revenue
FROM revenue_by_country
ORDER BY revenue DESC
LIMIT 10;
Los indices sobreviven a un REFRESH: Postgres los reconstruye por ti. Eso si: cuantos mas indices anadas, mas lento sera cada refresco.
Datos obsoletos: el compromiso clave
El precio de la velocidad son datos obsoletos. Entre dos REFRESH, la vista muestra una foto antigua.
Gotcha: un usuario hace un pedido, pero no aparece en revenue_by_country hasta que llamas a REFRESH. Nunca uses una vista materializada donde necesites exactitud transaccional (saldos de cuenta, stock).
Ajusta la frecuencia al negocio: un panel analitico va bien refrescando cada hora; el casi tiempo real necesita cada par de minutos.
View, MV o tabla resumen
| Opcion |
Datos |
Cuando usar |
VIEW |
siempre frescos |
consulta ligera, se exige actualidad |
MATERIALIZED VIEW |
del ultimo refresco |
consulta pesada, tolera retraso |
| tabla resumen |
la mantienes tu |
necesitas logica incremental y UPSERT |
Una tabla resumen es el equivalente manual: la rellenas con INSERT ... SELECT y la actualizas de forma incremental tu mismo.
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;
Diferencias por motor:
- MySQL no tiene vistas materializadas en absoluto: las emulas con tablas resumen mas triggers o el planificador de eventos.
- ClickHouse tiene
MATERIALIZED VIEW, pero no es un cache: es un trigger incremental que actualiza agregados en cada insercion en la tabla origen.
La regla es simple: necesitas actualidad, usa una VIEW; la consulta es cara y el retraso es aceptable, usa una MATERIALIZED VIEW; necesitas logica incremental fina, monta tu propia tabla resumen.
Una vista materializada es una consulta cuyo resultado se almacena fisicamente en disco como una tabla. Pagas el
SELECTpesado una sola vez, al refrescar, y luego lees la respuesta precalculada en milisegundos.Para que sirve
Una
VIEWnormal es solo texto de consulta guardado: cada acceso vuelve a ejecutar el plan. Si agrega millones de filas o une media tabla, tu panel se arrastra. Una vista materializada calcula el resultado por adelantado y lo conserva.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;Ahora
SELECT * FROM revenue_by_countrylee filas ya calculadas sin tocarordersniusers. La pega: los datos quedan congelados en el momento del ultimo refresco.Refrescar: REFRESH y CONCURRENTLY
Para traer datos frescos, recalculas la vista:
REFRESH MATERIALIZED VIEW revenue_by_country;El problema: un
REFRESHnormal toma un bloqueoACCESS EXCLUSIVE, asi que los lectores se bloquean mientras corre. En conjuntos grandes son segundos de parada. La solucion esCONCURRENTLY: los nuevos datos se construyen en segundo plano y se intercambian de forma atomica sin bloquearSELECT.-- CONCURRENTLY necesita un indice UNIQUE sobre las filas del resultado CREATE UNIQUE INDEX ON revenue_by_country (country); REFRESH MATERIALIZED VIEW CONCURRENTLY revenue_by_country;CONCURRENTLYlanza un error.CONCURRENTLYes mas lento (compara fila por fila) pero nunca bloquea las lecturas.pg_cron) o como paso posterior al ETL.Indexar la vista
Una vista materializada es una tabla, asi que puedes ponerle indices normales y acelerar las lecturas sobre ella.
CREATE INDEX idx_rev_revenue ON revenue_by_country (revenue DESC); SELECT country, revenue FROM revenue_by_country ORDER BY revenue DESC LIMIT 10;Los indices sobreviven a un
REFRESH: Postgres los reconstruye por ti. Eso si: cuantos mas indices anadas, mas lento sera cada refresco.Datos obsoletos: el compromiso clave
El precio de la velocidad son datos obsoletos. Entre dos
REFRESH, la vista muestra una foto antigua.Ajusta la frecuencia al negocio: un panel analitico va bien refrescando cada hora; el casi tiempo real necesita cada par de minutos.
View, MV o tabla resumen
VIEWMATERIALIZED VIEWUna tabla resumen es el equivalente manual: la rellenas con
INSERT ... SELECTy la actualizas de forma incremental tu mismo.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;Diferencias por motor:
MATERIALIZED VIEW, pero no es un cache: es un trigger incremental que actualiza agregados en cada insercion en la tabla origen.La regla es simple: necesitas actualidad, usa una
VIEW; la consulta es cara y el retraso es aceptable, usa unaMATERIALIZED VIEW; necesitas logica incremental fina, monta tu propia tabla resumen.