Secure connections
Save PostgreSQL connection details with encrypted passwords, SSL settings, URL import, testing, search, edit, and delete actions.
Manage PostgreSQL table columns
Every ALTER TABLE looks equally simple in SQL, and they are not equally safe. Some finish instantly on any table size; others rewrite every row while holding a lock that blocks reads and writes.
Short answer: ADD COLUMN with a constant default is instant on modern PostgreSQL. RENAME COLUMN is instant. DROP COLUMN is instant but does not reclaim the space. ALTER COLUMN TYPE usually rewrites the whole table under an ACCESS EXCLUSIVE lock, which is the one that causes outages. Always set lock_timeout before altering a busy table.

PgDeck is shaped around PostgreSQL tasks that happen every day: connect, inspect, filter, edit, query, manage structure, copy, and export.
Save PostgreSQL connection details with encrypted passwords, SSL settings, URL import, testing, search, edit, and delete actions.
Browse schemas and tables, search the table list, pin favorites, filter rows, sort columns, resize columns, and paginate results.
Run SELECT and non-SELECT statements from multi-tab SQL editors with syntax highlighting and interactive query results.
Edit cells, add rows, duplicate rows, set NULL, stage deletes, review pending changes, and commit everything in one transaction.
Since PostgreSQL 11, adding a column with a constant default no longer rewrites the table — the default is stored as metadata and applied on read. This is instant regardless of table size. A volatile default such as now() or random() still forces a full rewrite, because each row needs its own value.
-- Instant on any table size
ALTER TABLE users ADD COLUMN country text;
ALTER TABLE users ADD COLUMN plan text NOT NULL DEFAULT 'free';
-- Rewrites every row: volatile default
ALTER TABLE users ADD COLUMN token uuid NOT NULL DEFAULT gen_random_uuid();ALTER TABLE needs an ACCESS EXCLUSIVE lock. If a long-running query holds the table, your ALTER waits — and every query arriving after it queues behind your pending lock, including reads. That is how a one-second migration takes a site down. A short lock_timeout makes the ALTER fail fast instead.
SET lock_timeout = '3s';
ALTER TABLE orders ADD COLUMN note text;
-- If it cannot get the lock in 3s it errors out, and you retry laterRENAME is a catalog change only, so it is instant on any table size. The risk is not the database, it is the application: every query referencing the old name breaks the moment it commits. For zero-downtime, add the new column, write to both, migrate reads, then drop the old one.
ALTER TABLE users RENAME COLUMN signup_date TO created_at;Most type changes rewrite the entire table and hold an exclusive lock for the duration — minutes or hours on a large table. Some widening conversions are exempt, such as varchar(50) to varchar(100) or to text. When a cast is not implicit, USING tells PostgreSQL how to convert.
-- No rewrite: widening within the same type
ALTER TABLE users ALTER COLUMN name TYPE text;
-- Full rewrite under ACCESS EXCLUSIVE: test the duration first
ALTER TABLE events ALTER COLUMN payload TYPE jsonb USING payload::jsonb;SET NOT NULL normally scans the whole table to verify no nulls exist. On PostgreSQL 12 and later you can avoid that: add a validated CHECK constraint first — NOT VALID then VALIDATE, which takes only a share lock — and SET NOT NULL will then use it as proof and skip the scan.
ALTER TABLE users ADD CONSTRAINT users_country_nn
CHECK (country IS NOT NULL) NOT VALID;
ALTER TABLE users VALIDATE CONSTRAINT users_country_nn; -- no exclusive lock
ALTER TABLE users ALTER COLUMN country SET NOT NULL; -- now instant
ALTER TABLE users DROP CONSTRAINT users_country_nn;DROP COLUMN is instant because PostgreSQL only marks the column dropped in the catalog — the data stays in each row until that row is rewritten. Disk space is reclaimed gradually by normal updates, or immediately by VACUUM FULL, which takes an exclusive lock and needs free space equal to the table.
ALTER TABLE users DROP COLUMN legacy_flag;
-- Space is only reclaimed on rewrite:
VACUUM FULL users; -- ACCESS EXCLUSIVE, needs room for a full copyPeople assume a slow ALTER only blocks other writers. It is worse than that: when your ALTER waits for an ACCESS EXCLUSIVE lock, every subsequent query on that table queues behind your request, including plain SELECTs. One long-running report can therefore turn a trivial ALTER into a total stall on that table. Setting lock_timeout means you fail and retry instead of taking the application down.
Rewrites: most ALTER COLUMN TYPE changes, and ADD COLUMN with a volatile default. No rewrite: ADD COLUMN with a constant default or no default, RENAME COLUMN, DROP COLUMN, SET DEFAULT, DROP NOT NULL, and widening conversions within a type family such as varchar to text. Knowing which list an operation is on is the entire difference between a safe migration and a risky one.
The rename itself is instant and safe. What breaks is every deployed application instance still sending the old column name. The standard zero-downtime pattern is expand then contract: add the new column, write to both from the application, backfill, switch reads, deploy, then drop the old column in a later release. Renaming in place is fine in development and rarely fine in production.
A single UPDATE touching millions of rows holds locks and generates a huge amount of WAL. Batch it: update a bounded set of rows at a time in separate transactions, with a short pause between batches. It takes longer overall and it does not block anything, which is the trade you want on a production database.
ALTER TABLE and DROP COLUMN execute as readily as a SELECT and cannot be corrected by another statement afterwards. Reading the exact SQL before it runs, and taking a backup when the table matters, is the whole safety procedure. Clients that show the generated SQL and require confirmation for structure actions exist because this is a common way to lose data.
More on choosing, comparing, and running a PostgreSQL desktop client.
ALTER TABLE tablename ADD COLUMN colname type. On PostgreSQL 11 and later this is instant even with a constant default, because the default is stored as metadata rather than written to every row. A volatile default such as gen_random_uuid() does force a full table rewrite.
Yes, it takes an ACCESS EXCLUSIVE lock. If another query is holding the table, your ALTER waits — and every query arriving afterwards queues behind it, including reads. Set lock_timeout to a few seconds so the ALTER fails fast rather than stalling the table.
Most type changes rewrite the whole table under an exclusive lock, so test the duration on a copy first. Widening within a type family, such as varchar(50) to text, does not rewrite. For a genuinely large table, the safe pattern is adding a new column, backfilling in batches, switching the application, then dropping the old one.
Not immediately. PostgreSQL marks the column dropped in the catalog and leaves the data in place until each row is rewritten by a normal update. Space is reclaimed gradually, or all at once with VACUUM FULL, which takes an exclusive lock and needs free space equal to the table size.
Add the column with a constant default, which is instant. If you need NOT NULL on an existing column, add a CHECK constraint as NOT VALID, VALIDATE it under a share lock, then SET NOT NULL — PostgreSQL 12 and later uses the validated constraint as proof and skips the full table scan.