MP008critical

no-multi-ddl-transaction

Multiple DDL statements in a single transaction compound lock duration.

operation
Transactions
lock taken
no table lock
remediation
Manual rewrite
category
Lock safety

What triggers it

Fires on the second (or later) DDL statement inside an open BEGIN...COMMIT block, once isInsideTransaction finds an enclosing BEGIN and findPrecedingDDLInTransaction finds an earlier DDL statement in that same block.

What does not

The first statement in a file (statementIndex === 0) never fires, since nothing precedes it. It also stands down for statements outside any transaction block, non-DDL statements (per isDDL), and the first DDL statement inside a transaction block — only the second and later ones are flagged.

Where it applies

Applies to every PostgreSQL version MigrationPilot targets. It works on the SQL text alone — no database connection needed.

The lock, and what it blocks

The rule doesn't check ctx.lock at all — it's about accumulation, not a specific lock mode. Whatever locks the individual DDL statements take are held until the transaction's final COMMIT, so the total blocked time is the sum of every statement's hold time rather than just the slowest one.

Why it matters

Each DDL statement acquires locks that are held until the transaction commits. Multiple DDL in one transaction means all locks are held simultaneously for the total duration, multiplying the impact on concurrent queries.

Unsafe, and safe

Flagged

BEGIN;
ALTER TABLE users ADD COLUMN bio TEXT;
CREATE INDEX idx_users_bio ON users (bio);
COMMIT;

Safe alternative

-- Run each DDL in its own transaction
ALTER TABLE users ADD COLUMN bio TEXT;
-- separate transaction
CREATE INDEX CONCURRENTLY idx_users_bio ON users (bio);

What it assumes

Transaction-boundary detection walks the parsed statement list for BEGIN/COMMIT/ROLLBACK, not comment text, so a comment merely mentioning BEGIN is correctly ignored — but a transaction opened implicitly by a migration framework outside the analyzed file won't be seen either.

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.

Every safe-migration guide tells you to use CREATE INDEX CONCURRENTLY.

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

PostgreSQL manual

Locks in PostgreSQL are held until the end of the transaction, never released early.

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

What the CLI prints

migrationpilot analyze migration.sql
✗ [MP008] CRITICAL (line 3)
  Multiple DDL statements in a single transaction. Locks are held for the ENTIRE transaction, the combined duration of all DDL operations. Run each DDL in its own transaction.

  Why: When multiple DDL statements run in one transaction, all locks are held until COMMIT. This multiplies the downtime window: the total lock time is the sum of all DDL operations, not just the longest one.
  Docs: https://migrationpilot.dev/rules/mp008

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 MP008
BEGIN;

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

.migrationpilotrc.yml
rules:
  MP008: false

# or keep it, and downgrade it
rules:
  MP008:
    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 MP008 in the playground

Related rules