ALTER TABLE ... ALTER COLUMN ... TYPE ... USING ...
(or related things like ALTER TABLE ... ADD COLUMN ... DEFAULT ... NOT NULL
) requires a full table rewrite with an exclusive lock.
You can, with a bit of effort, work around this in steps:
ALTER TABLE thetable ADD COLUMN thecol_tmp newtype
withoutNOT NULL
.Create a trigger on the table that, for every write to
thecol
, updatesthecol_tmp
as well, so new rows that're created, and rows that're updated, get a value fornewcol_tmp
as well asnewcol
.In batches by ID range,
UPDATE thetable SET thecol_tmp = CAST(thecol AS newtype) WHERE id BETWEEN .. AND ..
once all values are populated in
thecol_tmp
,ALTER TABLE thetable ALTER COLUMN thecol_tmp SET NOT NULL;
.Now swap the columns and drop the trigger in a single tx:
BEGIN;
ALTER TABLE thetable DROP COLUMN thecol;
ALTER TABLE thetable RENAME COLUMN thecol_tmp TO thecol;
DROP TRIGGER whatever_trigger_name ON thetable;
COMMIT;
Ideally we'd have an ALTER TABLE ... ALTER COLUMN ... CONCURRENTLY
that did this within PostgreSQL, but nobody's implemented that. Yet.