Skip to content
AgileLogics
6 min read

Schema Migrations

Prerequisites: Creating tables, Transactions

Applications evolve, and so do their databases: new tables, new columns, renamed fields. A migration is a versioned script that changes the schema in a controlled, repeatable way.

Why migrations

  • Every environment — your laptop, colleagues' machines, test, production — gets the same schema changes in the same order.
  • Changes are stored in version control alongside the code that needs them.
  • The database records which migrations have run, so each runs exactly once.

Migration tools

Most frameworks include one — Laravel migrations, Django migrations, Rails Active Record migrations, Entity Framework migrations, Prisma Migrate — and standalone tools such as Flyway, Liquibase, golang-migrate, and Alembic work with any stack. A plain-SQL migration might look like:

-- migrations/0007_add_phone_to_customers.up.sql
ALTER TABLE customers ADD COLUMN phone TEXT;
-- migrations/0007_add_phone_to_customers.down.sql
ALTER TABLE customers DROP COLUMN phone;

The "down" script reverses the change, for rolling back.

Changing live databases safely

On a busy production database, some changes lock tables or break the running application. Safe patterns:

Adding a required column — in steps:

  1. Add the column as nullable (fast, non-blocking).
  2. Deploy code that writes the new column.
  3. Backfill existing rows in batches.
  4. Add the NOT NULL constraint.

Renaming a column — "expand and contract":

  1. Add the new column; write to both old and new.
  2. Backfill; switch reads to the new column.
  3. Stop writing the old column, then drop it in a later release.

Renaming directly breaks every running instance of the old code the moment the migration runs.

Adding an index on a large table — use non-blocking options where available (CREATE INDEX CONCURRENTLY in PostgreSQL; online DDL in MySQL).

Good habits

  • Keep each migration small and focused, and never edit a migration that has already run in production — write a new one.
  • Test migrations against a copy of production-sized data before running them for real.
  • Take a backup before significant changes, and know how you'd roll back.
  • Run migrations in a transaction where the database supports transactional DDL (PostgreSQL and SQLite do; MySQL commits DDL immediately).