问题复现
问题场景,在开发阶段,由于项目没有进行项目部署,所以没有启用flyway对数据库进行版本控制,之后到部署阶段,启用了flyway了,但是问题就是flyway会根据版本控制进行执行相关SQL语句,就会出现一些错误,导致项目无法进行启动
错误信息
Error creating bean with name 'flywayInitializer' defined in class path
resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$F
lywayConfiguration.class]: Found non-empty schema(s) `db` but no
schema history table. Use baseline() or set baselineOnMigrate to true
to initialize the schema history table.
找到的非空架构“db”,但没有架构历史表。使用baseline()或将baselineOnMigrate设置为true来初始化模式历史表。
解决方法
问题分析
在Flyway在尝试迁移数据库时,发现你的数据库已经有了一些表,但没有找到Flyway用来记录迁移历史的表。Flyway需要一个叫做"schema history table"(通常是flyway_schema_history)的表来跟踪哪些迁移已经应用过。
关键信息是这一条:
关键信息是这一条:
Found non-empty schema(s) `db` but no
schema history table. Use baseline() or set baselineOnMigrate to true
to initialize the schema history table.
解决方案
在Flyway的配置中设置baselineOnMigrate=true,让Flyway在执行迁移时自动创建历史表并从当前状态开始管理迁移。
flyway:
enabled: true
# 在迁移中启用基线
baseline-on-migrate: true
# 将基线版本设置为您想要开始的版本
baseline-version: 0.0.5
这里为什么要设置0.0.5
flyway会将V0.0.5__add_info_to_training.sql作为基线版本,不会再执行这个SQL脚本。Flyway会认为这个版本的迁移已经完成,并从V0.0.5开始记录数据库迁移历史。
具体来说:
V0.0.5__add_info_to_training.sql:这将被视为已经应用过的迁移,因此不会再执行。
后续的迁移:Flyway将会从V0.0.6(或其他后续版本)开始执行迁移脚本。
因此,V0.0.5__add_info_to_training.sql的内容不会在设置基线后再次执行。Flyway只会执行V0.0.6及其之后的迁移脚本。
此时,从新启动后端,执行flyway,启动成功。
查看数据库flyway_schema_history
Flyway迁移
按照verion的顺序(和数据库中的更新记录对比,找到未更新的),更新如下
更新记录如下
做一个实验,从图上我们可以看到是根据查询版本号进行判断是否进行迁移,但是如果把之前版本的内容进行修改呢,会不会报错,注释一行sql语句
从新启动后端,执行flyway,启动失败
Migration checksum mismatch for migration version 0.0.5
-> Applied to database : -820011765
-> Resolved locally : -1079584856
我们查看flyway_schema_history表的字段发现,他是通过checksum这个字段进行内容校验
//字段名:
installed_rank //迁移的顺序
version //版本号
description //描述
type //类型
script //迁移的文件名
checksum //迁移内容的校验
installed_by //迁移的用户
installed_on //迁移被执行的时间戳
execution_time //执行过程的时间(过程时间)
success //成功: 1 、 失败:0
再做一个实验,这次我们进行修改description信息看看是否能启动成功
add info to training
修改为
add info to trainingg
从新启动后端,执行flyway,启动失败
Caused by: org.flywaydb.core.api.exception.FlywayValidateException: Validate failed: Migrations have failed validation
Migration description mismatch for migration version 0.0.5
-> Applied to database : add info to trainingg
-> Resolved locally : add info to training
查看flyway源码发现他对每一个字段都进行了校验,只要一个条件不符合就执行不成功
if (this.resolvedMigration != null && this.appliedMigration != null && this.getType() != CoreMigrationType.DELETE && !this.getType().isUndo()) {
// 获取迁移标识符
migrationIdentifier = this.appliedMigration.getVersion() == null
? this.appliedMigration.getScript() // 如果没有版本号,使用脚本名称作为标识符
: "version " + this.appliedMigration.getVersion(); // 否则使用版本号作为标识符
// 检查迁移是否符合当前基线(baseline)版本
if (this.getVersion() == null || this.getVersion().compareTo(this.context.appliedBaseline) > 0) {
String mismatchMessage;
// 校验迁移类型是否匹配
if (this.resolvedMigration.getType() != this.appliedMigration.getType()) {
mismatchMessage = this.createMismatchMessage("type", migrationIdentifier, this.appliedMigration.getType(), this.resolvedMigration.getType());
return new ErrorDetails(ErrorCode.TYPE_MISMATCH, mismatchMessage);
}
// 校验迁移内容的校验和(checksum)是否匹配
if ((this.resolvedMigration.getVersion() != null ||
this.context.isPendingIgnored() && MigrationState.OUTDATED != state && MigrationState.SUPERSEDED != state) &&
!this.resolvedMigration.checksumMatches(this.appliedMigration.getChecksum())) {
mismatchMessage = this.createMismatchMessage("checksum", migrationIdentifier, this.appliedMigration.getChecksum(), this.resolvedMigration.getChecksum());
return new ErrorDetails(ErrorCode.CHECKSUM_MISMATCH, mismatchMessage);
}
// 校验迁移描述是否匹配
if (this.descriptionMismatch(this.resolvedMigration, this.appliedMigration)) {
mismatchMessage = this.createMismatchMessage("description", migrationIdentifier, this.appliedMigration.getDescription(), this.resolvedMigration.getDescription());
return new ErrorDetails(ErrorCode.DESCRIPTION_MISMATCH, mismatchMessage);
}
}
}
// 检查迁移状态,如果未被忽略且状态为 PENDING(待处理),则执行额外验证
if (!this.context.isPendingIgnored() && MigrationState.PENDING == state && this.resolvedMigration instanceof ResolvedMigrationImpl) {
((ResolvedMigrationImpl)this.resolvedMigration).validate();
}
// 如果没有问题,返回 null,表示没有发现校验错误
return null;
从源码我们可以看到对基线(baseline)进行了比较,这样就能明白了为什么设置基线(baseline)就能解决最开始的问题
if (this.getVersion() == null || this.getVersion().compareTo(this.context.appliedBaseline) > 0) {
案例 1:假设 this.getVersion() 为 V2,this.context.appliedBaseline 为 V1。
由于 V2 比 V1 大,条件 this.getVersion().compareTo(this.context.appliedBaseline) > 0 为真,所以会执行后续的校验逻辑。
案例 2:假设 this.getVersion() 为 null,this.context.appliedBaseline 为 V1。
因为版本号为 null,条件 this.getVersion() == null 为真,所以会执行后续的校验逻辑。
案例 3:假设 this.getVersion() 为 V0.5,this.context.appliedBaseline 为 V1.0。
由于 V0.5 小于 V1.0,条件 this.getVersion().compareTo(this.context.appliedBaseline) > 0 为假,校验逻辑将被跳过。
参考文章
https://pdai.tech/md/spring/springboot/springboot-x-mysql-fly...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。