Flyway vs Liquibase: Choosing the Right Migration Tool

You are about to standardize a team on one migration tool, and the decision is not about XML versus SQL — it is about which tool’s failure modes you are willing to operate. Flyway and Liquibase diverge most where it costs you most: how each handles a transaction boundary on a failed migration, how each tracks checksums, and how each recovers from a locked history table after a CI/CD run dies. Pick wrong and you inherit either silent half-applied schemas or noisy checksum drift on every branch merge. This page compares the two on the axes that decide a zero-downtime deploy, and sits within the broader migration tool comparison section of the migration fundamentals guide.

Flyway vs Liquibase decision axes Two columns compare Flyway Community and Liquibase Open Source on transaction wrapping, rollback primitives, and checksum granularity, the three axes that drive operational risk. Where the Two Tools Diverge Flyway (Community) Liquibase (OSS) Txn wrapping manual BEGIN/COMMIT per-changeset, auto Rollback U-scripts / paid undo rollback <tag> built-in Checksum per-file CRC-32 per-changeset MD5 On MySQL neither tool can roll back DDL — the engine commits it implicitly.
The choice turns on transaction handling, rollback primitives, and checksum granularity — not on file format.

Symptom / Error Signatures

These signatures point to a tool-behavior mismatch rather than a bad script:

  • ERROR: lock timeout / deadlock detected during ALTER TABLE from a migration runner
  • FlywayException: Migration checksum mismatch or LiquibaseException: Checksum validation failed
  • flyway_schema_history or DATABASECHANGELOG held by an orphaned CI/CD session after a runner crash
  • PostgreSQL ERROR: cannot execute ALTER TABLE in a read-only transaction
  • Connection-pool exhaustion as a prolonged DDL blocks concurrent reads and writes

Root Cause Analysis

The divergence is rooted in how each tool manages the transaction boundary and the history table. Flyway Community does not wrap each migration in a transaction by default on most databases — a failed migration leaves the database partially altered unless you wrap the SQL yourself in BEGIN/COMMIT. Liquibase wraps each changeset in a transaction where the database supports it, at the cost of XML/YAML parsing and pre-flight validation overhead. On top of that, both tools track applied state in a history table, and concurrent pipelines competing for that table produce the lock contention you see in the logs. Both also surface checksum errors when legacy scripts lack conditional guards, which is why idempotent script design reduces churn regardless of tool. Crucially, the transactional advantage evaporates on MySQL, where DDL forces an implicit commit — the reason is detailed in handling non-transactional DDL in MySQL migrations.

Dimension Flyway (Community) Liquibase (Open Source)
Migration format Versioned SQL files SQL, XML, YAML, JSON changesets
Rollback support Manual U scripts; undo requires Teams/Enterprise liquibase rollback <tag> built-in
Checksum enforcement Per-file CRC-32; repair clears failed entries Per-changeset MD5; clearCheckSums forces recalc
Transactional wrapping Manual (BEGIN/COMMIT in your SQL) Per-changeset, configurable via runInTransaction
CI/CD integration Docker image, Maven/Gradle plugins, CLI Same, plus Spring Boot auto-configuration
Transaction boundary on a failed migration Three statements run in sequence. Without a transaction the first two commit and only the third fails, leaving a half-applied schema. Inside a changeset transaction the failure of statement three rolls back all three atomically. Statement 3 fails: what survives? Flyway, unwrapped ALTER TABLE add column committed — stays CREATE INDEX committed — stays ADD CONSTRAINT FAILS Half-applied schema 1 and 2 linger, no rollback Liquibase changeset txn ALTER TABLE add column inside transaction CREATE INDEX inside transaction ADD CONSTRAINT FAILS All three rolled back schema unchanged, safe retry The transaction boundary, not the file format, decides recoverability.
Without an explicit transaction, a failed third statement strands the first two; a changeset transaction rolls the whole unit back.

Immediate Mitigation

Run as a privileged DBA or service account with pg_terminate_backend / MySQL PROCESS privilege. Halt all deployment pipelines first, and avoid peak traffic windows.

  1. Diagnose the blocking session holding the history table:
-- PostgreSQL · read-only diagnostic · run as a role that can see all sessions
SELECT pid, usename, state, query, wait_event_type, wait_event
FROM pg_stat_activity
WHERE (query LIKE '%flyway_schema_history%' OR query LIKE '%DATABASECHANGELOG%')
  AND state = 'active'
  AND pid <> pg_backend_pid();
-- MySQL · read-only diagnostic · run as a privileged user
SELECT * FROM information_schema.innodb_trx
WHERE trx_query LIKE '%DATABASECHANGELOG%';
  1. Terminate the orphaned lock — PostgreSQL SELECT pg_terminate_backend(<pid>);, MySQL KILL <trx_mysql_thread_id>;. Confirm the session is orphaned, not actively committing.
  2. Reconcile the history table. Flyway: flyway repair fixes checksums of failed migrations and removes failed entries. Liquibase: liquibase clearCheckSums forces recalculation on the next run.
  3. Reverse explicitly when needed. Flyway Teams/Enterprise: flyway undo -target=<previous_version>. Flyway Community: run pre-written inverse DDL wrapped in BEGIN; ... COMMIT;. Liquibase: liquibase rollback <tag_or_date>.

During reconciliation, route application traffic to a read replica or trip circuit breakers. Never force-reset a history table without a verified logical backup.

Permanent Fix / Long-Term Pattern

Remove lock contention and checksum drift with architectural controls rather than tool tuning. Decouple schema from app deploys with the expand-and-contract methodology: add columns and tables first, ship the code, drop legacy objects in a later release. Make transaction handling explicit — on Flyway, wrap DDL in BEGIN;/COMMIT; in your migration files (and remember PostgreSQL DDL is transactional while MySQL DDL is not); on Liquibase, set runInTransaction="false" for statements that cannot run inside a transaction, such as CREATE INDEX CONCURRENTLY. Enforce idempotent script design with IF NOT EXISTS guards and online-build flags (CREATE INDEX CONCURRENTLY on PostgreSQL, ALGORITHM=INPLACE, LOCK=NONE on MySQL). Add a pre-flight gate that runs flyway info or liquibase updateSQL against a staging replica before promotion, and enforce a single-runner policy so no two pipelines target the same history table at once. If a switch is on the table, plan it with migrating from Flyway to Liquibase without downtime.

Expand-and-contract release timeline Three sequenced releases across time. Expand adds columns and tables without breaking old code. Migrate ships the new application. Contract drops the legacy objects in a later release, so no long DDL lock overlaps live traffic. Decouple schema change from app deploy time Release 1 Expand add columns / tables additive, online build old code still works Release 2 Migrate ship new app code reads / writes new no schema lock Release 3 Contract drop legacy objects once nothing reads them separate deploy Each step is independently deployable and reversible. No single long DDL lock ever contends with live traffic.
Splitting the change across three releases keeps every DDL step additive and short, so it never blocks live reads and writes.

Verification Checklist

Choosing on Guarantees, Not Format

The Flyway-versus-Liquibase decision is usually framed as SQL-first versus database-agnostic changelog, but the format is the least important axis. Both record and verify a checksum for every applied change, both track an ordered history, and both refuse to re-run what they have already applied — the properties that actually keep a schema honest. Flyway’s appeal is that a migration is the SQL the database runs, so there is nothing between your intent and the engine and lock behavior is fully visible; its cost is that cross-database portability is your problem. Liquibase’s appeal is an abstract changelog that can target multiple engines and express rollbacks declaratively; its cost is a layer of indirection between what you wrote and what runs, which can obscure the exact DDL and the lock it takes.

The right way to decide is to name the failure you most need to stop paying for by hand and pick the tool that makes preventing it cheapest. If you run a single engine and care most about seeing precisely what locks, Flyway’s transparency wins. If you target several engines or want first-class declarative rollbacks and are willing to trade some transparency for that reach, Liquibase earns its indirection. Either way the non-negotiables are the same — checksum verification on, honest transactional behavior for your engine, and migrations authored to be idempotent and single-purpose — because those habits, not the tool, are what actually make a deploy safe.

Frequently Asked Questions

Which tool should I pick if rollback matters most? Liquibase, because liquibase rollback <tag> is a first-class, built-in primitive in the open-source edition, whereas Flyway’s undo requires a paid tier and otherwise relies on hand-written U scripts. That said, on MySQL neither tool can truly roll back DDL — the engine commits it implicitly — so for MySQL the rollback story is your inverse-DDL discipline, not the tool.

Does Flyway really leave the database half-migrated on failure? On most databases the Community edition does not wrap a migration in a transaction by default, so a multi-statement script that fails partway leaves earlier statements applied. The fix is to wrap your DDL in explicit BEGIN;/COMMIT; (effective on PostgreSQL) and to keep every script idempotent so a retry converges.

How do I stop checksum mismatches on every branch merge? The mismatch usually means a migration file changed after it was applied. Treat applied migrations as immutable, add new files instead of editing old ones, and reserve flyway repair / liquibase clearCheckSums for genuine reconciliation rather than as a routine step in the pipeline.