3. 依赖关系类型:硬依赖(必须执行)、软依赖(条件执行)、基于上下文的依赖

在 Liquibase 变更集编排中,依赖关系决定了变更集之间的执行顺序与触发条件。理解三种核心依赖类型——硬依赖、软依赖与基于上下文的依赖——是构建可靠数据库迁移脚本的基础。错误的依赖配置会导致迁移失败、数据不一致或回滚混乱。

3.1 三种依赖类型对比

依赖类型 核心语义 执行条件 失败影响 典型场景
硬依赖 前置变更集必须已执行且成功 前置变更集的 id + author + filepath 必须出现在 DATABASECHANGELOG 表中 前置未执行 → 当前变更集跳过,迁移中断 建表后加外键、创建存储过程前先建函数
软依赖 前置变更集若存在则必须已执行,若不存在则忽略 前置变更集在 DATABASECHANGELOG 中存在则校验顺序,不存在则直接执行当前变更集 前置不存在 → 正常执行;前置存在但未执行 → 中断 可选索引、历史数据修补、环境特定补丁
基于上下文的依赖 仅在特定上下文(环境)中激活的依赖关系 当前运行上下文匹配 context 属性时,依赖规则生效;不匹配则跳过 上下文不匹配 → 跳过整个变更集;上下文匹配但前置未执行 → 中断 开发环境初始化数据、生产环境只执行 DDL

3.2 硬依赖:必须执行

硬依赖是最严格的依赖形式。使用 dependsOn 属性声明,Liquibase 会强制要求前置变更集在 DATABASECHANGELOG 表中已标记为 EXECUTED 状态。若前置变更集不存在或未执行,当前变更集不会执行,迁移过程抛出异常并回滚当前事务。

硬依赖配置示例:
<changeSet id="create_users_table" author="dba">
  <createTable tableName="users">
    <column name="id" type="int" autoIncrement="true">
      <constraints primaryKey="true"/>
    </column>
    <column name="email" type="varchar(255)">
      <constraints unique="true"/>
    </column>
  </createTable>
</changeSet>

<changeSet id="add_profile_table" author="dba" dependsOn="create_users_table">
  <createTable tableName="user_profiles">
    <column name="user_id" type="int">
      <constraints foreignKeyName="fk_profile_user" 
                   referencedTableName="users" 
                   referencedColumnNames="id"/>
    </column>
  </createTable>
</changeSet>

冒烟验证要点:

  • 确认 dependsOn 引用的变更集 id 在全局唯一,避免歧义
  • 验证前置变更集在 DATABASECHANGELOG 中状态为 EXECUTED,而非 RERAN 或 FAILED
  • 测试回滚场景:若前置变更集被回滚,依赖它的变更集是否被正确处理

3.3 软依赖:条件执行

软依赖使用 dependsOn 配合 runOnChange="true" 或通过 preConditions 中的 changeSetExecuted 条件实现。核心区别在于:前置变更集不存在时,当前变更集仍然可以执行,不会中断迁移。

最佳实践:软依赖适用于可选组件或历史修补场景。例如,某个索引在旧版本中不存在,新版本中创建该索引时,不应因为旧版本缺少前置变更集而阻塞整个迁移。
<changeSet id="add_index_on_email" author="dev">
  <preConditions onFail="WARN">
    <changeSetExecuted changeSetFile="path/to/changelog.xml" 
                       author="dba" 
                       id="create_users_table"/>
  </preConditions>
  <createIndex tableName="users" indexName="idx_email">
    <column name="email"/>
  </createIndex>
</changeSet>

冒烟验证要点:

  • 测试前置变更集不存在时,当前变更集是否正常执行且不报错
  • 测试前置变更集存在但未执行时,当前变更集是否按预期失败或警告
  • 验证 onFail 行为(WARN / MARK_RAN / HALT)是否符合预期

3.4 基于上下文的依赖

基于上下文的依赖通过 context 属性实现环境隔离。变更集只在匹配的上下文中执行,依赖关系也只在对应上下文中生效。这是实现多环境(开发/测试/生产)差异化迁移的核心机制。

<changeSet id="seed_dev_data" author="dev" context="dev">
  <insert tableName="users">
    <column name="email" value="admin@dev.local"/>
  </insert>
</changeSet>

<changeSet id="add_dev_index" author="dev" 
           context="dev" 
           dependsOn="seed_dev_data">
  <createIndex tableName="users" indexName="idx_dev_email">
    <column name="email"/>
  </createIndex>
</changeSet>
常见陷阱:上下文不匹配时,变更集完全跳过,DATABASECHANGELOG 表中不会记录该变更集。如果后续切换上下文执行,Liquibase 会认为这是一个全新的变更集并尝试执行。务必确保上下文切换不会导致重复执行或数据冲突。

3.5 依赖关系执行流程

下图展示了 Liquibase 在解析变更集依赖关系时的完整决策路径:

依赖关系解析与执行决策流程 解析变更集 检查 context 是否匹配 不匹配 跳过变更集 匹配 检查 dependsOn 判断依赖类型 硬依赖 软依赖 上下文依赖 前置必须已执行 → 否则中断 前置存在则校验,否则忽略 上下文匹配才校验依赖 执行变更集

3.6 依赖关系故障排查清单

故障现象 可能原因 排查步骤
变更集被跳过,日志显示依赖未满足 硬依赖的前置变更集未执行或已被回滚 查询 DATABASECHANGELOG 表确认前置状态;检查 dependsOn 引用是否正确
软依赖变更集在无前置时仍失败 preConditions 配置了 onFail="HALT" 而非 WARN 检查 preConditions 的 onFail 属性;确认 changeSetExecuted 条件逻辑
上下文变更集在预期环境中未执行 运行时未传递正确的 context 参数 检查命令行 --contexts 参数;验证变更集 context 属性拼写
依赖链过长导致迁移超时 变更集之间形成循环依赖或深度嵌套 使用 liquibase status --verbose 查看依赖树;重构为扁平化结构

3.7 依赖关系设计原则

核心原则:依赖关系应尽可能少且明确。过度使用硬依赖会导致迁移脚本脆弱,环境切换困难。优先使用软依赖和上下文依赖来提升迁移脚本的鲁棒性。
  • 最小依赖原则:只在确实需要前置变更集结果时才声明依赖,避免不必要的耦合
  • 显式优于隐式:使用 dependsOn 或 preConditions 明确声明依赖,不要依赖变更集在文件中的物理顺序
  • 上下文隔离:环境特定的变更集(如测试数据)使用 context 属性隔离,不要通过依赖关系控制
  • 回滚兼容:设计依赖关系时考虑回滚场景,确保回滚操作不会破坏依赖链的完整性