What's Actually Happening
Liquibase database update fails during execution. Database schema changes are not applied successfully.
The Error You'll See
```bash $ liquibase update
ERROR: Validation Failed: 1 change sets check sum my-changelog.xml::1::author was: 8:abc123 but is now: 8:def456 ```
Lock error:
ERROR: Could not acquire change log lockSQL error:
ERROR: Execution of "CREATE TABLE users..." failed: relation "users" already existsChangelog not found:
ERROR: The file my-changelog.xml was not found in the search pathWhy This Happens
- 1.Checksum mismatch - Changelog modified after execution
- 2.Lock not released - Previous run didn't complete
- 3.SQL syntax error - Invalid SQL in changeset
- 4.Duplicate changeset - ID already used
- 5.Missing dependency - Referenced file not found
- 6.Permission denied - User lacks permissions
Step 1: Check Database Status
```bash # View Liquibase history: liquibase history
# Check databasechangelog table: psql -c "SELECT id, author, filename, dateexecuted, checksum FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 10;"
# Check databasechangeloglock: psql -c "SELECT * FROM databasechangeloglock;"
# View status: liquibase status
# Check unexpected changesets: liquibase unexpectedChangeSets
# List pending changesets: liquibase status --verbose
# Validate changelog: liquibase validate ```
Step 2: Fix Checksum Mismatch
```bash # Checksum mismatch means changeset was modified after execution
# View mismatched changesets: liquibase validate
# Option 1: Revert changeset to original: git checkout my-changelog.xml
# Option 2: Clear checksums: liquibase clearCheckSums
# Option 3: Update checksum in database: psql -c "UPDATE databasechangelog SET checksum = NULL WHERE id = '1' AND author = 'author';"
# Option 4: Add new changeset instead: # Don't modify existing changeset # Create new changeset: <changeSet id="2" author="author"> <!-- New change --> </changeSet>
# Option 5: Use runOnChange for procedures/views: <changeSet id="1" author="author" runOnChange="true"> <createProcedure> CREATE OR REPLACE PROCEDURE my_proc() ... </createProcedure> </changeSet>
# Option 6: Use validCheckSum for accepted change: <changeSet id="1" author="author" validCheckSum="8:def456"> <!-- Modified changeset --> </changeSet> ```
Step 3: Release Database Lock
```bash # Check lock status: psql -c "SELECT locked, lockgranted, lockedby FROM databasechangeloglock;"
# If locked = true and no Liquibase running: liquibase releaseLocks
# Or manually: psql -c "UPDATE databasechangeloglock SET locked = false, lockgranted = null, lockedby = null;"
# Force unlock: liquibase --changeLogFile=my-changelog.xml releaseLocks
# Check for orphaned locks: psql -c "SELECT * FROM databasechangeloglock WHERE locked = true;"
# Clear lock after crash: liquibase --url=jdbc:postgresql://localhost/db releaseLocks
# Prevent lock issues: # Always let Liquibase complete # Use proper timeout settings ```
Step 4: Fix Changeset Errors
```bash # View the failing changeset: liquibase status --verbose
# Common changeset errors:
# 1. Duplicate changeset ID: # Each changeset must have unique id + author + filename <changeSet id="1" author="author"> <!-- First --> <changeSet id="1" author="author"> <!-- ERROR: duplicate -->
# Fix: Use unique ID <changeSet id="2" author="author">
# 2. SQL syntax error: # Error: syntax error at or near "..." # Fix: Review SQL, test manually
# 3. Object already exists: # Error: relation "users" already exists # Fix: Add preconditions: <preConditions onFail="MARK_RAN"> <not> <tableExists tableName="users"/> </not> </preConditions>
# 4. Missing dependency: # Error: relation "orders" does not exist # Fix: Ensure table created in earlier changeset
# 5. Invalid column reference: # Fix: Check column names match actual schema ```
Step 5: Handle Failed Changesets
```bash # If changeset failed during execution:
# Check failed changesets: liquibase status
# View error in databasechangelog: psql -c "SELECT id, author, comments FROM databasechangelog WHERE comments LIKE '%Error%';"
# Option 1: Fix SQL and retry: liquibase update
# Option 2: Mark as ran (if manually fixed): liquibase changeLogSync
# Option 3: Rollback failed changeset: liquibase rollback <tag>
# Option 4: Mark specific changeset as executed: liquibase markNextChangeSetRan
# Manual cleanup: # 1. Fix data in database # 2. Update databasechangelog: psql -c "UPDATE databasechangelog SET comments = 'Fixed manually' WHERE id = '1';"
# Then continue: liquibase update ```
Step 6: Fix Changelog Path Issues
```bash # Liquibase search path:
# Check changelog file exists: ls -la db/changelog/my-changelog.xml
# Use correct path: liquibase --changeLogFile=db/changelog/my-changelog.xml update
# Multiple changelog files: liquibase --changeLogFile=db/changelog-master.xml update
# Master changelog includes: <databaseChangeLog> <include file="db/changelog/changes/001-create-users.xml"/> <include file="db/changelog/changes/002-create-orders.xml"/> <includeAll path="db/changelog/changes/"/> </databaseChangeLog>
# Classpath for resources: liquibase --classpath=/opt/migrations update
# Use absolute path: liquibase --changeLogFile=/opt/migrations/changelog.xml update
# Check working directory: pwd # Run from project root ```
Step 7: Configure Connection Properly
```bash # Liquibase connection configuration:
# Command line: liquibase \ --url=jdbc:postgresql://localhost:5432/mydb \ --username=user \ --password=pass \ --changeLogFile=db/changelog.xml \ update
# Properties file (liquibase.properties): url=jdbc:postgresql://localhost:5432/mydb username=user password=pass changeLogFile=db/changelog.xml driver=org.postgresql.Driver
# Environment variables: export LIQUIBASE_URL=jdbc:postgresql://localhost:5432/mydb export LIQUIBASE_USERNAME=user export LIQUIBASE_PASSWORD=pass
# Spring Boot configuration: spring.liquibase.url=jdbc:postgresql://localhost:5432/mydb spring.liquibase.user=user spring.liquibase.password=pass spring.liquibase.change-log=classpath:db/changelog.xml
# Test connection: liquibase --url=jdbc:postgresql://localhost:5432/mydb --username=user --password=pass status ```
Step 8: Use Preconditions Properly
```xml <!-- Preconditions help avoid common errors: -->
<!-- Check table doesn't exist before creating --> <changeSet id="1" author="author"> <preConditions onFail="MARK_RAN"> <not> <tableExists tableName="users"/> </not> </preConditions> <createTable tableName="users"> <column name="id" type="int"> <constraints primaryKey="true"/> </column> </createTable> </changeSet>
<!-- Check column exists before modifying --> <changeSet id="2" author="author"> <preConditions onFail="HALT"> <columnExists tableName="users" columnName="email"/> </preConditions> <dropColumn tableName="users" columnName="email"/> </changeSet>
<!-- Check SQL condition --> <changeSet id="3" author="author"> <preConditions onFail="MARK_RAN"> <sqlCheck expectedResult="0"> SELECT COUNT(*) FROM users WHERE role = 'admin' </sqlCheck> </preConditions> <insert tableName="users"> <column name="role" value="admin"/> </insert> </changeSet>
<!-- onFail options: HALT, CONTINUE, MARK_RAN, WARN --> ```
Step 9: Handle Contexts and Labels
```xml <!-- Use contexts for environment-specific changes: -->
<changeSet id="1" author="author" context="dev"> <!-- Only runs in dev context --> </changeSet>
<changeSet id="2" author="author" context="prod"> <!-- Only runs in prod context --> </changeSet>
<changeSet id="3" author="author" context="dev, test"> <!-- Runs in dev OR test context --> </changeSet>
<!-- Run with specific context --> liquibase --contexts=dev update liquibase --contexts=prod update
<!-- Use labels for grouping --> <changeSet id="4" author="author" labels="version-1.0"> <createTable tableName="products"/> </changeSet>
<changeSet id="5" author="author" labels="version-1.0, version-1.1"> <!-- Multiple labels --> </changeSet>
<!-- Run specific labels --> liquibase --labels=version-1.0 update
<!-- Context expressions --> liquibase --contexts="dev and !test" update liquibase --contexts="dev or test" update ```
Step 10: Liquibase Verification Script
```bash # Create verification script: cat << 'EOF' > /usr/local/bin/check-liquibase.sh #!/bin/bash
CHANGELOG=${1:-"db/changelog.xml"}
echo "=== Liquibase Status ===" liquibase --changeLogFile=$CHANGELOG status 2>&1 | head -30
echo "" echo "=== Database Lock ===" psql -c "SELECT locked, lockgranted, lockedby FROM databasechangeloglock;" 2>/dev/null || echo "Cannot query lock"
echo "" echo "=== Recent History ===" psql -c "SELECT id, author, filename, dateexecuted FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 10;" 2>/dev/null || echo "Cannot query history"
echo "" echo "=== Validation ===" liquibase --changeLogFile=$CHANGELOG validate 2>&1
echo "" echo "=== Changelog File ===" ls -la $CHANGELOG 2>/dev/null || echo "Changelog not found at $CHANGELOG"
echo "" echo "=== Pending Changesets ===" liquibase --changeLogFile=$CHANGELOG status --verbose 2>&1 | head -30
echo "" echo "=== Unexpected Changesets ===" liquibase --changeLogFile=$CHANGELOG unexpectedChangeSets 2>&1 | head -20
echo "" echo "=== Recommendations ===" echo "1. Run liquibase validate to check checksums" echo "2. Use liquibase releaseLocks if locked" echo "3. Clear checksums with liquibase clearCheckSums" echo "4. Check changeset IDs are unique" echo "5. Verify changelog file path" echo "6. Add preconditions for safety" echo "7. Use contexts for environment control" EOF
chmod +x /usr/local/bin/check-liquibase.sh
# Usage: /usr/local/bin/check-liquibase.sh db/changelog.xml ```
Liquibase Update Checklist
| Check | Expected |
|---|---|
| Lock released | databasechangeloglock.locked = false |
| Checksums valid | No mismatch errors |
| Changelog found | File exists in search path |
| Changeset IDs | Unique |
| Connection | Database reachable |
| Preconditions | Added for safety |
| SQL valid | No syntax errors |
Verify the Fix
```bash # After fixing Liquibase update issues
# 1. Release lock if needed liquibase releaseLocks // Lock released
# 2. Validate changelog liquibase validate // No errors
# 3. Run update liquibase update // Changesets applied
# 4. Check status liquibase status // Up to date
# 5. Verify history psql -c "SELECT * FROM databasechangelog ORDER BY dateexecuted DESC LIMIT 5;" // Shows new changesets
# 6. Test application // Application works with new schema ```
Related Issues
- [Fix Flyway Migration Failed](/articles/fix-flyway-migration-failed)
- [Fix PostgreSQL Permission Denied](/articles/fix-postgresql-permission-denied)
- [Fix MySQL Table Already Exists](/articles/fix-mysql-foreign-key-constraint)