MP021warningauto-fixPG 12+

require-concurrent-reindex

REINDEX without CONCURRENTLY acquires ACCESS EXCLUSIVE, blocking all operations.

operation
Indexes
lock taken
ACCESS EXCLUSIVE
remediation
Fixed by --fix
category
Lock safety

What triggers it

Fires on any ReindexStmt (REINDEX TABLE/INDEX/SCHEMA/DATABASE) once ctx.pgVersion is 12 or higher, as long as it is not REINDEX_OBJECT_SYSTEM and does not already carry the concurrently DefElem in its params.

What does not

Non-ReindexStmt nodes are skipped outright. It also returns null when ctx.pgVersion is below 12 (CONCURRENTLY did not exist yet), when the target is REINDEX SYSTEM (which PostgreSQL will not run CONCURRENTLY), and when the statement already sets concurrently.

Where it applies

Applies to PostgreSQL 12 and later. It works on the SQL text alone — no database connection needed.

REINDEX CONCURRENTLY requires PostgreSQL 12 or later; on older versions the rule does not fire at all since there is no safe CONCURRENTLY form to suggest.

The lock, and what it blocks

REINDEX TABLE holds ACCESS EXCLUSIVE on the whole table for the rebuild; REINDEX INDEX holds SHARE, which still blocks writes. Either way the block lasts as long as the index build takes, scaling with table size.

Why it matters

REINDEX rebuilds the index while blocking all reads and writes. On PG 12+, REINDEX CONCURRENTLY rebuilds the index without blocking concurrent operations.

Unsafe, and safe

Flagged

REINDEX INDEX idx_users_email;

Safe alternative

REINDEX INDEX CONCURRENTLY idx_users_email;

Deploy and transaction boundaries

REINDEX CONCURRENTLY carries the same restriction as CREATE INDEX CONCURRENTLY (see MP025): it cannot run inside a transaction block, so it needs to be the only statement in its deploy step rather than batched with other DDL.

What it assumes

The rule fires on syntax and PostgreSQL version alone, with no size or traffic signal, so a REINDEX on a tiny or idle table is flagged the same as one on a huge, hot one.

What backs this rule

Every rule is a claim about PostgreSQL, so it carries what the claim rests on: a handbook chapter that cites the manual, the incidents that put it there, and the version it was last checked against.

When CREATE INDEX CONCURRENTLY fails, it does not clean up after itself.

verified against
PostgreSQL 17.10
last checked
2026-08-11
confidence
High

What the CLI prints

migrationpilot analyze migration.sql --pg-version 12
⚠ [MP021] WARNING (line 1)
  REINDEX INDEX "idx_users_email" without CONCURRENTLY blocks all writes (or reads for tables). On PostgreSQL 17, use REINDEX CONCURRENTLY instead.

  Safe alternative:
  REINDEX INDEX CONCURRENTLY idx_users_email

  Why: REINDEX without CONCURRENTLY acquires ACCESS EXCLUSIVE (table) or SHARE (index) locks, blocking all queries for the duration. REINDEX CONCURRENTLY (PG 12+) builds the new index without blocking reads or writes.
  Docs: https://migrationpilot.dev/rules/mp021

Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here.

Turning it off

For one statement, put a comment on the line before it:

-- migrationpilot-disable MP021
REINDEX INDEX idx_users_email;

For the whole project, in .migrationpilotrc.yml — by name or by id:

.migrationpilotrc.yml
rules:
  MP021: false

# or keep it, and downgrade it
rules:
  MP021:
    severity: warning

Try it

Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.

Run MP021 in the playground

Related rules