precondition失败时liquibase默认跳过changeset并标记为skipped,不执行也不回滚;需配置onfail=”halt”或合并操作至单个changeset才能实现中止与回滚。

Liquibase 的 precondition 失败时默认不执行变更(changeSet),也不会触发回滚——因为它根本没进入执行阶段。理解这一点是处理失败前提条件的关键。
precondition 失败的本质:跳过而非中断
Liquibase 在执行每个 changeSet 前,会依次校验其 preConditions。一旦任一条件不满足(如表不存在、列已存在、sql 查询返回空/非零等),Liquibase 就直接跳过该 changeSet,并记录为 “SKIPPED”,不会抛出异常(除非配置了 onFail="HALT")。
- 默认行为是继续执行后续 changeSet,整个 changelog 可能部分成功
- 数据库状态保持不变——没有 INSERT/UPDATE/ALTER 发生,自然无需回滚
- 失败的 precondition 不影响 DATABASECHANGELOG 表的写入:该 changeSet 的记录不会插入
如何让 precondition 失败触发中止与显式回滚
若需“失败即终止+回滚之前已执行的 changeSet”,必须主动设计控制流,Liquibase 本身不提供跨 changeSet 的自动回滚能力:
- 在 changelog 开头使用
<pre class="brush:php;toolbar:false;" conditions><onfail>HALT</onfail></pre>,使首次失败立即停止执行 - 将逻辑上强依赖的多个操作合并到单个 changeSet中(用
<sql></sql>或自定义 Change),并在其中手动编写带事务边界的 SQL(如 postgresql 的BEGIN / EXCEPTION块) - 借助外部脚本(Shell/Python)调用 Liquibase,捕获命令退出码(
liquibase update遇HALT会返回非 0 状态),再调用回滚专用 changelog(如rollbackCount 1)
日志记录的关键位置与排查建议
Liquibase 的 precondition 日志分散在不同层级,需结合多处定位问题:
- 控制台输出:启用
--logLevel=debug,可看到类似Precondition failed: table 'users' does not exist的明细 - liquibase.log 文件(若配置了 fileHandler):包含完整 precondition 校验上下文和堆栈(尤其自定义 precondition)
- DATABASECHANGELOG 表:检查
EXECTYPE字段,SKIPPED表示 precondition 失败,EXECUTED表示成功运行 - 对
sqlCheck类型 precondition,建议在 SQL 中添加注释或用select 'DEBUG: condition X' AS reason辅助判断实际返回值
避免 precondition 不可靠的设计习惯
很多 precondition 失败源于环境假设偏差,可通过以下方式增强健壮性:
- 慎用
dbms="h2"等硬编码类型判断,改用<not><dbms type="oracle"></dbms></not>组合 - 检查表/列存在时,优先用
<tableexists></tableexists>和<columnexists></columnexists>,而非手写 SQL(它们适配多数据库元数据查询) - 对版本类判断(如
<runningas></runningas>或自定义 SQL 返回值),在测试环境预跑并比对 DATABASECHANGELOG 记录的实际执行顺序 - 在 CI/CD 流程中,对关键 changelog 加入
liquibase status+liquibase futureRollbackSQL预检步骤