SQL Migration Lock Check · comparison

SQL Migration Lock Check vs. reading the migration by hand

A careful reviewer genuinely catches the well-known single-statement case: a bare CREATE INDEX, a stray TRUNCATE. This page names no company or product — reading a migration file by hand before you run it is a habit most developers already have, not a competing tool.

What a careful read catches reliably

The risks that live entirely on one line are the ones a reviewer who knows the rules already catches well: CREATE INDEX with no CONCURRENTLY, VACUUM FULL, a bare TRUNCATE. This tool's create-index-not-concurrent, full-table-rewrite, and truncate checks flag exactly the same statements, for the same reason.

What gets harder as soon as it spans more than one line

CaseReading by handThis tool
A missing lock_timeoutDepends on whether a SET lock_timeout appears earlier in the same file, possibly dozens of lines away from the statement it protectsTracks every SET lock_timeout against every exclusive-lock statement below it, every time
NOT VALID + VALIDATE CONSTRAINT sharing one transactionBoth lines look correct individually; the bug is which BEGIN block they happen to sit insideFlags the pair specifically when they share a transaction, via validate-in-same-transaction
Several exclusive-lock statements in one transactionEach one reads as an acceptably brief lock on its own; adding five of them into one running total while reading top to bottom is easy to get wrongmany-locks-one-transaction reports the compounded hold, not each statement in isolation
ADD COLUMN with a volatile default, e.g. DEFAULT now()Looks identical to the cheap, metadata-only constant-default case Postgres 11+ made routine, unless the reader happens to know which functions are VOLATILEadd-column-volatile-default checks the function against Postgres's own volatility classification

The gap that matters most: a relationship between two statements, not a property of one

Whether an ALTER TABLE is protected by a lock_timeout, or whether a NOT VALID constraint's later VALIDATE actually buys a cheap step, is not visible on the risky line itself — it depends on what else the file does, sometimes many lines away. A reviewer has to hold the whole file's SET and transaction structure in mind while reading every later statement, which is the part that degrades fastest on a long or generated migration. This tool checks every statement against the full file's lock_timeout and transaction context the same way on every run, regardless of file length.

When to still read the migration yourself

This is a textual scan over SQL, not a query planner — it has no connection to your database, so it cannot see your table's actual row count, its column's current type, or whether an index it assumes exists actually does. It reports what a statement can do under Postgres's own locking rules, not a guarantee that it will hurt your specific table. Deciding whether a flagged statement is safe for your data, and catching SQL this tool's patterns don't recognise, is still a job for reading the migration yourself.

Common questions

What does reading a migration file by hand actually catch?

A reviewer who knows the rules catches the well-known single-statement case reliably: CREATE INDEX with no CONCURRENTLY, or a bare TRUNCATE. Those are real risks and a careful read finds them the same way this tool's create-index-not-concurrent and truncate checks do.

Why is a missing lock_timeout hard to catch by eye?

Whether a given ALTER TABLE is protected depends on whether a SET lock_timeout statement appears earlier in the same file -- a relationship between two statements that can be dozens of lines apart, not a property visible on the risky line itself. A reviewer has to hold the whole file's SET statements in mind while reading every ALTER, which is easy to get right on a short migration and easy to miss on a long generated one.

Why does NOT VALID plus VALIDATE CONSTRAINT in one transaction look safe when it is not?

Both statements are individually correct and idiomatic -- NOT VALID is the documented way to add a constraint without an immediate table scan. The trap is entirely about grouping: wrapped in the same BEGIN block as its own VALIDATE CONSTRAINT, the split buys nothing, because the transaction still holds the lock for the whole validation. Nothing about either line looks wrong on its own; the bug is which migration file boundary they happen to share.

Why is a run of several exclusive-lock statements easy to underestimate?

Each statement, read on its own, is an acceptably brief lock. Five of them in one transaction is not five brief locks -- it is one lock held from the first statement until the last one finishes, because Postgres does not release an ACCESS EXCLUSIVE lock until the transaction commits. Adding up five separate impact estimates in your head while reading a migration top to bottom is exactly the kind of running total a person is bad at keeping accurate.

Does this replace understanding what a flagged statement does?

No. This is a textual scan over SQL, not a query planner -- it cannot see your table's actual row count, current type, or whether an index already exists, so it reports what a statement can do under Postgres's locking rules, not a guarantee that it will hurt in your specific case. Deciding whether a flagged statement is actually safe for your table, and reading SQL this tool's patterns don't recognise, is still a job for reading the migration yourself.

Check a migration