Liquibase 入门实战教程

Liquibase 使用教程

本文记录当前项目从 Flyway 脚本切换到 Liquibase 后的基本概念和实战操作。

1. 基本概念

1.1 changeSet

changeSet 是 Liquibase 的最小执行单元。Liquibase 会根据以下三项判断一个 changeSet 是否已经执行过:

  • FILENAME
  • ID
  • AUTHOR

例如:

text 复制代码
db.changelog-rule-migration.xml::rule-migration-08-rule-task-by-136-20260623::lix

只要这三项在 DATABASECHANGELOG 中存在,Liquibase 就认为这个 changeSet 已经执行过,后续 update 会跳过。

1.2 changelog

changelog 是 Liquibase 的入口配置文件,用来组织多个 changeSet 或 include 其他 changelog 文件。

当前项目常用入口:

文件 用途
db.changelog-master.xml 全量初始化入口
db.changelog-incremental.xml 增量更新入口
db.changelog-rule-migration.xml rule-migration 数据迁移入口
db.changelog-test.xml 本地事务回滚验证入口

1.3 DATABASECHANGELOG

Liquibase 执行成功一个 changeSet 后,会在目标库的 DATABASECHANGELOG 表中记录执行历史。

常用查看语句:

sql 复制代码
SELECT ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM
FROM DATABASECHANGELOG
ORDER BY ORDEREXECUTED;

1.4 DATABASECHANGELOGLOCK

Liquibase 执行时会通过 DATABASECHANGELOGLOCK 加锁,避免多个 Liquibase 同时更新同一个库。

如果异常中断后锁未释放,可以先确认锁状态:

sql 复制代码
SELECT *
FROM DATABASECHANGELOGLOCK;

确认没有 Liquibase 进程在执行时,才可以手工释放:

sql 复制代码
UPDATE DATABASECHANGELOGLOCK
SET LOCKED = 0,
    LOCKEDBY = NULL,
    LOCKGRANTED = NULL
WHERE ID = 1;

2. 当前项目目录约定

主要目录:

text 复制代码
Db.Scripts/
  changelog/
    db.changelog-master.xml
    db.changelog-incremental.xml
    db.changelog-rule-migration.xml
    db.changelog-test.xml
    drop1/
    drop2/
    drop3/
    rule-migration/
    local/
  sql/
    init/
      DDL/
      DML/

约定:

  • 原始 SQL 主要在 Db.Scripts/sql/init 下。
  • Liquibase XML 和迁移入口在 Db.Scripts/changelog 下。
  • drop1~drop3 按版本阶段组织。
  • rule-migration 用于独立执行迁移数据。
  • local 可放本地验证脚本,不应挂到正式入口中。

3. Docker 执行模板

建议本地验证时挂载 changelog 目录,这样修改脚本后不用重新打镜像。

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=<changelog-file> `
  <command>

如果镜像内已经包含驱动,不需要额外挂载 driver。如果没有包含驱动,需要按实际镜像方案处理驱动依赖。

4. 常用命令

4.1 validate

只校验 changelog 和数据库记录是否一致,不执行业务 SQL。

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  validate

4.2 update-sql

预览即将执行的 SQL,不真正改库。

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  update-sql

4.3 update

真正执行未执行过的 changeSet。

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  update

建议执行顺序:

text 复制代码
validate -> update-sql -> update

4.4 常用场景速查

下面命令都沿用第 3 节 Docker 模板,只替换最后的 <command>。实际执行时需要确认 --changelog-file 使用的是当前场景对应入口,例如:

  • 全量初始化:db.changelog-master.xml
  • 增量更新:db.changelog-incremental.xml
  • 规则迁移:db.changelog-rule-migration.xml
场景 推荐命令 说明
看还有哪些 changeSet 没执行 status --verbose 只查看待执行变更,不改库。适合执行 update 前确认范围。
看历史执行记录 history 查看 Liquibase 已执行过哪些 changeSet。适合排查某个版本是否已经跑过。
校验 changelog validate 只检查 Liquibase 文件、引用路径、属性等,不校验 SQL 业务语义。
预览即将执行 SQL update-sql 不改库,只输出 Liquibase 准备执行的 SQL。
正式执行增量 update 执行未记录在 DATABASECHANGELOG 中的 changeSet。
checksum 报错后重新计算 clear-checksums 清空 DATABASECHANGELOG.MD5SUM,下次运行时重新计算;不会重跑已成功 changeSet。
查看 Liquibase 锁 list-locks 查看 DATABASECHANGELOGLOCK 是否被占用。
异常中断后释放锁 release-locks 确认没有 Liquibase 正在执行后再使用,避免并发更新同一个库。
旧库做基线 changelog-sync 不执行 SQL,只把未执行 changeSet 标记为已执行。
预览基线写入记录 changelog-sync-sql 不改库,预览 changelog-sync 会写入哪些记录。
执行到指定 tag update-to-tag --tag=<tag-name> 从当前状态执行到指定 tag 为止,适合验证 drop 级阶段。
预览执行到指定 tag update-to-tag-sql --tag=<tag-name> 不改库,只预览执行到指定 tag 前会执行哪些 SQL。
回滚前预览 SQL rollback-count-sql <count> 不改库,查看回滚最近 N 个 changeSet 的 SQL。
回滚最近 N 个 changeSet rollback-count <count> 真正执行回滚,依赖 changeSet 是否有 rollback 或 Liquibase 是否支持自动回滚。
status:查看待执行 changeSet

适用场景:

  • 想确认本次 update 会不会执行新脚本。
  • 多人提交 changelog 后,想看目标库还差哪些 changeSet。
  • 使用 context/label 时,确认过滤条件下有哪些 changeSet 会被执行。

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\ Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-incremental.xml `
  status --verbose
history:查看已执行历史

适用场景:

  • 确认某个版本脚本是否已经执行成功。
  • 排查 update 为什么跳过某个 changeSet。
  • DATABASECHANGELOG 表记录交叉核对。

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-incremental.xml `
  history
update-to-tag:执行到指定 tag

适用场景:

  • 只想把测试库执行到某个 drop 阶段,例如 drop1
  • 验证某一阶段的初始化和回滚,不想继续执行后续 drop。

预览命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  update-to-tag-sql --tag=drop1

真正执行:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  update-to-tag --tag=drop1

执行后可以通过 DATABASECHANGELOG.TAG 确认是否到达目标 tag:

sql 复制代码
SELECT ORDEREXECUTED, ID, AUTHOR, FILENAME, TAG, DATEEXECUTED
FROM DATABASECHANGELOG
ORDER BY ORDEREXECUTED;
list-locks / release-locks:处理锁

适用场景:

  • Liquibase 执行异常中断。
  • 再次执行时报 DATABASECHANGELOGLOCK 已锁定。
  • 需要确认锁是谁持有、何时加锁。

先查看锁:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-incremental.xml `
  list-locks

确认没有 Liquibase 进程还在执行后,再释放锁:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-incremental.xml `
  release-locks

注意:release-locks 只解决 Liquibase 锁记录,不会回滚已经执行过的 SQL。使用前要确认没有其他部署任务正在跑。

clear-checksums:处理 checksum 不一致

适用场景:

  • 已执行的 changeSet 后续被格式化或做了无业务影响调整。
  • Liquibase 报 Validation Failed,提示旧 checksum 和新 checksum 不一致。
  • 确认不需要重跑该 changeSet,只希望重新计算 checksum。

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  clear-checksums

注意:clear-checksums 不会让已执行成功的 changeSet 重新执行。如果要重跑某个 changeSet,需要先确认业务数据影响,再处理 DATABASECHANGELOG 对应记录。

changelog-sync / changelog-sync-sql:已有库做基线

适用场景:

  • 旧库已经通过 Flyway 或人工 SQL 建好了对象。
  • 不希望 Liquibase 重复执行历史脚本。
  • 希望 Liquibase 从某个基线后开始接管后续增量。

先预览将写入的基线记录:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  changelog-sync-sql

确认无误后执行基线同步:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  changelog-sync

注意:changelog-sync 只写 Liquibase 执行记录,不会检查业务表是否真的存在,也不会补表或补数据。

5. 空库和已有库的区别

5.1 空库

空库没有业务表,也没有 Liquibase 记录表。执行 update 时,Liquibase 会:

  1. 创建 DATABASECHANGELOG
  2. 创建 DATABASECHANGELOGLOCK
  3. 按 changelog 顺序执行所有未执行 changeSet。
  4. 成功一个 changeSet,记录一条执行历史。

空库适合验证全量初始化。

5.2 已有库

已有库可能已经通过 Flyway 或人工 SQL 初始化过,但 Liquibase 不会自动读取 Flyway 历史。

如果已有库没有 DATABASECHANGELOG,直接执行 db.changelog-master.xml update,Liquibase 会认为全部 changeSet 都未执行,可能导致:

  • 重复建表。
  • 重复加字段。
  • 重复插入初始化数据。
  • 主键或唯一索引冲突。

已有库切换到 Liquibase 时,需要先做基线或使用增量入口。

6. Flyway 旧库切换 Liquibase

6.1 使用 changelog-sync 做基线

如果旧库中脚本已经通过 Flyway 执行过,并且当前数据库结构和数据已经与 changelog 对齐,可以使用 changelog-sync 标记基线。

作用:

  • 不执行 changeSet 中的 SQL。
  • 只往 DATABASECHANGELOG 写入执行记录。
  • 后续 update 会跳过这些已同步的 changeSet。

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  changelog-sync

注意:changelog-sync 只同步 Liquibase 记录,不校验业务表是否真的存在,也不补数据。

6.2 使用 db.changelog-incremental.xml 做增量

当前项目提供 db.changelog-incremental.xml 作为增量入口,只包含旧 Flyway 已执行脚本之后的增量部分。

适用场景:

  • 现场库已经有旧版本业务表和初始化数据。
  • 不希望 Liquibase 重跑旧 Flyway 脚本。
  • 只希望执行后续新增脚本。

命令示例:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-incremental.xml `
  update

7. rule-migration 数据迁移

db.changelog-rule-migration.xml 是独立入口,用来执行 rule-migration 下的数据迁移 SQL。

执行前建议:

  1. 先确认目标库已经完成基础表结构初始化。
  2. 先执行 validate
  3. 再执行 update-sql
  4. 最后执行 update

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-rule-migration.xml `
  update

如果 01~07 已成功,08 失败,则修复 08 后重新执行同一个 update,Liquibase 会跳过 01~07,从未成功记录的 08 继续执行。

8. 重复数据和幂等处理

迁移数据时常见错误:

text 复制代码
Duplicate entry '<id>' for key 'PRIMARY'

表示目标库中已经存在相同主键数据。

常见原因:

  • 旧 Flyway 初始化数据已经插入过。
  • rule-migration 导出的数据包含基础系统数据。
  • 脚本重复执行。

处理策略:

策略 说明
保持 INSERT 适合只执行一次、确保目标库无重复数据的脚本
DELETE + INSERT 适合希望固定覆盖某些基础数据的场景
REPLACE INTO 可处理重复主键,但本质是删除后插入,需注意外键、触发器、级联影响
INSERT IGNORE 重复时跳过,适合已有数据优先的场景
新增补偿 changeSet 已执行过的 changeSet 不建议直接改,推荐新增 changeSet 修正数据

已执行成功的 changeSet 不建议再修改内容,否则会出现 checksum 错误。

9. checksum 错误

报错示例:

text 复制代码
Validation Failed:
changesets check sum
was: <old-md5>
but is now: <new-md5>

原因:

  • changeSet 已经执行成功。
  • 后来修改了对应 XML 或 sqlFile 内容。
  • Liquibase 重新计算 checksum 后发现与 DATABASECHANGELOG.MD5SUM 不一致。

推荐处理:

  1. 已执行成功的 changeSet 尽量恢复原内容。
  2. 如果需要补数据,新增 changeSet。
  3. 如果只是格式、幂等化调整,并确认数据库状态无需重跑,可以使用 clear-checksums

命令:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  clear-checksums

注意:clear-checksums 不会重新执行已经成功的 changeSet,只是清空已记录的 checksum,让下次运行重新计算。

10. 从头重跑某个 changelog

Liquibase 是否执行 changeSet,取决于 DATABASECHANGELOG 记录。

如果希望同一个 changelog 从头重跑,可以删除对应记录:

sql 复制代码
SELECT ID, AUTHOR, FILENAME, ORDEREXECUTED, DATEEXECUTED
FROM DATABASECHANGELOG
WHERE FILENAME = 'db.changelog-rule-migration.xml'
ORDER BY ORDEREXECUTED;

确认无误后:

sql 复制代码
DELETE FROM DATABASECHANGELOG
WHERE FILENAME = 'db.changelog-rule-migration.xml';

然后重新执行 update

注意:

  • 只删除 DATABASECHANGELOG 不会删除业务数据。
  • 如果 SQL 不是幂等的,重跑仍然可能主键冲突。
  • 测试库可以这样做,生产环境应谨慎。

也可以通过修改 changelog 文件名让 Liquibase 认为这是另一批 changeSet,但不推荐作为常规方案,因为它会制造新的执行历史,后续维护不清晰。

11. 事务和回滚

11.1 changeSet 内事务回滚

Liquibase 的 changeSet 默认:

xml 复制代码
runInTransaction="true"

对于 MariaDB/MySQL 的 InnoDB 表,如果一个 changeSet 中有多条 DML,第 9 条失败,则前面已经成功执行的 DML 会跟随该 changeSet 事务回滚。

已验证场景:

  • pf_message 表中同一个 changeSet 放 10 条 INSERT
  • 第 9 条故意使用第 1 条相同主键。
  • 执行失败后查询测试 ID,前 8 条没有保留。

说明当前 DML 场景中,同一个 changeSet 内失败会回滚前面已执行的 DML。

边界:

  • 只回滚失败的当前 changeSet。
  • 不会回滚之前已经成功提交的其他 changeSet。
  • DDL 在 MariaDB/MySQL 中可能隐式提交,不能完全依赖事务回滚。
  • 如果设置 runInTransaction="false",则不保证同一 changeSet 内整体回滚。

11.2 Liquibase rollback

Liquibase 的 rollback 是部署回滚,不等同于数据库事务回滚。

常用预览:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  rollback-count-sql 1

真正回滚最近 1 个 changeSet:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  rollback-count 1

如果使用 tag:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  rollback --tag=<tag-name>

注意:回滚到某个 tag,表示回滚该 tag 之后的 changeSet。比如要回滚 drop5,通常是回滚到 drop4,而不是回滚到 drop5。

11.3 按 drop 阶段验证回滚

如果要验证某个 drop 阶段,例如 drop1,建议用空测试库按下面顺序执行:

text 复制代码
update-to-tag-sql -> update-to-tag -> 查询 DATABASECHANGELOG 数量 -> rollback-count-sql -> rollback-count -> 验证表/视图是否删除

执行到 drop1

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  update-to-tag --tag=drop1

查询当前实际执行了多少个 changeSet:

sql 复制代码
SELECT COUNT(*) AS executed_count
FROM DATABASECHANGELOG;

查看执行明细:

sql 复制代码
SELECT ORDEREXECUTED, ID, AUTHOR, FILENAME, TAG, DATEEXECUTED
FROM DATABASECHANGELOG
ORDER BY ORDEREXECUTED;

如果只统计某个 drop 阶段,可以按文件名和 tag 过滤:

sql 复制代码
SELECT COUNT(*) AS drop1_executed_count
FROM DATABASECHANGELOG
WHERE FILENAME LIKE '%drop1/%'
   OR ID = 'master-tag-drop1';

回滚前先预览 SQL。这里的 <count> 要使用目标库 DATABASECHANGELOG 中实际查到的数量:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  rollback-count-sql --count=<count>

rollback-count-sql 只打印 SQL,不会真正修改数据库。日志中看到 DROP TABLEDELETE FROM DATABASECHANGELOG 也只是预览结果。

确认预览 SQL 没问题后,再真正执行回滚:

powershell 复制代码
docker run --rm `
  -v "${PWD}\Db.Scripts\changelog:/liquibase/changelog" `
  -w /liquibase/changelog `
  -e LIQUIBASE_COMMAND_URL="jdbc:mariadb://<host>:<port>/<database>?useUnicode=true&characterEncoding=UTF8&serverTimezone=Asia/Shanghai" `
  -e LIQUIBASE_COMMAND_USERNAME="<user>" `
  -e LIQUIBASE_COMMAND_PASSWORD="<password>" `
  -e LIQUIBASE_COMMAND_DRIVER="org.mariadb.jdbc.Driver" `
  liquibasedbscript:<tag> `
  --changelog-file=db.changelog-master.xml `
  rollback-count --count=<count>

回滚后验证:

sql 复制代码
SELECT COUNT(*) AS executed_count
FROM DATABASECHANGELOG;
sql 复制代码
SELECT TABLE_NAME, TABLE_TYPE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('act_evt_log', 't_db_info', 'pf_message', 'T_PF_USER_SESSION');

注意:如果当前已经执行到 drop1 的 tag,rollback --tag=drop1 不会回滚 drop1 本身,因为它的语义是回滚到该 tag 所在位置。要回滚 drop1 本身,需要使用 rollback-count 回滚实际已经执行的 drop1 changeSet 数量。

12. 常见问题

12.1 file not found

报错:

text 复制代码
The file drop2/xxx.xml was not found in the configured search path

原因通常是容器工作目录或 changelog 挂载路径不对。

处理:

  • 设置 -w /liquibase/changelog
  • 挂载 Db.Scripts/changelog/liquibase/changelog
  • 使用 --changelog-file=db.changelog-master.xml

12.2 XML 属性不支持

报错:

text 复制代码
Attribute 'ifExists' is not allowed to appear in element 'dropTable'

说明当前 Liquibase XML schema 不支持该属性。需要改成支持的写法,例如配合 preConditions 控制是否执行。

12.3 字段不存在

报错类似:

text 复制代码
Unknown column '<column>' in 'field list'

处理思路:

  1. 查 DML 是否引用了该字段。
  2. 查 DDL/changelog 是否创建或新增过该字段。
  3. 如果 DML 来自高版本库导出,而当前目标库没有该字段,需要补 DDL 或调整 DML。

12.4 重复主键

报错:

text 复制代码
Duplicate entry '<id>' for key 'PRIMARY'

处理思路:

  1. 查脚本内是否重复插入同一个主键。
  2. 查初始化 DML 是否已经插入过该数据。
  3. 查目标库是否已有该数据。
  4. 根据业务语义选择 DELETE + INSERTREPLACE INTOINSERT IGNORE 或剔除重复数据。

13. 新增脚本流程

13.1 SQL 转 Liquibase XML

如果是结构变更,优先使用 Liquibase XML 标签表达,例如:

  • createTable
  • addColumn
  • dropColumn
  • createIndex
  • addUniqueConstraint

如果是大量 DML 或不适合结构化表达的 SQL,可以放入 <sql><sqlFile>

13.2 添加到入口

全量初始化入口:

text 复制代码
Db.Scripts/changelog/db.changelog-master.xml

现场增量入口:

text 复制代码
Db.Scripts/changelog/db.changelog-incremental.xml

规则迁移入口:

text 复制代码
Db.Scripts/changelog/db.changelog-rule-migration.xml

新增脚本时需要明确:

  • 是否属于全量初始化。
  • 是否属于现场增量。
  • 是否属于独立数据迁移。
  • 是否需要 rollback。
  • 是否允许重复执行。

13.3 DDL 回滚策略

DDL 可以补 rollback 的,尽量补充。比如:

  • createTable 对应 dropTable
  • addColumn 对应 dropColumn
  • createIndex 对应 dropIndex

DML 一般不机械补 rollback,除非业务明确要求。

14. 推荐操作顺序

空库全量验证:

text 复制代码
validate -> update-sql -> update -> 检查 DATABASECHANGELOG

已有库增量更新:

text 复制代码
确认 Flyway 历史 -> 选择 changelog-sync 或 db.changelog-incremental.xml -> validate -> update-sql -> update

rule-migration:

text 复制代码
确认基础表结构已存在 -> validate -> update-sql -> update -> 遇到失败修未成功的 changeSet -> 重新 update

异常处理:

text 复制代码
先看失败的 changeSet ID -> 查 DATABASECHANGELOG 是否已记录 -> 判断是脚本问题、数据重复、字段缺失还是 checksum 问题 -> 再选择修复方式

15. 官方文档链接

常用入口:

相关推荐
Zk.Sun1 小时前
Linux设置触屏双击距离容差
linux·运维·数据库
山峰哥2 小时前
数据库工程:Explain执行计划对比实战指南‌
数据库·sql·oracle·深度优先·宽度优先
crisps_ww2 小时前
自媒体运营分析-BI可视化仪表盘制作
数据库
Geek-Chow3 小时前
微服务认证与授权:01 — 概念
java·前端·数据库
烤代码的吐司君3 小时前
Redis IO 多路复用原理与引入原因深度解析
数据库·redis·php
韶博雅4 小时前
oracle密码策略检查
数据库·oracle
Database_Cool_4 小时前
单库瓶颈解决方案首选:阿里云 PolarDB-X 平滑替代分库分表
数据库·阿里云
2503_931712484 小时前
10m/s超高速电梯:西奥XO-NEWIII如何树立行业速度标杆
java·大数据·数据库
无小道5 小时前
Redis——redis和mysql的数据同步
数据库·redis·mysql