Can Doctrine Migrations Detect Non‑Transactional DDL Automatically?
0 reputation · 08 Apr 2020, 11:35 UTC
0 reputation · 08 Apr 2020, 11:35 UTC
Ensure that a Doctrine migration can safely roll back a schema change without risking data loss or transaction failure.
Doctrine’s migration system exposes a transactional flag that defaults to true, wrapping the entire migration in a database transaction. The framework does not analyze the SQL to identify non‑transactional DDL statements such as CREATE INDEX on MySQL’s MyISAM engine. If such a statement runs inside a transaction, the database may raise an error, causing the migration to fail or leave the schema in an inconsistent state. Current practice requires developers to manually set transactional: false for migrations that contain unsupported DDL, but this introduces the risk of accidental data loss when the down method drops columns or tables.
There is no built‑in mechanism to preserve data modified during a migration when rolling back a column or table change, and the Doctrine roadmap has not yet decided whether to add automatic detection of non‑transactional statements.
35275 reputation · 08 Apr 2020, 18:42 UTC
No. Doctrine Migrations does not analyze the SQL inside a migration to detect non‑transactional DDL statements. The only control is the transactional flag on the migration class, which defaults to true. You must set transactional: false yourself when a migration contains statements that the target database engine cannot run inside a transaction.
| Engine | Transactional DDL Support | Typical Failure Mode | Required Config |
|---|---|---|---|
| MySQL InnoDB | Most DDL (CREATE/DROP TABLE, ALTER TABLE, CREATE INDEX) is transactional | Rare; only a few statements like CREATE DATABASE or DROP DATABASE are non‑transactional | Leave transactional: true (default) |
| MySQL MyISAM | No DDL is transactional | Implicit commit before/after each DDL; transaction rolls back with error | Set transactional: false |
| PostgreSQL | Most DDL is transactional, but CREATE INDEX CONCURRENTLY, VACUUM, CREATE DATABASE, DROP DATABASE are not | ERROR: cannot run CREATE INDEX CONCURRENTLY inside a transaction block | Set transactional: false for migrations containing those statements |
| SQLite | DDL is transactional in modern versions, but VACUUM, ATTACH/DETACH DATABASE, and some pragma statements are not | Transaction rollback or SQLITE_ERROR | Set transactional: false when using non‑transactional pragmas or VACUUM |
| SQL Server | Most DDL is transactional; CREATE DATABASE, DROP DATABASE, and full‑text catalog commands are not | Error 3902: The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION | Set transactional: false for those specific commands |
| Oracle | DDL issues an implicit commit; cannot be rolled back | Implicit commit ends the transaction; subsequent rollback has no effect on DDL | Set transactional: false (DDL never rolls back anyway) |
mysql, postgresql) at analysis time, not just at execution time.addSql() with parameters), static analysis cannot be 100% reliable. A fallback would be a new annotation like @nonTransactionalStatements or a method override.transactional flag. Any auto-detection would need to default to the current behavior unless opted in, otherwise it would silently change rollback semantics for existing projects.transactional: false manually would see no change.true) but contain non‑transactional DDL would suddenly start failing at analysis time instead of at runtime—arguably an improvement, but a breaking change for CI pipelines that don’t run the new analyzer.$this->addSql(sprintf(...))) would get false negatives unless they also adopt declarative hints.protected $transactional = false; to those migration classes.transactional: false, implement a down() that restores data from a backup table or uses a separate data‑migration step, because a failed rollback cannot be wrapped in a transaction.doctrine:migrations:migrate --dry-run against a staging database matching production’s engine and version to verify behavior before deploying.Which database engine and version are you targeting in production? The exact list of non‑transactional statements differs between MySQL 5.7 vs 8.0, PostgreSQL 13 vs 16, etc., and that changes which migrations need transactional: false.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.