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
- Can stall the lock queue
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
Public incidents and write-ups
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
Public incidents and write-ups
What the CLI prints
✗ [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:
rules:
MP008: false
# or keep it, and downgrade it
rules:
MP008:
severity: warningTry 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