checksum mismatch 不是 Flyway 抽风,是它的一种保护:你改了已经应用过的迁移脚本,本地重算的 checksum 和数据库里存的对不上。别急着
repair,先想清楚该不该改这个脚本——长期正确做法是新增迁移,而不是原地改历史。
Issue & Env(案发现场与环境)
- 现象:应用启动到 Flyway 校验阶段失败,进程退出,数据库连接其实已经建立好了。
- 环境:Spring Boot 2.x / 3.x,Flyway(
flyway-core+spring-boot-starter-jdbc),迁移脚本默认放在src/main/resources/db/migration。
StackTrace(堆栈日志)
启动日志里的校验失败:
org.flywaydb.core.api.FlywayException: Validate failed: Migrations have failed validation
Migration checksum mismatch for migration version 1.0
-> Applied to database : 87747630
-> Resolved locally : 1882320251
at org.flywaydb.core.Flyway.doValidate(Flyway.java:325)
at org.flywaydb.core.Flyway.migrate(Flyway.java:187)
at org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer.afterPropertiesSet(FlywayMigrationInitializer.java:36)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)
at org.springframework.context.support.DefaultLifecycleProcessor.lambda$startBeans$1(DefaultLifecycleProcessor.java:152)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1767)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
两行数字是关键:Applied to database 是数据库记录的历史 checksum,Resolved locally 是它现在根据本地脚本重算出来的值,两者不一致就报错。
Reproduce(复现代码)
数据库已经应用过 V1__create_user.sql,之后你手贱改了它——哪怕只加一行注释:
-- V1__create_user.sql(改前)
create table user_info (
id bigint primary key
);
-- V1__create_user.sql(改后,加了一行注释)
-- add comment
create table user_info (
id bigint primary key
);
再次启动应用,Flyway 校验阶段就会抛 checksum mismatch。
Root Cause(根因分析)
Flyway 第一次应用某个迁移脚本时,把脚本内容算出一个 checksum,连同版本号、描述一起写进 flyway_schema_history 表。之后每次启动,Flyway 在 validate 阶段会重新读本地脚本、重算 checksum,再和表里的历史值比对。
只要你改了已经应用过的脚本,checksum 就变,比对自然失败。这个机制是刻意的:它防止你把自己都不知道的历史改动带进各个环境,尤其是生产。常见的触发原因有几类:
- 直接编辑了已应用的
V*脚本,哪怕只改注释、重新格式化。 - 文件换行符漂移(Windows 的 CRLF 和 Unix 的 LF 混用),内容一样 checksum 却不同。
- 文件编码不一致,脚本里含中文等非 ASCII 字符时尤其明显。
The Fix(解决方案)
分场景处理,别一上来就 repair。
先判断:这个脚本现在还该不该是原来的样子。
首选——脚本不该改。 已应用的 versioned migration 就当历史资产冻结,新变更写成新脚本:
-- V2__add_email_column.sql
alter table user_info add column email varchar(255);
脚本被误改。 把内容改回应用时的原样,checksum 就恢复,不用碰数据库。
确认新内容才是对的、且库结构和脚本一致。 这时才用 repair 重算并更新历史记录:
mvn flyway:repair \
-Dflyway.url=jdbc:mysql://localhost:3306/your_db \
-Dflyway.user=root \
-Dflyway.password=xxx
repair 只是把 flyway_schema_history 里的 checksum 刷新成当前脚本的值,不改变你的表结构。生产环境用它要格外谨慎,得先确认库里的实际结构确实和脚本一致。
开发环境最省事。 直接重建库,让 Flyway 从零跑一遍:
DROP DATABASE IF EXISTS your_db;
CREATE DATABASE your_db;
编码问题从根上防:脚本统一存成 UTF-8,并在配置里显式声明:
spring.flyway.encoding=UTF-8
这样能避免不同编辑器、不同操作系统之间的编码漂移,从源头少掉一类 checksum 变更。
