在项目开发中,我们对代码通常已经有比较成熟的管理方式:提交到 Git,功能分支走 Review,发布依赖流水线,线上问题可以通过提交记录追踪。
但是数据库变更呢?
很多团队一开始可能还是比较原始的方式:开发写一段 SQL,发到群里;测试环境手动执行一次;预发环境再执行一次;上线时 DBA 或开发再连到生产库执行一次。
这种方式短期看很方便,但问题也很明显:
- 哪些 SQL 执行过,依赖人工记忆;
- 测试、预发、生产环境容易不一致;
- SQL 文件散落在群聊、文档、本地目录里;
- 线上数据库需要人工直连操作;
- 出问题后,很难追踪这次变更是谁加的、什么时候执行的、在哪个环境执行过。
Liquibase 要解决的,正是这些问题。它的核心价值不是"帮我们执行 SQL",而是让数据库变更像代码一样进入工程化管理。在云栈社区的技术讨论中,数据库版本管理一直是团队协作中的高频痛点。
一、Liquibase 到底是什么?
Liquibase 是一个数据库变更管理工具。它可以把数据库表结构变更、索引变更、初始化数据、数据修复脚本等内容,统一放到版本管理中,并通过固定流程执行到目标数据库。
实际使用中,我们并不是一定要把所有 SQL 都改写成复杂的 YAML 或 XML 结构。很多项目里,最自然的方式就是:继续写 SQL,但把 SQL 放进 Liquibase 的 changeSet 里。
例如下面这种形式:
<changeSet id="2026-003-create-blog-image" author="test">
<sql>
CREATE TABLE `blog_image` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` VARCHAR(64) NOT NULL COMMENT '上传者账号',
`original_name` VARCHAR(255) DEFAULT NULL COMMENT '原始文件名',
`file_name` VARCHAR(255) NOT NULL COMMENT '存储文件名',
`file_path` VARCHAR(500) NOT NULL COMMENT '文件存储路径',
`image_url` VARCHAR(500) NOT NULL COMMENT '图片访问URL',
`file_size` BIGINT DEFAULT NULL COMMENT '文件大小(字节)',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
PRIMARY KEY (`id`),
KEY `idx_account` (`account`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图片上传记录表';
</sql>
</changeSet>
这其实很适合团队落地。开发仍然可以用自己熟悉的 SQL 表达数据库变更,但这段 SQL 不再是一个孤立脚本,而是变成了一个有编号、有作者、有执行记录的受控变更。
二、为什么要用 Liquibase?
我觉得 Liquibase 最重要的几个关键词是:可控、可追溯、可重复、可集成。
1. 可控:不再随手直连线上库改表
数据库变更最怕的不是写 SQL,而是执行过程失控。如果开发、测试、运维都可以在不同环境里手动执行 SQL,那么环境很容易出现差异。更严重的是,线上数据库被人工直接修改后,代码仓库里可能根本没有留下对应记录。
使用 Liquibase 后,数据库变更通常会变成这样的流程:
- 开发编写 changeSet;
- changeSet 随代码一起提交;
- 代码 Review 时一起审查数据库变更;
- 测试环境通过 Liquibase 执行;
- 预发、生产环境通过发布流程执行;
- 执行结果记录在数据库中。
也就是说,生产库不是不能变,而是不能绕过流程随意变。数据库变更从"人连上去执行 SQL",变成"变更文件进入仓库,由发布流程执行"。这就是可控。
2. 可追溯:每一次数据库变更都有记录
Liquibase 会在数据库中维护一张核心表:DATABASECHANGELOG
这张表会记录每个已经执行过的 changeSet,包括:
- changeSet 的 id;
- author;
- 文件路径;
- 执行时间;
- 执行顺序;
- checksum;
- 执行状态。
所以当我们想知道某个库是否执行过某次变更时,不需要靠猜,也不需要去翻群聊,只要看 DATABASECHANGELOG 就可以。比如某张表什么时候创建的,某个字段是不是已经加过,某个索引在哪个版本上线,都可以通过这张表追踪。

3. 可重复:新环境初始化更轻松
很多项目都有一个痛点:老环境能跑,新环境搭不起来。原因往往是数据库结构没有完整沉淀下来。项目跑了几年之后,建表 SQL、字段修改 SQL、初始化数据散落在不同地方,新人或新环境想从零搭建数据库,非常痛苦。
Liquibase 的好处是,所有数据库演进历史都在 changelog 里。一个新环境只要配置好数据库连接,执行 Liquibase,就可以按照历史顺序把数据库结构一步步构建出来。这对于本地开发环境、测试环境、临时演示环境、自动化测试环境都很有帮助。
4. 可集成:适合放进 CI/CD 流程
Liquibase 可以通过命令行、Maven、Gradle、Spring Boot、Jenkins、GitHub Actions 等方式集成到发布流程中。在 Spring Boot 项目里,比较常见的方式是应用启动时自动执行 Liquibase。
配置示例:
spring:
liquibase:
enabled: true
change-log: classpath:liquibase/master.xml
主 changelog 文件负责组织具体变更:

这样数据库变更就可以跟随应用版本一起发布,而不是靠上线前临时执行一堆 SQL。
三、Liquibase 是怎么运转的?
Liquibase 的运行逻辑其实不复杂。可以简单理解成:
读取 changelog 文件
↓
连接目标数据库
↓
检查 DATABASECHANGELOGLOCK
↓
读取 DATABASECHANGELOG
↓
判断哪些 changeSet 没执行过
↓
按顺序执行未执行的 changeSet
↓
执行成功后写入记录
↓
释放锁
这里有两张表比较关键。
DATABASECHANGELOG
这张表负责记录"哪些变更已经执行过"。Liquibase 判断一个 changeSet 是否执行过,主要看:id + author + file path
如果数据库里没有这条记录,就执行;如果已经有记录,就不会重复执行。如果已经执行过,但文件内容被改了,checksum 对不上,Liquibase 通常会报错。这也是它防止历史变更被偷偷修改的重要机制。
DATABASECHANGELOGLOCK
这张表负责加锁。在某些场景下,可能会有多个应用实例同时启动。如果它们都尝试执行 Liquibase,就可能产生并发执行数据库变更的问题。DATABASECHANGELOGLOCK 的作用就是保证同一时间只有一个 Liquibase 实例在执行变更。
四、如何接入和使用 Liquibase?
下面以 Spring Boot 项目为例,介绍 Liquibase 的基本接入方式。
1. 引入 Maven 依赖
在 pom.xml 中加入 Liquibase 依赖:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
如果项目使用的是 MySQL,还需要确保已经引入 MySQL 驱动:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
Spring Boot 会自动识别 Liquibase,并在应用启动时根据配置执行数据库变更。
2. 添加 Liquibase 配置
在 application.yml 中增加配置:
spring:
liquibase:
enabled: true
change-log: classpath:liquibase/master.xml
其中:
enabled: true 表示启用 Liquibase;
change-log 指定 Liquibase 的主变更文件。
如果不同环境有不同策略,也可以在不同配置文件中控制是否启用:
spring:
liquibase:
enabled: false
例如本地环境启用,某些特殊测试场景关闭,或者生产环境统一由流水线单独执行。
3. 创建 changelog 目录
推荐目录结构如下:
src/main/resources
└── liquibase
└── master.xml
└── db
├── 2026-001-create-user.xml
├── 2026-002-create-blog.xml
└── 2026-003-create-blog-image.xml
主文件 master.xml 只负责组织各个变更文件:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<include file="db/2026-001-create-user.xml"/>
<include file="db/2026-002-create-blog.xml"/>
<include file="db/2026-003-create-blog-image.xml"/>
</databaseChangeLog>
这样做的好处是结构清晰,每次数据库变更都有独立文件,后续维护和排查都更方便。
4. 编写 changeSet
实际项目中,可以直接把 SQL 写在 <sql> 标签中。例如创建图片上传记录表:
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="2026-003-create-blog-image" author="test">
<sql>
CREATE TABLE `blog_image` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` VARCHAR(64) NOT NULL COMMENT '上传者账号',
`original_name` VARCHAR(255) DEFAULT NULL COMMENT '原始文件名',
`file_name` VARCHAR(255) NOT NULL COMMENT '存储文件名',
`file_path` VARCHAR(500) NOT NULL COMMENT '文件存储路径',
`image_url` VARCHAR(500) NOT NULL COMMENT '图片访问URL',
`file_size` BIGINT DEFAULT NULL COMMENT '文件大小(字节)',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
PRIMARY KEY (`id`),
KEY `idx_account` (`account`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图片上传记录表';
</sql>
</changeSet>
</databaseChangeLog>
这里有几个关键点:
id 用来标识这次变更;
author 表示变更作者;
<sql> 中是真正要执行的数据库脚本;
id + author + 文件路径 共同决定一个 changeSet 的唯一性。
也就是说,这段 SQL 不再是一段临时脚本,而是一个受 Liquibase 管理的数据库变更单元。
五、使用 Liquibase 的注意事项
Liquibase 好用,但不能乱用。下面这些点非常关键。
1. 已执行过的 changeSet 不要随便改
这是最常见的坑。如果某个 changeSet 已经在测试或生产环境执行过,后面又修改了它的 SQL,Liquibase 会发现 checksum 不一致。
正确做法一般是:
- 已执行过的 changeSet 尽量保持不变;
- 后续调整通过新的 changeSet 完成;
- 把 changelog 当作数据库历史,而不是普通草稿文件。
2. id 命名要有规范
建议使用有含义的 id,比如:
2026-003-create-blog-image
2026-004-add-blog-image-index
2026-005-update-blog-status
不要简单写成:
001
002
003
项目小的时候没问题,项目大了以后很难维护。
3. SQL 要经过 Review
Liquibase 能保证流程可控,但不能保证 SQL 本身一定安全。比如这些操作仍然需要谨慎:
- 大表加字段;
- 大表建索引;
- 修改字段类型;
- 删除字段;
- 删除表;
- 大批量 update;
- 涉及数据迁移的脚本。
尤其是生产环境,大表 DDL 可能带来锁表、慢查询、主从延迟等问题。Liquibase 不是数据库性能风险的免死金牌。
4. 不要轻易删除历史 changelog
历史 changelog 文件不是执行完就没用了。它们是数据库从零演进到当前状态的完整路径。如果随便删除,可能会导致新环境初始化失败,也会影响问题追踪。
5. 回滚要单独设计,不要盲目信任自动回滚
Liquibase 支持 rollback,但生产环境的回滚不能只看结构。比如:
- 加字段可以 drop 掉;
- 建索引可以 drop 掉;
- 但删除字段后的数据可能已经丢了;
- 数据更新后也未必能完整恢复。
所以高风险变更要提前设计回滚方案,必要时先备份数据。
6. 不同环境要统一入口
不要一部分环境用 Liquibase,一部分环境手动 SQL。这样很快就会重新回到环境不一致的问题。
更推荐的方式是:
- 本地可以手动触发;
- 测试环境自动执行;
- 预发环境走发布流程;
- 生产环境通过受控流水线执行。
重点是:所有环境最终都应该以 changelog 为准。
六、Liquibase 适合什么场景?
我认为 Liquibase 特别适合这些项目:
- 多人协作开发;
- 有测试、预发、生产多个环境;
- 发布比较频繁;
- 数据库结构经常变;
- 希望减少人工操作生产库;
- 希望数据库变更能被 Review 和追踪;
- 希望新环境可以自动初始化。
如果是非常小的 Demo 项目,可能直接执行 SQL 更快。但只要项目开始长期维护,有多人协作和正式上线流程,Liquibase 的价值就会越来越明显。
七、总结
Liquibase 不是为了让数据库变更变复杂。恰恰相反,它是为了让数据库变更变得更可靠。它把原本分散、临时、依赖人工执行的 SQL,变成了有编号、有作者、有记录、有顺序、有校验的变更过程。
用 Liquibase 之后,数据库变更不再是:"我连上生产库执行一下。" 而是:"我提交一个受控的 changeSet,经过 Review,在发布流程中执行,并留下完整记录。" 这才是它真正的价值。
总结一下,Liquibase 带来的优势主要是:
- 数据库变更可控;
- 执行过程可追溯;
- 环境结构更一致;
- 减少直接操作线上数据库;
- 方便接入 CI/CD;
- 新环境初始化更简单;
- 历史变更更容易审计。
当然,Liquibase 只是工具,不是万能保险。SQL 是否安全、变更方案是否合理、生产执行时机是否合适,仍然需要团队认真评估。但从工程化角度看,Liquibase 确实能让数据库变更从"手工活"变成"标准流程"。这也是它最值得引入的地方。