3

问题复现

问题场景,在开发阶段,由于项目没有进行项目部署,所以没有启用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来初始化模式历史表。

image.png

解决方法

问题分析

在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

image.png

这里为什么要设置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,启动成功。

image.png

查看数据库flyway_schema_history

image.png

Flyway迁移

按照verion的顺序(和数据库中的更新记录对比,找到未更新的),更新如下

image.png

更新记录如下

image.png

做一个实验,从图上我们可以看到是根据查询版本号进行判断是否进行迁移,但是如果把之前版本的内容进行修改呢,会不会报错,注释一行sql语句

image.png

从新启动后端,执行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信息看看是否能启动成功

image.png

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...


kexb
519 声望16 粉丝

引用和评论

0 条评论