Commit the migration run instead of trusting MySQL to
SQLAlchemy 2.0 connections do not autocommit, and on MySQL alembic reports "non-transactional DDL", so context.begin_transaction() is a no-op. Nothing in a migration run committed. It looked like it worked because MySQL implicitly commits on DDL: every ALTER/CREATE flushed whatever was queued before it, including the PREVIOUS migration's version stamp. The LAST migration of a run has no DDL after it, so its stamp was rolled back when the connection closed. `flask db upgrade` then exited 0 with the schema change really applied and alembic_version one revision behind, and re-ran that same migration on the next deploy. A migration that is not idempotent applies twice. Found while adding a core migration: the column default really changed and alembic_version still named its parent, with UPDATE alembic_version followed immediately by ROLLBACK in the log. Existing databases upgraded before this are one revision behind their real schema. Compare `flask db current` against `flask db heads`; re-running the upgrade re-applies the final migration once and lands the stamp.
This commit is contained in:
@@ -147,6 +147,21 @@ def run_migrations_online():
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
# COMMIT THE RUN. SQLAlchemy 2.0 connections do not autocommit, and on
|
||||
# MySQL alembic reports "non-transactional DDL" so begin_transaction()
|
||||
# above is a no-op - nothing here commits on its own.
|
||||
#
|
||||
# It looked like it worked because MySQL implicitly commits on DDL: each
|
||||
# ALTER/CREATE flushed everything queued before it, including the
|
||||
# PREVIOUS migration's version stamp. The LAST migration of every run
|
||||
# has no DDL after it, so its stamp was rolled back at close. The column
|
||||
# changes survived (already committed by their own DDL) while
|
||||
# alembic_version stayed one revision behind, so `flask db upgrade`
|
||||
# exited 0 having silently re-run the final migration, and re-ran it
|
||||
# again on the next deploy. A migration that is not idempotent would
|
||||
# apply twice.
|
||||
connection.commit()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
|
||||
Reference in New Issue
Block a user