1. Lock Conflicts — The Sneaky Culprit
This is the one I see most often. Your migration runs, hits a table that's locked by another process, and just stops. No error message that makes sense — just a timeout or a vague "deadlock found." I've seen this happen when a cron job was running a report on the same table while the migration tried to add a column.
How to check
- Open your database console.
- Run this command (MySQL example):
SHOW PROCESSLIST;
Look for rows where Command is Query and Time is high. That's your blocker.
What to do
- Kill the blocking query. In MySQL:
KILL <process_id>;
Replace <process_id> with the number from the first column of SHOW PROCESSLIST.
After killing it, your migration should finish. But wait — the locked tables might have left the migration in a weird state. Check the migration history table. For Laravel, it's migrations. For Django, it's django_migrations. If the last migration is marked as completed but didn't actually finish, you need to remove that record.
DELETE FROM migrations WHERE migration = '2025_03_21_123456_add_status_column';
Then run the migration again. It'll pick up where it stopped.
Real story: A client had a migration fail every Friday at 2 PM. Turned out their backup job locked the users table. Moved the backup to 3 AM. Problem gone.
2. Schema Version Mismatch — The "I Forgot to Git Pull" Problem
You're on branch A, you wrote a migration. Then you switch to branch B, which has a different migration history. Your local schema version number doesn't match what's in the database. When the migration runs, it tries to apply something that's already there — or skips something that's missing. The result? A partial mess.
How to check
Look at your migration files and compare them to the migrations table. If you see a migration file that's in the folder but not in the table — or vice versa — you've got a version mismatch.
What to do
The fix depends on your framework. For Laravel:
- Rollback the incomplete batch:
php artisan migrate:rollback --step=1
This rolls back the last batch. You'll see something like:
Rolled back: 2025_03_21_123456_add_status_column
If it says "Nothing to rollback," then the migration wasn't fully applied. In that case, manually delete the record from the migrations table.
- Then reset and re-run all migrations (careful — this drops tables):
php artisan migrate:fresh
For Django:
- Check migration status:
python manage.py showmigrations
Look for migrations marked as [X] (done) that shouldn't be, or [ ] (not done) that should.
- To fix, you can fake a rollback:
python manage.py migrate myapp 0001 --fake
This tells Django "pretend you rolled back to version 0001." Then run:
python manage.py migrate myapp
It'll re-apply the missing ones.
Don't use --fake unless you know what you're doing. It won't change the actual database schema — it only updates the migration history. If the schema is out of sync, you'll end up with a bigger mess.
3. Migration Timeout — The "SQL Query Too Slow" Problem
You're adding a column to a table with 10 million rows. Or you're changing a column type on a busy production server. The migration starts, locks the table, and then the database says "nope, you took too long." The lock releases, but half the rows got the new column, and the other half didn't. Now your app crashes because some rows have the column and some don't.
How to check
Check your database logs. Look for:
ERROR 1205 (HY000): Lock wait timeout exceeded
Or in PostgreSQL:
FATAL: sorry, too many clients already
What to do
- First, rollback the partial migration (same method as section 2).
- Then, adjust the timeout. In MySQL, raise the lock wait timeout:
SET GLOBAL innodb_lock_wait_timeout = 120;
That gives you 120 seconds instead of the default 50.
- For really big tables, don't do the migration during business hours. Do it at 3 AM when nobody's hitting the database.
- Alternatively, break the migration into smaller steps. Instead of one migration that adds a column and then updates it, do two separate migrations: one to add the column (with a default value), and one to populate it.
My rule: If a migration takes more than 5 seconds on a test database with 100k rows, it'll fail on production. Redesign the migration.
Quick-Reference Summary
| Cause | Symptoms | Fix |
|---|---|---|
| Lock conflict | Migration hangs, timeout, deadlock message | Kill blocking query, remove partial migration record, retry |
| Version mismatch | Migration history table doesn't match files | Rollback batch, or fake migration to sync history |
| Timeout | Lock wait timeout error, partial schema changes | Raise timeout, run off-hours, break into smaller migrations |
Remember: the worst thing you can do when a migration fails is to keep hammering the same command. Stop. Figure out what broke. Then apply the right fix. Your database will thank you.