agent assumed DDL was transactional but mysql commits implicitly - the half-applied migration cant be rolled back
Recovery and prevention for an agent that assumed DDL was transactional on mysql: the half-applied migration cannot be rolled back because mysql commits DDL implicitly. Use when a mysql migration failed midway and left a partial schema. Not for postgres, where transactional DDL usually allows rollback.
TL;DR
MySQL commits every DDL statement implicitly, so the failed migration left some statements applied and some not, with no transaction to roll back. Inventory which statements took effect by comparing the migration file against the live schema, apply the missing ones by hand, and mark the migration complete. Going forward, write mysql migrations as forward-only and idempotent, and test them on mysql, never sqlite.
The query
agent assumed DDL was transactional but mysql commits implicitly - the half-applied migration cant be rolled backUse this when
- A mysql migration failed halfway and cannot be rolled back
- Agent assumed DDL was transactional but mysql commits implicitly
- Half-applied migration on mysql with no transaction to roll back
- Writing mysql-safe migrations after a transactional assumption broke
Not for
- Postgres migrations (transactional DDL usually rolls back cleanly there)
- A mysql migration that failed before any statement ran (just fix and re-run)
- DML-only migrations (those are transactional on mysql's innodb)
Steps
1. Inventory what applied and what did not
Read the migration file top to bottom. For each DDL statement, check the live schema: does the table, column, index, or constraint exist?
SHOW CREATE TABLE [table];Expected output: a statement-by-statement list of applied vs not-applied. This inventory is the recovery plan; do not proceed without it.
2. Apply the missing statements by hand
Run the not-applied statements individually, in order, watching each one succeed. Skip the ones already applied: re-running a CREATE TABLE that exists errors, and mysql has no IF NOT EXISTS for every statement type, so the inventory from step 1 is what keeps you safe.
Expected output: the schema fully matches the migration's intended end state, verified statement by statement.
3. Mark the migration complete in the history table
Update the migration state (flywayschemahistory, alembic_version, or your tool's equivalent) to record the version as applied, since you applied it by hand. Rehearse the exact update on a copy first.
Expected output: the history agrees with the schema, and the next migrate run continues from the right version instead of retrying the half-applied file.
4. Rewrite future mysql migrations as forward-only and idempotent
Each migration must tolerate partial application: guard with existence checks where the dialect allows, keep migrations small (one logical change each), and never rely on rollback. The down migration, if one exists, is for tested reversals, not for crash recovery.
Expected output: the next mid-migration failure is a short inventory-and-finish, not a forensic exercise.
5. Test migrations on mysql, never on sqlite
Add a CI job that runs the migration chain against real mysql (a container is fine). Sqlite's transactional DDL hides exactly the failure mode that just bit you.
Expected output: mysql-specific behavior (implicit commits, lock behavior) is tested where it will actually run.
Tool compatibility
mysql 5.7, 8.0, and mariadb. Postgres and sqlite behave differently (transactional DDL); the recovery pattern here is mysql-specific.
Variant phrasings
mysql DDL implicit commit half-applied migration
Steps 1 through 3: inventory, hand-apply the rest, mark complete.
agent assumed DDL transactional mysql
Steps 4 and 5: forward-only idempotent migrations, tested on real mysql.
mysql migration failed midway cannot rollback
Step 1 first: you cannot roll back, so the only path is forward through the inventory.
Why it happens
MySQL's storage engine interface commits implicitly around DDL: each ALTER, CREATE, or DROP ends whatever transaction was open. This is a documented engine behavior, not a bug, and it means "the migration transaction" never existed on mysql. Tools written against postgres semantics (alembic's transactional upgrades) silently lose their safety net on mysql.
Edge cases
- Some statements are not safely re-runnable even when skipped correctly (e.g. data backfills inside the migration): inventory the DML too, not just the DDL.
- pt-online-schema-change and gh-ost exist precisely because mysql DDL locks and commits: for large tables, prefer them over raw ALTER in migrations.
- Marking the migration complete by hand is a lie if the inventory was wrong: verify the end state against a scratch database migrated cleanly, not just against your notes.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstr6WM9Bcl7t_yMYrtqMc6w