-- This locks the entire table
ALTERTABLEusersADDCOLUMNphoneVARCHAR(20)NOTNULL;-- 10 million rows, exclusive lock held for minutes
-- Application queries queue up
-- Users see errors
-- Step 1: Add new column
ALTERTABLEusersADDCOLUMNfull_nameVARCHAR(100);-- Step 2: Copy data
UPDATEusersSETfull_name=nameWHEREfull_nameISNULL;-- Step 3: Deploy code using both columns (reads from full_name, writes to both)
-- Step 4: Deploy code using only full_name
-- Step 5: Drop old column
ALTERTABLEusersDROPCOLUMNname;
CREATEINDEXidx_users_emailONusers(email);-- Table locked for minutes during index build
Good: CONCURRENTLY doesn’t lock
1
2
3
4
5
-- PostgreSQL
CREATEINDEXCONCURRENTLYidx_users_emailONusers(email);-- Takes longer but doesn't block
-- Check for completion: SELECT * FROM pg_stat_progress_create_index;
Gotcha: CONCURRENTLY can fail and leave an invalid index:
1
2
3
4
5
6
7
-- Check for invalid indexes
SELECTindexrelid::regclass,indisvalidFROMpg_indexWHERENOTindisvalid;-- If found, drop and recreate
DROPINDEXCONCURRENTLYidx_users_email;CREATEINDEXCONCURRENTLYidx_users_emailONusers(email);
Bad: Adding FK validates all existing data with lock
1
2
3
4
ALTERTABLEordersADDCONSTRAINTfk_orders_userFOREIGNKEY(user_id)REFERENCESusers(id);-- Scans entire table with lock held
Good: Add NOT VALID, then validate separately
1
2
3
4
5
6
7
-- Step 1: Add constraint without validation (instant)
ALTERTABLEordersADDCONSTRAINTfk_orders_userFOREIGNKEY(user_id)REFERENCESusers(id)NOTVALID;-- Step 2: Validate existing data (no lock on writes)
ALTERTABLEordersVALIDATECONSTRAINTfk_orders_user;
classAddPhoneToUsers<ActiveRecord::Migration[7.0]disable_ddl_transaction!# Required for CONCURRENTLYdefchangeadd_column:users,:phone,:stringadd_index:users,:phone,algorithm::concurrentlyendend
# migrations/0042_add_phone.pyfromdjango.dbimportmigrationsclassMigration(migrations.Migration):atomic=False# Disable transaction for concurrent opsoperations=[migrations.AddField(model_name='user',name='phone',field=models.CharField(max_length=20,null=True),),migrations.RunSQL("CREATE INDEX CONCURRENTLY idx_phone ON users(phone);",reverse_sql="DROP INDEX CONCURRENTLY idx_phone;"),]