Liquibase SQL格式化与数据库迁移版本控制策略 - 第2节

2.1 ChangeLog:迁移脚本的入口清单

ChangeLog 是 Liquibase 迁移的顶层入口文件,它定义了一个有序的数据库变更清单。Liquibase 通过解析 ChangeLog 来获取所有需要执行的数据库变更,并按照定义的顺序逐一应用。

ChangeLog 支持多种格式,包括 XML、YAML、JSON 以及 SQL 格式化文件。在 SQL 格式化策略中,ChangeLog 本身是一个遵循特定注释约定的 .sql 文件,通过 --liquibase formatted sql 声明开头,后续通过 --changeset 注释划分变更集。

核心要点: ChangeLog 是变更的“目录”,它不直接包含 SQL 逻辑,而是组织和引用变更集。在 SQL 格式化模式下,ChangeLog 与 SQL 文件合二为一。

ChangeLog 的典型结构(SQL 格式化)

--liquibase formatted sql

--changeset author:runOnChange:true
--comment: 创建用户表
CREATE TABLE users (
    id INT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100)
);

--changeset author:context:test
--comment: 插入测试数据
INSERT INTO users (id, username, email) VALUES (1, 'test_user', 'test@example.com');

每个 ChangeLog 文件可以包含多个 --changeset 注释块,每个块代表一个原子变更单元。Liquibase 会按文件中的物理顺序从上到下依次处理这些变更集。

2.2 ChangeSet:原子化的数据库变更单元

ChangeSet 是 Liquibase 中最小的执行单元,代表一个不可分割的数据库变更操作。每个 ChangeSet 必须包含一个唯一的标识符,由 作者(author) 和 变更集 ID(id) 共同组成,Liquibase 使用 author:id 作为全局唯一键来追踪执行状态。

属性 说明 示例值
author 变更集的作者标识,通常使用用户名或邮箱 zhangsan、devops_team
id 变更集的序号或描述性标识,在同一作者下唯一 001、create-user-table
runOnChange 当 ChangeSet 内容发生变化时是否重新执行 true / false
context 指定该变更集仅在特定上下文中执行 test、production
runAlways 是否每次运行都强制执行该变更集 true / false
最佳实践: 每个 ChangeSet 应只包含一个逻辑变更(如创建一张表、添加一个索引、修改一个字段类型)。避免在一个 ChangeSet 中混合多个不相关的 DDL 或 DML 操作,这有助于回滚和问题定位。

在 SQL 格式化文件中,ChangeSet 的声明方式如下:

--changeset zhangsan:create-user-table
--comment: 创建用户主表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

--changeset zhangsan:add-email-index
--comment: 为用户邮箱添加索引
CREATE INDEX idx_users_email ON users(email);

Liquibase 在执行时,会检查 DATABASECHANGELOG 表中是否已存在 zhangsan:create-user-table 和 zhangsan:add-email-index 的记录。如果已存在且内容未变化,则跳过;如果不存在或内容有变化(且 runOnChange=true),则执行。

2.3 DATABASECHANGELOG 表:变更执行的历史账本

DATABASECHANGELOG 表是 Liquibase 自动在目标数据库中创建的一张元数据表,用于记录所有已执行的 ChangeSet 的详细信息。它是 Liquibase 实现增量迁移和幂等性的核心保障。

每次成功执行一个 ChangeSet 后,Liquibase 都会向该表插入一条记录。下次运行时,Liquibase 通过查询该表来判断哪些 ChangeSet 已经执行过,从而避免重复执行。

DATABASECHANGELOG 表结构

字段名 类型 说明
ID VARCHAR ChangeSet 的 id 属性值
AUTHOR VARCHAR ChangeSet 的 author 属性值
FILENAME VARCHAR ChangeSet 所在的 ChangeLog 文件路径
DATEEXECUTED TIMESTAMP 执行完成的时间戳
ORDEREXECUTED INT 全局执行顺序号,单调递增
MD5SUM VARCHAR ChangeSet 内容的 MD5 校验和,用于检测变更
DESCRIPTION VARCHAR 变更类型的描述(如 "Create Table")
COMMENTS VARCHAR ChangeSet 中的 comment 内容
TAG VARCHAR 标记名称,用于回滚定位
LIQUIBASE VARCHAR 执行该变更的 Liquibase 版本
CONTEXTS VARCHAR 执行时生效的上下文
LABELS VARCHAR 执行时生效的标签
DEPLOYMENT_ID VARCHAR 一次部署会话的唯一标识
注意: 不要手动修改 DATABASECHANGELOG 表中的记录。如果手动删除或修改记录,会导致 Liquibase 认为某些 ChangeSet 未执行或校验和不匹配,从而引发意外的重复执行或执行失败。

2.4 DATABASECHANGELOGLOCK 表:并发执行的互斥锁

DATABASECHANGELOGLOCK 表是 Liquibase 用于实现分布式环境下的互斥锁机制的表。当多个 Liquibase 实例(如 CI/CD 流水线、多个开发人员本地执行)同时尝试对同一个数据库执行迁移时,该表确保同一时间只有一个实例能够执行变更,防止并发冲突导致的数据不一致。

锁机制的工作流程

  1. 获取锁: Liquibase 在开始执行任何 ChangeSet 之前,首先尝试向 DATABASECHANGELOGLOCK 表插入一条锁记录。如果插入成功,表示获取锁成功,可以继续执行。
  2. 执行变更: 持有锁的实例正常执行所有待处理的 ChangeSet,并更新 DATABASECHANGELOG 表。
  3. 释放锁: 所有变更执行完毕后,Liquibase 删除锁记录,释放锁资源。
  4. 等待重试: 如果另一个实例尝试获取锁时发现锁已被占用,它会等待一段时间(默认 60 秒)后重试,直到超时或锁被释放。

DATABASECHANGELOGLOCK 表结构

字段名 类型 说明
ID INT 锁记录的唯一标识,通常固定为 1
LOCKED BOOLEAN 是否被锁定(1 表示锁定,0 表示未锁定)
LOCKGRANTED TIMESTAMP 锁被授予的时间
LOCKEDBY VARCHAR 持有锁的主机名或实例标识
故障处理: 如果 Liquibase 执行过程中意外中断(如网络断开、进程被 kill),锁可能未被释放。此时可以通过 liquibase releaseLocks 命令手动释放锁,或直接删除 DATABASECHANGELOGLOCK 表中的记录。

2.5 核心概念关系图

下图展示了 ChangeLog、ChangeSet、DATABASECHANGELOG 表和 DATABASECHANGELOGLOCK 表之间的协作关系:

Liquibase 核心概念协作关系 ChangeLog 迁移入口清单 ChangeSet 原子变更单元 DATABASECHANGELOG 变更执行历史账本 DATABASECHANGELOGLOCK 并发执行互斥锁 包含 执行后写入记录 执行前获取锁 说明: Liquibase 首先解析 ChangeLog 获取 ChangeSet 列表,然后获取 DATABASECHANGELOGLOCK 锁, 执行未记录的 ChangeSet,最后将执行记录写入 DATABASECHANGELOG 表并释放锁。

2.6 四者协作流程

下图以时序方式展示了 Liquibase 在一次迁移执行过程中,四个核心概念之间的交互顺序:

一次迁移执行的时序流程 Liquibase ChangeLog DATABASECHANGELOGLOCK DATABASECHANGELOG 数据库 1. 解析 ChangeLog 2. 尝试获取锁 3. 查询已执行记录 4. 执行未应用的 ChangeSet 5. 返回执行结果 6. 写入执行记录 7. 释放锁 8. 迁移完成,返回结果 整个流程确保:每个 ChangeSet 只执行一次,并发环境下只有一个实例操作,变更历史可追溯。

2.7 核心概念对比总结

概念 角色 存储位置 是否可手动修改 关键作用
ChangeLog 变更清单入口 文件系统(.sql / .xml / .yaml / .json) 是(版本控制管理) 定义变更集的顺序和分组
ChangeSet 原子变更单元 ChangeLog 文件内部 是(版本控制管理) 封装具体的 DDL/DML 操作
DATABASECHANGELOG 执行历史账本 目标数据库 否(Liquibase 自动维护) 记录已执行变更,防止重复执行
DATABASECHANGELOGLOCK 并发互斥锁 目标数据库 否(可通过 releaseLocks 释放) 确保同一时间只有一个实例执行迁移
理解这四个核心概念是掌握 Liquibase 的基础: ChangeLog 是“剧本”,ChangeSet 是“每一幕戏”,DATABASECHANGELOG 是“演出记录”,DATABASECHANGELOGLOCK 是“后台的场务锁”。只有理清它们各自的职责和协作关系,才能在实际项目中设计出可靠、可追溯的数据库迁移方案。