Skip to content

Constraints

NOT VALID, then VALIDATE

Adding a constraint to a table full of rows means checking every one of them. The two-step form does the same work under a lock that lets the application carry on.

10 minutes · NOT VALID · VALIDATE CONSTRAINT · SHARE UPDATE EXCLUSIVE · foreign keys

The expensive half is the scan

ADD CONSTRAINT … CHECK (…) does two things: it records the rule, and it proves every existing row obeys it. The first is a catalog write. The second is a full scan, and it happens under ACCESS EXCLUSIVE.

NOT VALID splits them. The rule applies to every new and updated row immediately; existing rows are simply not checked. The statement returns in milliseconds.

VALIDATE CONSTRAINT then does the scan — under SHARE UPDATE EXCLUSIVE, which conflicts with neither reads nor writes. Same total work, and none of it in anybody's way.

Try this
ALTER TABLE orders ADD CONSTRAINT total_positive CHECK (total >= 0) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT total_positive;

Watch: Two statements, two different lock levels. Compare the second one's lock with the first's.

Foreign keys lock more than you asked for

Adding a foreign key from orders.customer_id to customers.id is a change to orders. That is not the whole story.

Predict

Which tables does adding that foreign key lock against writes?

Try this
ALTER TABLE orders ADD CONSTRAINT orders_customer_fkey
  FOREIGN KEY (customer_id) REFERENCES customers (id);

Watch: The lock queue while it runs. Both tables are in it.

A CHECK can buy you a NOT NULL

SET NOT NULL has no NOT VALID form. It scans the whole table under ACCESS EXCLUSIVE, and there is no keyword that changes that.

There is a way round it that is not a trick. Add CHECK (col IS NOT NULL) as NOT VALID, validate it under the weak lock, and then run SET NOT NULL. PostgreSQL sees a validated constraint that already proves the fact, trusts it, and skips the scan entirely. The final statement becomes a catalog update.

Once the column is NOT NULL the CHECK is redundant and can be dropped.

Try this
ALTER TABLE orders ALTER COLUMN note SET NOT NULL;

Watch: Twenty seconds of ACCESS EXCLUSIVE, and everything queued behind it.

Try this
ALTER TABLE orders ADD CONSTRAINT note_set CHECK (note IS NOT NULL) NOT VALID;
ALTER TABLE orders VALIDATE CONSTRAINT note_set;
ALTER TABLE orders ALTER COLUMN note SET NOT NULL;
ALTER TABLE orders DROP CONSTRAINT note_set;

Watch: Four statements instead of one. The last one is a catalog update, because the third statement no longer has anything to prove.

This is the shape of almost every safe migration: more statements, each of them cheap, instead of one that is correct and unshippable.

The database

The same one every claim above was made about. Nothing here is graded — run whatever you like.

Reading your progress…

Answer the 1 prediction above first.

All tracks
Everything you do here stays in this browser.Part of liter8.sh