ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot整合Liquibase:MySQL数据库版本管理与迁移实战

SpringBoot整合Liquibase:MySQL数据库版本管理与迁移实战 说实话我一开始也怀疑过数据库也要搞版本管理是不是小题大做。直到有一次上线开发环境的库改了一个字段名生产环境的脚本忘了执行凌晨两点被线上报错电话叫醒才彻底明白数据库变更这件事纯靠人和纪律迟早要翻车。那之后我把项目里的数据库迁移方案从手工维护SQL脚本换成了Liquibase配合SpringBoot和MySQL这套常用组合跑了大半年多个项目上线再也没有因为表结构不一致出过线上事故。这篇就把我在SpringBoot整合LiquibaseMySQL过程中的完整做法、踩过的坑和排查思路整理出来希望能帮你少走弯路。1. 为什么需要Liquibase数据库变更失控的日常1.1 手工维护SQL脚本的三种典型翻车现场先说说没有工具时的常态。最常见的情况是团队里每个人手里都有一份本地数据库改表结构靠口头通知或者是往某个共享目录里丢一个update_xxx.sql。这种模式下我总结过三种必然出现的翻车现场。第一种是漏执行。开发A在自己的环境里执行了ALTER TABLE user ADD COLUMN age INT代码也推上去了但生产环境没人跑这条SQL。等新功能上线代码一查age字段直接报错。这种问题最恶心的地方在于它不会在测试阶段暴露一定是上线后才炸。第二种是重复执行。SQL脚本没有幂等保护某个脚本被手动跑了两次或者部署脚本里没判断表是否已存在于是报Duplicate column name、Table already exists这类错误。有经验的同事会习惯性在SQL前面加IF NOT EXISTS但总有漏网之鱼。第三种是环境漂移。开发、测试、预发、生产四套数据库结构越改越不像同一套系统。你在测试环境验证通过的功能到了预发环境因为表结构差异行为完全不同。我见过最离谱的一次生产库比测试库少了三张表居然是因为当初建表脚本是在测试库手写的从来没同步到生产。1.2 Liquibase和Flyway主流的两种思路数据库迁移工具两大主流一个Flyway一个Liquibase。两者的核心思路都是把数据库变更纳入版本管理但实现方式有很大差异。Flyway的思路很直接你把SQL脚本按版本命名比如V1__create_user.sql、V2__add_age.sql它按照版本号顺序逐一执行执行过的记录在flyway_schema_history表里。简单、直观、轻量适合团队习惯写SQL、变更以简单DDL为主的场景。Liquibase的思路则是用changelog文件描述变更一个changeSet对应一次变更操作格式可以是XML、YAML、JSON或者SQL。它的优势在于变更描述和具体数据库方言解耦同一套changelog可以跑在MySQL、PostgreSQL等不同数据库上支持上下文contexts区分环境支持更灵活的回滚策略preConditions前置条件控制也更精细。从我实际使用的感受来说如果项目长期只在MySQL上跑、变更也简单Flyway完全够用。但如果团队有跨数据库的潜在需求或者希望把数据库变更当成代码一样做评审、控制、回滚Liquibase的设置上限更高。1.3 本文的方案SpringBoot Liquibase MySQL我这次的组合是SpringBoot 2.7/3.x Liquibase MySQL 8.0实际在5.7上也验证过。Liquibase在SpringBoot生态里属于亲儿子级别的集成SpringBoot的自动配置机制会在启动时自动执行数据库迁移不需要自己写监听器或者容器启动钩子这是它相对纯命令行方案最舒服的一点。接下来我会按照一个真实的整合过程来讲从依赖引入、连接配置、changelog写法、启动验证到我在实际项目中遇到并解决过的问题逐个拆开说。2. 整合前的准备依赖、连接参数与Liquibase自动配置机制2.1 依赖引入pom.xml里到底要加什么先看最基础的依赖。我用的是Maven项目整合Liquibase需要引入liquibase-core同时数据库驱动mysql-connector-j不能少。如果你的项目里已经有spring-boot-starter-jdbc或者spring-boot-starter-data-jpaLiquibase会共用这个DataSource不用额外配数据源。dependency groupIdorg.liquibase/groupId artifactIdliquibase-core/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependencySpringBoot 3.x里liquibase-core的版本由SpringBoot依赖管理统一控制不需要写版本号也不建议自己指定版本否则可能和SpringBoot的自动配置行为产生兼容性问题。SpringBoot 2.x时代Liquibase版本普遍是3.x/4.xSpringBoot 3.x默认管理的是4.x甚至更高对Java 17做了适配这里需要注意。如果你用的是SpringBoot 3.2版本Liquibase版本也在持续升级整体兼容性都做得很好。真正容易出问题的反而是MySQL驱动和连接参数这个我放到下一小节说。2.2 MySQL连接配置SSL、时区与驱动版本那些坑依赖加好了接下来就是配置application.yml里的数据源。这里集中了我见过最多的坑尤其是MySQL 8。spring: datasource: url: jdbc:mysql://localhost:3306/test_db?useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver liquibase: change-log: classpath:db/changelog/db.changelog-master.yaml先说useSSLfalse。MySQL 8的驱动com.mysql.cj.jdbc.Driver在连接时如果服务端配置了SSL客户端默认会去协商加密连接。开发环境通常没配证书于是会出现SSL connection error或者更常见的Public Key Retrieval is not allowed。这个报错我单独写一节做排查这里先给出开发环境的建议useSSLfalse关掉SSL加密同时加上allowPublicKeyRetrievaltrue因为MySQL 8默认的认证插件caching_sha2_password在非SSL连接下需要先获取RSA公钥那件事没打开就会报错。serverTimezoneAsia/Shanghai是另一个常踩的坑。MySQL驱动8.x和JDK 8的组合不手动指定时区会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized虽然不影响Liquibase的DDL执行但你在SQL里如果写了now()之类的函数保存到数据库的时间会和本地差8小时。至于为什么有乱码因为MySQL返回的时区名是GBK编码驱动按UTF-8解析就花了。driver-class-name建议显式写成com.mysql.cj.jdbc.Driver。如果你还在用com.mysql.jdbc.Driver那是5.x的老驱动写法连接MySQL 8会有警告虽然能勉强跑但我不建议在生产环境冒险。2.3 Liquibase自动配置SpringBoot是怎么把它接进来的配置写完了理解一下SpringBoot是怎么在启动时把Liquibase拉起来的。其实核心就是SpringBoot自动配置机制。spring-boot-autoconfigure里有个LiquibaseAutoConfiguration它生效的条件有三个classpath里有liquibase-core、有DataSource、而且spring.liquibase.enabled的值不是false。这就是为什么只要引入依赖和配置数据源Liquibase就会自动执行。自动配置会创建SpringLiquibase这个bean设置好changelog路径、数据源、默认schema这些属性然后在应用启动阶段调用liquibase.update()方法完成迁移。整个过程在Spring容器刷新期间执行所以你不需要做任何额外编排启动即迁移。这里有几个配置项我需要重点说明整理成一张表配置项作用我的建议spring.liquibase.change-log指定主changelog文件路径用classpath:db/changelog/db.changelog-master.yamlspring.liquibase.enabled是否启用Liquibase迁移默认true本地调试时可临时设falsespring.liquibase.default-schema默认schemaMySQL下通常不设置保持默认库名spring.liquibase.liquibase-schemaLiquibase系统表所在的schema不设置就跟随业务库spring.liquibase.contexts激活的contexts多环境隔离时用spring.liquibase.label按label筛选changeSet用得不多灰度场景可考虑顺带说一下如果项目里同时引入了spring-boot-starter-data-jpaSpringBoot还会多创建一套DataSourceInitializer这和Liquibase的Migration是两条线互不干扰。但如果你同时又配置了spring.jpa.hibernate.ddl-autoupdate那Hibernate和Liquibase会各执行一遍自己的建表逻辑可能出现表结构冲突。我建议一旦用了Liquibase就把ddl-auto设为none让数据库结构完全交给Liquibase管理这才是单一职责。3. 第一个changeSetchangelog文件的写法与设计规范3.1 changelog放哪里、怎么组织依赖和配置都就绪后绕不开的就是changelog文件。放哪个目录其实没有硬性要求但业界约定俗成放在src/main/resources/db/changelog下面。主文件名我喜欢用db.changelog-master.yaml和SpringBoot默认查找路径保持一致看起来也清晰。主changelog文件本身不写具体变更只做include引入。具体变更按业务模块或者发布批次拆成子文件比如db.changelog-user.yaml、db.changelog-order.yaml。这样做的原因很简单多人并行开发时各自改各自的子文件合并冲突的概率比所有人往同一个文件里堆changeset小得多。而且代码评审的时候只看一个子文件就能了解这个模块的完整演化过程。databaseChangeLog: - include: file: db/changelog/2025/01/db.changelog-user.yaml - include: file: db/changelog/2025/01/db.changelog-order.yaml主changelog文件按日期分目录也是好习惯这样发布的时候能清楚看到每次上线的变更范围回滚某个版本时也能准确定位。3.2 用YAML写一个建表changeSetLiquibase支持四种格式XML、YAML、JSON、SQL。XML是最早期的格式标签丰富但冗长YAML更简洁适合大多数场景SQL最贴近DBA习惯但避开Liquibase本身的元数据管理能力JSON用得少。我个人默认用YAML只有在需要写复杂查询或特定数据库函数时才用SQL格式。一个标准的changeSet长这样databaseChangeLog: - changeSet: id: 20250101-001-create-user-table author: zhangsan changes: - createTable: tableName: user columns: - column: name: id type: bigint autoIncrement: true constraints: primaryKey: true nullable: false - column: name: username type: varchar(50) constraints: nullable: false unique: true - column: name: email type: varchar(100) - column: name: created_at type: datetime defaultValueComputed: CURRENT_TIMESTAMPchangeSet的三个核心属性是id、author、changes。id不一定要全局唯一只需要在同一个文件内唯一但为了防止合并冲突我习惯用日期序号描述的格式比如20250101-001-create-user-table。author不一定是人名也可以是工号或用户名它用于追踪这个变更是谁加的。changes是实际变更操作Liquibase会根据数据库类型把它翻译成对应的DDL。createTable是最常用的变更类型。除了建表日常还会用到addColumn加列、createIndex建索引、insert插数据、addForeignKeyConstraint加外键。每一类都有对应的XML标签或YAML节点用的时候去官方文档查就行。我列一下高频操作变更类型用途示例场景createTable建表新增业务表addColumn加字段给已有表扩展字段dropColumn删字段字段废弃createIndex建索引优化查询addForeignKeyConstraint加外键表关联insert插入初始数据字典表、管理员账号update更新已有数据修复脏数据sql自定义SQL复杂存储过程或特定方言3.3 配置application.yml让启动时自动执行changelog文件写好了对应的spring.liquibase.change-log已经指到了主文件路径启动SpringBoot应用Liquibase会自动执行所有未执行的changeSet。第一次启动的样子是日志里出现几行Running Changeset: db/changelog/2025/01/db.changelog-user.yaml::20250101-001-create-user-table::zhangsan数据库里多了user表同时多了DATABASECHANGELOG和DATABASECHANGELOGLOCK两张系统表。如果你不想在本地每次启动都做一次迁移检查可以用spring.liquibase.enabledfalse临时关掉但我不建议因为本地环境应该和生产环境保持一致Liquibase的迁移是幂等的重复启动不会重复执行开着它没坏处。4. 启动后发生了什么DATABASECHANGELOG与校验机制4.1 迁移执行流程拆解很多人以为Liquibase启动执行就是个有变更就执行没变更就跳过的开关。实际上它内部有清晰的状态流转理解这个流程对排查问题至关重要。Liquibase启动后的执行顺序大致是获取DATABASECHANGELOGLOCK表上的排他锁防止多个应用实例并发执行迁移。检查DATABASECHANGELOG表是否存在不存在则创建。读取当前changelog文件中所有changeSet。对照DATABASECHANGELOG表找出哪些changeSet已经执行过、哪些没有。对已经执行过的changeSet做checksum校验。按顺序执行未执行过的changeSet并在DATABASECHANGELOG表中插入记录。释放锁。这个流程里最关键的是第五步checksum校验。Liquibase在每个changeSet执行完毕后会算出这个changeSet的MD5或SHA-1校验和存到DATABASECHANGELOG表的MD5SUM字段里。下一次启动时它会重新计算当前文件中这个changeSet的校验和如果和数据库里存的不一致就会抛出Validation Failed异常启动直接失败。4.2 两张系统表解读实际跑到数据库能看到的Liquibase元数据是这两张表DATABASECHANGELOG和DATABASECHANGELOGLOCK。DATABASECHANGELOG表记录的是变更历史核心字段有字段含义IDchangeSet的idAUTHORchangeSet的作者FILENAME所属changelog文件路径DATEEXECUTED执行时间MD5SUM校验和DESCRIPTION变更类型描述TAG回滚标签EXECTYPE执行方式一般EXECUTEDDATABASECHANGELOGLOCK表则更简单核心只关心LOCKED字段。LOCKED1表示当前有实例正在执行迁移其他实例启动时会一直等待直到锁释放。如果某个实例异常崩溃导致锁没释放后续启动就会卡在等待锁这一步。我见过有同事看到这两张表以为是垃圾数据想删掉这是典型的操作事故隐患。删掉DATABASECHANGELOG表等于告诉Liquibase所有变更都没执行过它会重新跑所有changeSet库里已有的表大概率直接报Table already exists。所以我在这里明确一下这两张表是Liquibase的命根子除了特定维护场景不要动它们。4.3 checksum校验为什么会报Validation Failedchecksum机制是Liquibase保证迁移一致性的核心手段也是最容易让新手崩溃的地方。它背后的道理就是已经执行过的变更就像已经记上账的流水不能涂改。你如果回去编辑已经执行过的changeSet不管是改了个字段名还是加了个空格Liquibase都会认为这是一个不同的变更和数据库里的记录对不上于是拒绝启动。生活化地类比一下这就像你银行里的流水已经打出来了有人拿笔去改流水单上的金额。银行核对的时候发现账目对不上那肯定要冻结处理。Liquibase做的是同样的事只不过它用checksum当防篡改标记。遇到Validation Failed不要慌先明确一点改动已经执行过的changeSet是错误操作正确的做法是写一个新的changeSet去做增量变更。比如要修改某个字段长度就写一个modifyDataType的changeSet而不是去改最初建表的那个changeSet。我在下一节会把这类的完整排查链路展开这里先记住大原则。5. 真刀真枪排坑SSL连接、checksum冲突与SQL方言问题5.1 MySQL 8连接报错SSL、公钥检索的完整排查链路这一节我讲讲我实际遇到过、也网上回复过很多次的三个经典启动报错。第一个就是MySQL 8连接问题。启动报错内容大致长这样Caused by: java.sql.SQLException: Public Key Retrieval is not allowed拿这个例子说。现象是SpringBoot项目一启动就连不上数据库但同事的机器上一样的环境却好好的。我当时的排查链路是这样走的第一步确认JDBC URL里有没有allowPublicKeyRetrieval。没有的话加allowPublicKeyRetrievaltrue先试。大多数情况到这步就解决了。为什么需要这个参数因为MySQL 8默认的认证插件是caching_sha2_password它比老版本的mysql_native_password安全但在非SSL连接下客户端要拿到服务端的RSA公钥才能发送加密后的密码。允许客户端从服务端自动获取公钥的行为在JDBC驱动里默认是关闭的所以你要显式打开。如果你不想暴露公钥检索那只能走SSL加密连接或者把用户改成mysql_native_password但后者不推荐。第二步如果还报SSL相关错误比如SSL connection error或Communications link failure那就看URL里有没有useSSLfalse。开发环境没配证书的情况下把SSL关掉是最快路径。但注意这仅限于内网开发环境生产环境如果数据库和应用之间走公网SSL不能随便关隐私数据加密是底线。第三步如果上面都做了还在报时区错误检查serverTimezone。这个我之前提过不加会导致驱动无法识别MySQL返回的时区名。URL里的完整参数就按我第二节给的写法useUnicodetruecharacterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/Shanghai。这套参数组合在MySQL 5.7和8.0上都验证过也是目前社群里最主流的写法。如果你用的是云数据库比如阿里云RDS连接参数可能略有差异但上面的排查顺序和思路可以复用。5.2 修改已执行changeSet导致checksum失败的现场第二个经典场景是checksum冲突。描述一下现场某天你改了一个已经执行过的changeSet比如给建表语句里的某个字段追加了一个注释或者加了个默认值重启应用后启动失败日志里明确写着Validation Failed: 1 change sets check sum db/changelog/2025/01/db.changelog-user.yaml::20250101-001-create-user-table::zhangsan was: 8:abcdef123456 is now: 8:123456abcdef看日志后面的校验值was是数据库里存的is now是当前文件算出来的。数值不一样说明这个changeSet确实被动过。这时候的排查链路应该是第一步确认到底是哪个文件、哪个changeSet出了问题。日志里已经给出了文件名、id和author直接定位。第二步确认这个changeSet之前是否已经执行过。查DATABASECHANGELOG表SELECT ID, AUTHOR, MD5SUM, DATEEXECUTED FROM DATABASECHANGELOG WHERE FILENAME db/changelog/2025/01/db.changelog-user.yaml;如果查到对应的记录说明执行过那当前文件的改动就是篡改。第三步判断改动是否必要。如果只是无关紧要的格式变化建议恢复原文件让checksum回到数据库里的值。如果确实需要变更那应该新建一个changeSet比如- changeSet: id: 20250201-001-modify-username-comment author: zhangsan changes: - modifyDataType: tableName: user columnName: username newDataType: varchar(64)第四步万不得已的情况下可以执行mvn liquibase:clearChecksums清掉所有checksum重启后重新计算。但这相当于告诉Liquibase我不小心改了历史但请放过我会失去对这一批changeSet的篡改检测能力生产环境要谨慎再谨慎。我的建议是除非你100%确认改动是无害的并且全团队只有你一个人在用这套Liquibase否则不要走clearChecksums这条路。5.3 MySQL方言与保留字、事务的几个实战注意点第三个场景相对隐蔽但一旦踩到也很头疼。就是SQL方言和MySQL特有行为导致的坑。MySQL的保留字问题首当其冲。order、group、desc、lock这些词在业务表里特别常见。你在Liquibase的YAML里写tableName: order它会生成CREATE TABLE order(...)MySQL直接报语法错误。解决办法有两个一是建表时就把这些字段名避开比如用order_info、order_no这是最省心的二是给表名或列名加反引号Liquibase的YAML里可以在tableName或column下显式写quoted: true但这样做会降低changelog在不同数据库之间的可移植性我一般不用。再说事务问题。MySQL和InnoDB下DDL语句是支持事务的但有一个关键限制DDL会隐式提交当前事务。也就是说如果一个changeSet里既写了createTable又写了insert它们在一个事务里执行时createTable执行完可能就隐式提交了后续insert如果失败表还是留下来了但数据没进去整个changeSet在Liquibase的DATABASECHANGELOG里是未记录状态。我遇到过一次这样的现场changeSet里建了一张配置表然后往里插了三行初始数据。结果第二行数据因为唯一键冲突报错Liquibase标记这个changeSet未执行。但表已经建出来了导致下一次启动同一个changeSet重跑时createTable又报Table already exists。排查这个问题时我先看DATABASECHANGELOG表里有没有这个changeSet的记录发现没有再看表结构发现表确实存在最后定位到是DDL隐式提交惹的祸。这类问题的根治方法是把建表和插数据拆成两个changeSet并且遵循一个原则一个changeSet只做一类操作要么是结构变更要么是数据变更不要混着写。另外如果数据初始化操作逻辑比较复杂我会用splintStatements或直接写sql类型的changeSet把多个insert包在一个存储过程里但这属于进阶用法不展开细说。还有一个容易被忽略的是字符集。MySQL默认字符集在5.7和8.0不一样5.7默认是latin18.0默认utf8mb4。如果你用Liquibase建表不带任何编码设置表结构会跟随数据库默认。上次我在一个5.7的库上建了张表中文写入变成问号排查半天最后发现是表的默认字符集是latin1。解决方案是在changeSet的createTable里显式指定在YAML中可以给createTable节点的remarks或列定义设置encoding属性或者干脆单独执行一条ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4。我现在所有新项目都会在第一个changeSet里加一段配置确保新表统一用utf8mb4从源头避免乱码问题。6. 进阶玩法多环境contexts、回滚策略与执行锁6.1 contexts上下文开发环境和生产环境的数据不一样基础整合跑通之后你会遇到一个更现实的需求不同环境需要不同的初始数据。比如开发环境要插入一批测试用的管理员账号、配置项生产环境则只需要插入默认配置不能有测试数据。如果写死在同一个changeSet里两边都会执行就会污染生产环境。Liquibase的contexts就是专门解决这个问题的。每个changeSet可以声明它属于哪个或哪些上下文只有激活的context匹配时才会执行。具体用法是在changeSet里加一个context属性- changeSet: id: 20250201-002-insert-dev-admin author: zhangsan context: dev changes: - insert: tableName: user columns: - column: name: username value: devadmin - column: name: email value: devexample.com然后在application.yml里用spring.liquibase.contexts指定当前环境激活的上下文spring: liquibase: contexts: dev生产环境配置里写contexts: prod那么上面那个插数据的changeSet在生产环境就不会执行。注意context可以写多个用逗号分隔比如context: dev,test匹配逻辑是当前激活的contexts集合里包含任意一个就算匹配。我用这个功能管理了初始化数据、测试数据、灰度发布标记等场景之后最大的感受是环境差异从人为执行SQL变成了配置驱动自动迁移任何环境拉起应用数据库状态都是自洽的。6.2 rollback回滚怎么把数据库倒回去数据库迁移不是只进不退。Liquibase支持回滚但有个关键点要先说清楚不是所有变更都能自动回滚。createTable可以通过dropTable自动回滚addColumn可以通过dropColumn自动回滚但像dropTable这种破坏性操作就没有自动回滚的可能它本身就是不可逆的。因此对于重要的变更我建议手动定义rollback逻辑。在changeSet里加rollback节点- changeSet: id: 20250201-003-add-user-age author: zhangsan changes: - addColumn: tableName: user columns: - column: name: age type: int rollback: - dropColumn: tableName: user columnName: age手动定义了rollback之后你可以在命令行执行mvn liquibase:rollback -Dliquibase.rollbackCount1回滚最近一个changeSet或者先打个tag再按tag回滚mvn liquibase:tag -Dliquibase.tagversion-2.0 mvn liquibase:rollback -Dliquibase.tagversion-2.0这个机制上线发布的时候特别好用。我的发布流程是先在预发环境执行Liquibase迁移验证没问题后再到生产环境执行。万一生产环境执行后发现问题需要撤掉这次变更就在Liquibase里找到对应的回滚SQL重新执行。这里要特别注意生产回滚不要走mvn liquibase:rollback直接操作生产库除非你有完整的备份和演练流程。慎终如始数据库变更的每一步都值得敬畏。6.3 执行锁与并发问题最后一个进阶话题是执行锁。我在4.1小节提到过DATABASECHANGELOGLOCK表这里展开说下实际工作中遇到的问题。想象一个场景你的应用部署了多个实例比如两台服务器同时启动两个实例同时触发了Liquibase迁移。如果没有锁机制两个实例会同时去执行同一个changeSet结果就是Duplicate column name或者更严重的脏数据。Liquibase通过DATABASECHANGELOGLOCK表实现了分布式锁保证同一时刻只有一个实例能执行迁移。但锁机制也会出问题。最常见的场景是某个changeSet执行了特别长的操作比如几百万行的数据清洗跑了一个小时。这时候另一个实例启动在获取锁的时候会一直等待等待超时后抛异常。还有更麻烦的如果持有锁的实例被强制kill掉DATABASECHANGELOGLOCK里可能残留LOCKED1的记录后续实例永远等不到锁。遇到这种情况我的排查步骤是先查锁表状态SELECT * FROM DATABASECHANGELOGLOCK;如果LOCKED1且LOCKGRANTED是几个小时前基本可以判断是僵尸锁。检查现在有没有其他实例正在跑Liquibase确认没有之后把这行锁记录删掉或把LOCKED改成0让后续实例重新获取。DELETE FROM DATABASECHANGELOGLOCK;生产环境删除锁表记录之前务必确认没有其他Liquibase实例正在执行迁移否则可能两个实例同时操作同一批changeSet造成数据不一致。这个锁机制让我在实际工作中养成了一个习惯Liquibase迁移期间尽量不要滚动部署多个实例同时启动最好让一台实例先完成迁移再拉起其余实例。操作上就是发布脚本里加一个等待逻辑或者利用K8s的preStop钩子控制。细节虽小但能避免大部分并发问题。7. 我的一些体会从手工维护SQL脚本到正经用Liquibase管理数据库变更我最大的感受是数据库结构终于像代码一样可追溯、可评审、可回滚了。现在团队里每次有表结构变更先写changelog文件再提Pull Request同事review的时候能直接看到改动内容、影响范围而不是凭空说我改了张表。最后再分享一个小技巧Liquibase的DATABASECHANGELOG表就是一部完整的数据库结构演进史我定期会在测试环境对比一下这张表和上线记录确认环境漂移情况。这套机制用顺手之后你会发现数据库变更不再是上线前最担心的环节——它变成了最安静、最不需要人介入的环节。
返回列表