All rules Rule BV021 · LK
PostgreSQL
warning
LK · Locks & blocking
hosted, paid, --remote

Does REFRESH MATERIALIZED VIEW block reads?

Materialized view refreshed without CONCURRENTLY

Warning: this works, but blocks traffic or rewrites data at scale.

What happens

A plain REFRESH MATERIALIZED VIEW takes an exclusive lock on the view while it recomputes, every SELECT against it blocks for the whole rebuild. REFRESH ... CONCURRENTLY lets readers keep the old contents during the rebuild; it requires a unique index on the view.

Why it is dangerous on a populated table

It completes, but the time it holds its lock or rewrites rows scales with the table, and on a busy one that is long enough for traffic to queue.

Measured

Both forms ran against the same table (20M rows, 1.7 GB) on Postgres 18.6, with one session inserting a row and another reading one row every 50 ms. Worst wait is the slowest of those probe calls while the statement ran. This run is from Sep 16, 2026; the whole benchmark has every scenario and how to run it.

StatementTimeWorst write waitWorst read waitLock
FiresREFRESH MATERIALIZED VIEW (readers blocked)1.4 s7 ms1.4 sAccessExclusiveLock, ExclusiveLock, ShareLock, rewrite
SafeREFRESH MATERIALIZED VIEW CONCURRENTLY1.3 s6 ms2 msExclusiveLock

Fires on

REFRESH MATERIALIZED VIEW daily_revenue;

The safe pattern

REFRESH MATERIALIZED VIEW CONCURRENTLY lets readers keep the old contents while the new version is computed and diffed in. It requires a unique index on the view, build one CONCURRENTLY first if it is missing.

-- one-time prerequisite, own migration outside a transaction:
--    CREATE UNIQUE INDEX CONCURRENTLY daily_revenue_day_idx
--      ON daily_revenue (day);
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;

Fixtures

The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.

Fires (1)

refresh plain
REFRESH MATERIALIZED VIEW daily_revenue;

Stays silent (1)

refresh concurrently
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_revenue;

How to check locally

Catch this before it ships

This rule runs in the hosted service on Startup and above: add --remote with a team token, or use the GitHub Action. No install, nothing leaves your machine:

npx bolvrk check migration.sql --remote