A database migration is a versioned, incremental change to a database schema, written as code, applied in a defined order, and recorded so that every environment — a laptop, a staging server, production — can be brought to the exact same structure. Instead of a person connecting to production and running an ALTER TABLE by hand, a migration is a file committed to version control, reviewed like any other change, and applied by a tool that tracks exactly which migrations have already run.
What changed in 2026
- Branch-based database testing spread widely, with tools like Neon letting teams create an instant copy-on-write branch of production data to test a migration safely before it touches anything real.
- TypeScript-native migration tools (Drizzle Kit, Prisma Migrate) became the default for JavaScript backends, generating SQL migrations directly from a schema file instead of hand-written DDL.
- Migration review became a standard pull request checklist item, with teams explicitly checking for locking behavior and reversibility before approving, not just correctness.
- Automatic migration linting tools appeared, flagging risky patterns — a column added with a volatile default, a missing index on a foreign key — before a human even reviews the diff.
The anatomy of a migration
Every migration tool, regardless of language, tracks the same basic things:
| Element |
Purpose |
| Version or timestamp |
Orders migrations so they apply in a consistent sequence |
| Up step |
The change to apply — add a column, create a table, add an index |
| Down step |
The reverse, if one is possible — used to roll back |
| Migration history table |
A record, stored in the database itself, of which migrations already ran |
That history table is what lets a tool safely run migrate on any environment and apply only what is missing, instead of blindly re-running everything.
Schema migrations vs data migrations
A schema migration changes structure: adding a table, a column, an index, a constraint. A data migration changes or moves the values already stored: backfilling a new column, correcting bad records, transforming data into a new shape. Treating these as the same step is one of the most common sources of pain — a schema change is usually fast, but a data change on a large table can take minutes or hours and needs its own plan, often running as a background job rather than inside the migration itself.
Why migrations exist instead of manual schema edits
- Every environment reaches the same state, because the migration history table tracks exactly what has been applied where.
- Changes are reviewable before they run, the same way application code is reviewed, instead of being typed directly into a production console.
- A team of any size can coordinate schema changes without someone needing to remember what everyone else already ran.
- A smoke test after a migration confirms the application still starts and the critical paths still work, catching schema drift before real users hit it.
Common mistakes
Treating "no rollback" as an oversight rather than a decision. Some migrations genuinely cannot be reversed cleanly, such as one that drops a column. That is fine as long as it is deliberate and the team knows a rollback means restoring from backup, not running a down step.
Running a large data change inside the same migration as a schema change. A slow backfill inside a schema migration holds locks longer than necessary and blocks deploys.
Skipping a realistic test run before production. A migration that takes two seconds on a development database with a hundred rows can take twenty minutes on a production table with a hundred million.
What to skip
- Editing an already-applied migration file. Once it has run anywhere, treat it as permanent; make the correction in a new migration instead.
- Coordinating schema changes over chat instead of through the migration history table, which exists precisely so no one has to remember and communicate manually.
- Running migrations from a developer laptop against production. Route them through the same CI/CD path as everything else so there is one auditable trail.
FAQ
Is a database migration the same as a backup?
No. A migration changes schema structure going forward. A backup is a snapshot you restore from if something goes badly wrong, including a migration that cannot be cleanly reversed.
What is a migration history table?
A table the migration tool creates inside your own database to record which migrations have already been applied, so re-running the migration command only applies what is new.
Do NoSQL databases have migrations too?
The concept applies more loosely. Schema-less databases still accumulate implicit structure in application code, and teams commonly write migration-style scripts to transform existing documents when that implicit structure changes.
Why do some migrations fail only in production?
Usually scale. A lock, a long-running update, or a full table rewrite behaves very differently against a production-sized table than against a small development database.
Where to go next