Some migration tools do support rollback scripts for schema changes, but unless you're actually testing these before release (deploy the new version in staging, accumulate representative data in the new schema, roll back the schema, deploy an old version of the app, test that it is doing the right thing), then they aren't really something you can rely on in production.
* Use external online schema change tooling which operates on a shadow table, so the tooling can be interrupted without affecting the original table. (Generally all of the open source MySQL online schema change tools work this way.)
* Use declarative schema management (e.g. tool operates on repo of CREATE TABLE statements), so that humans never need to bother writing "down" migrations. Want to roll something back? Use `git revert` to create a new commit restoring the CREATE TABLE to its previous state, and then push that out in your schema management system. (Full disclosure, I spend my time developing an open source system in this area, https://skeema.io)
* Ensure that your ORMs / data access layers don't interact with brand new columns until a code push occurs or a feature flag is flipped.
Lets say you want to add a new non nullable column with foreign keys, to replace an old non nullable column of foreign keys to a different table that’s obsolete and needs to be deleted.
1) update the code to be ok with a new nullable column. Rollback: deploy previous version of code.
2) create the new column in DB with it’s desired constraint, but make it nullable. Roll back: delete the column.
3) have the code start populating the new column as well as the old. Rollback: deploy previous version of code.
4) start backfilling historical entries with the new column. Rollback: you can’t roll this back!
5) make new column non nullable. Rollback: make it nullable.
6) update code to read from new column, continuing to write to both. Rollback: deploy previous version of code.
7) make old column nullable. Rollback: you can’t roll this back!
8) stop writing to old column. Rollback: deploy previous version of code.
9) once you’re satisfied the old column is no longer used and the version of code from the previous step will never be deployed again, drop the old column.
Rollback: you can’t roll this back!
10) rename the obsoleted table and see if anything breaks. Rollback: rename it back to its original name.
11) delete the obsoleted renamed table.
Rollback: you can’t roll this back!