变更集依赖基础:dependsOn属性的作用、语法格式、前置变更集与后置变更集的关系
在Liquibase的变更集编排体系中,dependsOn是控制执行顺序的核心属性。它显式声明一个变更集必须在另一个(或多个)变更集成功应用之后才能执行,从而在数据库对象之间建立严格的依赖契约。理解dependsOn的语义与用法,是构建可靠、可重复的数据库迁移流水线的第一步。
2.1 dependsOn属性的作用
dependsOn解决的核心问题是:当变更集之间的依赖关系无法通过文件顺序或runOrder隐式表达时,如何强制指定执行先后顺序。典型场景包括:
- 对象引用依赖:先建表A,再建引用表A的外键约束;先创建存储过程,再创建调用该过程的触发器。
- 数据依赖:先插入基础字典数据,再插入业务数据;先执行数据清洗脚本,再执行数据聚合脚本。
- 环境准备依赖:先创建表空间/文件组,再在该表空间上建表;先创建用户,再授予权限。
- 回滚顺序保障:通过依赖关系隐式定义回滚时的逆序执行路径。
关键认知:
dependsOn 不是“建议性排序”,而是“强制性前置条件”。如果前置变更集失败或未执行,后置变更集将直接跳过(SKIP),不会尝试执行。
2.2 语法格式与使用位置
dependsOn属性定义在变更集的XML根元素<changeSet>上,通过dependsOn子元素指定一个或多个前置变更集。每个前置变更集通过changeSetId和changeSetAuthor两个属性唯一标识。
2.2.1 基本语法
<changeSet id="2" author="zhang">
<dependsOn>
<changeSet id="1" author="zhang"/>
</dependsOn>
<createTable tableName="orders">
<column name="id" type="int">
<constraints primaryKey="true"/>
</column>
<column name="customer_id" type="int"/>
</createTable>
</changeSet>
2.2.2 多依赖语法
一个变更集可以依赖多个前置变更集,此时所有前置变更集都必须成功应用,后置变更集才能执行:
<changeSet id="3" author="li">
<dependsOn>
<changeSet id="1" author="zhang"/>
<changeSet id="2" author="zhang"/>
</dependsOn>
<addForeignKeyConstraint
baseTableName="orders"
baseColumnNames="customer_id"
referencedTableName="customers"
referencedColumnNames="id"
constraintName="fk_orders_customers"/>
</changeSet>
2.2.3 跨文件依赖
dependsOn支持跨变更日志文件的依赖,只需在<changeSet>上额外指定filePath属性(可选),Liquibase会自动解析文件路径:
<changeSet id="4" author="wang" filePath="changelogs/v2/seed-data.xml">
<dependsOn>
<changeSet id="1" author="zhang" filePath="changelogs/v1/schema.xml"/>
</dependsOn>
<insert tableName="customers">
<column name="id" valueNumeric="1"/>
<column name="name" value="Acme Corp"/>
</insert>
</changeSet>
注意: 跨文件依赖时,
filePath 必须与 Liquibase 解析后的实际路径一致。建议使用相对于根变更日志的路径,避免因工作目录不同导致依赖解析失败。
2.3 前置变更集与后置变更集的关系
理解前置(Predecessor)与后置(Successor)变更集的关系,是正确使用dependsOn的前提。下表从多个维度对比两者的差异:
| 对比维度 | 前置变更集(Predecessor) | 后置变更集(Successor) |
|---|---|---|
| 定义位置 | 被 <dependsOn> 引用的变更集 |
包含 <dependsOn> 声明的变更集 |
| 执行顺序 | 必须先执行成功 | 在前置变更集执行成功后执行 |
| 失败影响 | 失败时,后置变更集被跳过 | 前置失败时,自身不执行 |
| 回滚顺序 | 在后置变更集回滚之后回滚 | 在前置变更集回滚之前回滚 |
| 依赖方向 | 被依赖方(被动) | 依赖方(主动) |
| 数量关系 | 一个前置可被多个后置依赖 | 一个后置可依赖多个前置 |
2.4 依赖关系执行流程
下图展示了Liquibase在运行时如何解析dependsOn依赖并决定变更集的执行状态:
2.5 依赖关系的最佳实践
- 显式优于隐式:即使变更集在文件中的顺序已经满足依赖,也建议显式声明
dependsOn,提高可读性和可维护性。 - 避免循环依赖:Liquibase不会检测循环依赖,但循环依赖会导致变更集永远无法执行。设计时应确保依赖图是有向无环图(DAG)。
- 最小依赖原则:只声明直接依赖,不要声明传递依赖。例如A→B→C,C只需依赖B,无需依赖A。
- 使用
filePath明确跨文件依赖:跨变更日志文件时,始终指定filePath属性,避免路径歧义。 - 依赖与回滚配合:确保依赖链上的每个变更集都有对应的回滚脚本,否则回滚时可能因缺少回滚操作导致数据库状态不一致。
冒烟验证提示: 在测试环境中,可以通过
liquibase updateSQL --changeset="id,author" 查看依赖解析后的执行顺序。如果发现后置变更集被跳过,优先检查前置变更集的执行状态和 dependsOn 的引用是否正确。