How to fix cannot refresh materialized view concurrently in PostgreSQL
This error occurs when attempting to use REFRESH MATERIALIZED VIEW CONCURRENTLY without a required unique index on the materialized view, or when the index includes a WHERE clause. Concurrent refresh requires a unique, unconditional index to safely perform the operation without locking the view.
Quick fix
Fixes cannot refresh materialized view concurrently
-- Replace 'my_materialized_view' with your actual view name
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'my_materialized_view'
AND schemaname = 'public';- Check if the materialized view has a unique index
- Create a unique index on the materialized view
- Verify the index has no WHERE clause
- Retry the concurrent refresh
-- Replace 'my_materialized_view' with your actual view nameSELECT indexname, indexdefFROM pg_indexesWHERE tablename = 'my_materialized_view'AND schemaname = 'public';cannot refresh materialized view concurrently
What this error means
The REFRESH MATERIALIZED VIEW CONCURRENTLY statement allows PostgreSQL to refresh a materialized view while keeping it accessible for concurrent read queries. This is more complex than a standard refresh because PostgreSQL must perform an outer join between the new materialized data and the existing view to detect changes without blocking readers. To ensure this operation can succeed without duplicate row conflicts during the join, PostgreSQL requires a unique index on the materialized view with no conditions or expressions. When you attempt concurrent refresh without this index, or if the index has restrictions, PostgreSQL cannot proceed safely and raises the "cannot refresh materialized view concurrently" error.
Typical symptoms
- REFRESH MATERIALIZED VIEW CONCURRENTLY fails with error 55000
- Standard REFRESH works, but adding CONCURRENTLY causes the error
- Hint message suggests creating a unique index on the materialized view
- The materialized view has a unique index with a WHERE clause or expression
- Concurrent refresh worked on an older PostgreSQL version but fails after an upgrade
Common causes
- No unique index exists on the materialized view at all
- The unique index includes a WHERE clause (partial index)
- The unique index is built on an expression rather than plain columns
- The unique index was dropped or missing after a schema change
- Using CONCURRENTLY with WITH NO DATA together, which is not allowed
- A bug in PostgreSQL versions before 10.18 where certain column names conflicted with internal aliases
How to fix "cannot refresh materialized view concurrently"
Query the PostgreSQL system catalog to list all indexes on your materialized view:
-- Replace 'my_materialized_view' with your actual view name
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'my_materialized_view'
AND schemaname = 'public';Look for an index that is marked as unique. If no index appears, or if the only index is not unique, you need to create one.
Create a unique index with NO WHERE clause on one or more columns of the materialized view. Choose columns that have unique or near-unique values:
-- Example: if the materialized view has an id column
CREATE UNIQUE INDEX idx_my_mv_unique ON my_materialized_view(id);
-- Or use multiple columns if needed
CREATE UNIQUE INDEX idx_my_mv_unique ON my_materialized_view(schema_id, object_id);The index MUST:
- Be declared UNIQUE
- Contain no WHERE clause (no partial indexes)
- Not be built on expressions like LOWER(name) or id + 1
- Only include plain column references
Confirm the index you created is truly unconditional:
SELECT indexdef
FROM pg_indexes
WHERE tablename = 'my_materialized_view'
AND schemaname = 'public'
AND indexname = 'idx_my_mv_unique';The indexdef should output something like:
CREATE UNIQUE INDEX idx_my_mv_unique ON public.my_materialized_view(id)If it contains a WHERE clause or function calls, drop it and recreate it without those restrictions.
Now that a valid unique index exists, the concurrent refresh should succeed:
REFRESH MATERIALIZED VIEW CONCURRENTLY my_materialized_view;If you still encounter an error, check that:
- The view definition hasn't changed
- The index still exists and is marked UNIQUE
- You are not combining CONCURRENTLY with WITH NO DATA (use one or the other)
- Your PostgreSQL version is 9.4 or later (when CONCURRENTLY was introduced)
Concurrent refresh is more resource-intensive than a standard refresh because it:
- Creates a temporary table with the new materialized data
- Performs a full outer join to detect changes
- Avoids locks that would block read queries
If your materialized view is small or updates are infrequent, a non-concurrent refresh may be faster:
-- Standard refresh (locks the view, but faster for small views)
REFRESH MATERIALIZED VIEW my_materialized_view;
-- Concurrent refresh (slower, keeps view readable)
REFRESH MATERIALIZED VIEW CONCURRENTLY my_materialized_view;Choose based on your availability requirements and view size.
Advanced notes
Concurrent refresh was introduced in PostgreSQL 9.4 (2014). A bug in versions before 10.18 prevented concurrent refresh if the materialized view contained columns named "mv" or "newdata" because those names conflicted with internal aliases PostgreSQL generated. If you are on PostgreSQL 10.x and encounter this error despite having a valid unique index, upgrade to 10.18+ or use non-concurrent refresh. Materialized views are useful for denormalizing expensive queries, but keep in mind that the data is a snapshot; use regular refresh (with CONCURRENTLY for large views) in your background jobs or triggers to keep the view in sync with source tables. Always test the refresh performance in your environment because concurrent refresh may not be faster for all view sizes.
Frequently asked questions
- What does "cannot refresh materialized view concurrently" mean?
- The REFRESH MATERIALIZED VIEW CONCURRENTLY statement allows PostgreSQL to refresh a materialized view while keeping it accessible for concurrent read queries. This is more complex than a standard refresh because PostgreSQL must perform an outer join between the new materialized data and the existing view to detect changes without blocking readers. To ensure this operation can succeed without duplicate row conflicts during the join, PostgreSQL requires a unique index on the materialized view with no conditions or expressions.
- How do I fix "cannot refresh materialized view concurrently"?
- This error occurs when attempting to use REFRESH MATERIALIZED VIEW CONCURRENTLY without a required unique index on the materialized view, or when the index includes a WHERE clause. Concurrent refresh requires a unique, unconditional index to safely perform the operation without locking the view.
- What causes "cannot refresh materialized view concurrently"?
- No unique index exists on the materialized view at all The unique index includes a WHERE clause (partial index) The unique index is built on an expression rather than plain columns
- Is the "cannot refresh materialized view concurrently" error serious?
- It is a medium-severity error โ more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- ๐PostgreSQL Official Documentation
- ๐postgresql.org - sql refreshmaterializedview(Official Docs)
- ๐postgresql.org - errcodes appendix(Official Docs)
- ๐
- ๐
- ๐docs.vultr.com - how to use materialized views in postgresql(Official Docs)