I have a table on PostgresSQL with millions of rows. The table has columns a and b. I already have a unique index on a and b:


I want to add a composite primary key on columns a and b.

Will there be any downtime if I do a

ALTER TABLE table_name ADD PRIMARY KEY (a, b);
  • There is no real difference between your existing unique constraint and a primary key. So why do you think you need the primary key?
    – user1822
    Commented Mar 15, 2023 at 21:40
  • @a_horse_with_no_name I need it due to pg_logical replication
    – gkatzioura
    Commented Mar 15, 2023 at 21:58
  • Hi, and welcome to dba.se! You could set up a little test to check times with a few thousand records and see what happens then? Don't forget to drop your constraint after you create the primary key!
    – Vérace
    Commented Mar 15, 2023 at 22:38

1 Answer 1


Will there be any downtime?

While CREATE INDEX only blocks concurrent writes and still allows reads, you need ALTER TABLE to add the PRIMARY KEY constraint, and that takes an ACCESS EXCLUSIVE lock on the table, blocking reads, too. So, yes, if the table is essential, I would call that "downtime".

However, there are options to alleviate the pain.

First, both index columns must be NOT NULL. While a UNIQUE constraint / index allows null values, the PK does not. If that's not the case, fix it first. Else, the later ALTER TABLE will do it for you, extending the downtime. The manual:

If [...] the index's columns are not already marked NOT NULL, then this command will attempt to do ALTER COLUMN SET NOT NULL against each such column. That requires a full table scan to verify the column(s) contain no nulls. In all other cases, this is a fast operation.

So, first (after fixing any null values):

ALTER TABLE public.demo

Then, the question is: unique index or constraint? An index can be repurposed in a very fast "metadata only" operation. The same is not possible for a constraint, unfortunately. One might think it should be: it's still the same index. But it's just not implemented. Too rare a use-case that anyone would have bothered. You can probably hack the system catalogs. But I am not going there. One false move and you can break the entire database. There was a similar request in the past, and I didn't go there, either:

So, if it's a constraint, build a second, otherwise identical, unique index first. Use a temporary name, it will later be thrown out the window. We have the non-blocking CREATE INDEX CONCURRENTLY for the task since Postgres 8.2 (almost forever now).


Finally, drop the constraint, and add the PK building on the existing or new index:

ALTER TABLE public.demo
  DROP CONSTRAINT a_b  -- if it was a constraint
, ADD  CONSTRAINT demo_pkey PRIMARY KEY USING INDEX a_b_temp;  -- "a_b" if it was an index

This takes said ACCESS EXCLUSIVE lock. But all the hard work has already been done, so it should be a matter of milliseconds - as soon as concurrent transactions let it take the lock.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.