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 在解析变更集依赖关系时的完整决策路径:
3.6 依赖关系故障排查清单
| 故障现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 变更集被跳过,日志显示依赖未满足 | 硬依赖的前置变更集未执行或已被回滚 | 查询 DATABASECHANGELOG 表确认前置状态;检查 dependsOn 引用是否正确 |
| 软依赖变更集在无前置时仍失败 | preConditions 配置了 onFail="HALT" 而非 WARN |
检查 preConditions 的 onFail 属性;确认 changeSetExecuted 条件逻辑 |
| 上下文变更集在预期环境中未执行 | 运行时未传递正确的 context 参数 |
检查命令行 --contexts 参数;验证变更集 context 属性拼写 |
| 依赖链过长导致迁移超时 | 变更集之间形成循环依赖或深度嵌套 | 使用 liquibase status --verbose 查看依赖树;重构为扁平化结构 |
3.7 依赖关系设计原则
核心原则:依赖关系应尽可能少且明确。过度使用硬依赖会导致迁移脚本脆弱,环境切换困难。优先使用软依赖和上下文依赖来提升迁移脚本的鲁棒性。
- 最小依赖原则:只在确实需要前置变更集结果时才声明依赖,避免不必要的耦合
- 显式优于隐式:使用
dependsOn或preConditions明确声明依赖,不要依赖变更集在文件中的物理顺序 - 上下文隔离:环境特定的变更集(如测试数据)使用
context属性隔离,不要通过依赖关系控制 - 回滚兼容:设计依赖关系时考虑回滚场景,确保回滚操作不会破坏依赖链的完整性