Change set immutability
An applied change set is immutable. You never edit it — you append a new one. This is the single most important Liquibase rule and the most common cause of a broken development or CI start-up.
The check to run before editing any changelog
When a change to an existing change set — or a file it references, such as a CSV or SQL script — is proposed, stop and determine whether it has already been applied anywhere:
-
Has this change set ever run (locally, in CI, in staging, in production)? If you cannot prove it has never run, assume it has. A change set merged to a shared branch has almost certainly run in CI.
SELECT id, author, filename, md5sum, dateexecuted FROM DATABASECHANGELOG WHERE id = '<id>'; -
If it has run, do not edit it. Take one of the alternatives in What to do instead. Editing it changes the stored checksum and fails validation on the next start-up of every environment that already ran it.
-
If it provably has never run anywhere — a brand-new file in the current uncommitted change, not yet started in any environment — editing is fine; it has no stored checksum yet.
|
If unsure, treat it as applied. The cost of a needless new change set is one extra file; the cost of editing an applied one is a broken start-up for everyone. |
Why editing breaks start-up — and blocks unrelated schema changes
Liquibase update runs in two phases:
-
Validate — a
ValidatingVisitorwalks every change set in the whole changelog, recomputes each checksum, and compares it to theMD5SUMstored inDATABASECHANGELOG. All mismatches are collected, and if there is even one, Liquibase throwsValidationFailedExceptionbefore executing a single change set. -
Execute — reached only if validation passed cleanly.
So a stale edit anywhere in the changelog aborts the entire run, including brand-new, unrelated schema changes that sit earlier in the file and would otherwise apply. This is why "I edited some old test data and now my new column won’t deploy" happens: file order is irrelevant — validation is global and upfront.
What to do instead
Append a new change set (the delta pattern)
The default. Add a new change set — usually a new file — that expresses only the change:
-
A wrong or changed column value on existing rows → a new change set with
<update>or<sql>. -
New rows → a new
load*/INSERTchange set. -
A column added or altered → a new
alter_entity_*file (see Changelog and change set conventions).
The original applied change set stays byte-for-byte untouched; its checksum stays valid.
Make a fixture loader re-runnable
For dev-only fixture data that changes often, the friction of append-only deltas is what tempts people to edit in place.
The fix is to make those change sets idempotent and runOnChange="true", so an edit re-runs instead of failing validation:
-
loadData→loadUpdateData primaryKey="…"(upsert by key), plusrunOnChange="true". -
A pure
<update>change set is already idempotent → safe to markrunOnChange="true". -
A
<sql>containingINSERTis not idempotent → do not addrunOnChange(a re-run duplicates rows or hits a primary-key collision). Express its amendments as a new delta.
This is a deliberate design choice for fixtures only, covered in detail in Test data management.
Never put runOnChange on real schema DDL — schema history must stay immutable.
Escape hatches (recovery only, not routine)
| Mechanism | When |
|---|---|
|
Wipes stored checksums so the next run re-baselines them. Use to recover a wedged development database; never against shared or production data you cannot re-create. |
|
Nuke and rebuild. The cleanest reset for disposable development data. |
|
Tells Liquibase to accept a changed checksum on one change set. A last resort that hides real drift; prefer a new change set. |
Decision table
| Situation | Action |
|---|---|
New file, never applied anywhere |
Edit freely |
Applied schema change set needs a fix |
New |
Applied seed/fixture row value wrong |
New |
Need to add fixture rows |
New |
Dev database wedged on a checksum error |
|
Production / staging failing on a checksum |
Do not |