Schema Migration Fails Mid-Run: Fix It Fast

Your schema migration crashed halfway through. Here's how to undo the mess and get back on track.

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

  1. Open your database console.
  2. 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

  1. 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:

  1. 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.

  1. Then reset and re-run all migrations (careful — this drops tables):
php artisan migrate:fresh

For Django:

  1. Check migration status:
python manage.py showmigrations

Look for migrations marked as [X] (done) that shouldn't be, or [ ] (not done) that should.

  1. 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

  1. First, rollback the partial migration (same method as section 2).
  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.

  1. For really big tables, don't do the migration during business hours. Do it at 3 AM when nobody's hitting the database.
  2. 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.

Related Errors in Database Errors
Fix phpMyAdmin Configuration File Writable Warning Table row count wrong after MySQL restore? Fix it here Fix Query Execution Plan Invalidated Error in SQL Server 0XC00D135D Fix NS_E_TRACK_PURCHASE_MAXIMUM_EXCEEDED (0XC00D135D) Quickly

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.