You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flyway迁移脚本在PostgreSQL中设置新列为NOT NULL时测试环境执行失败排查求助

问题分析与排查方案

我来帮你拆解下这个问题——核心矛盾是相同的Flyway迁移脚本在开发环境正常运行,但在测试环境(从头执行所有迁移的嵌入式PostgreSQL)中,UPDATE语句看似没有生效,导致最后设置NOT NULL约束失败。结合你提供的信息,我整理了几个可能的原因和对应的排查步骤:

1. PostgreSQL DDL的事务特性干扰脚本执行

PostgreSQL的DDL操作(比如ALTER TABLE)会隐式提交当前事务,如果Flyway把整个脚本当作一个事务执行,第一个ALTER TABLE添加列和外键后,事务会被自动提交,后续的UPDATE和第二个ALTER TABLE会在新事务中执行。极端情况下,可能出现事务隔离级别的问题,导致UPDATE的变更没被后续的ALTER TABLE感知到。

排查步骤

把原脚本拆分成3个独立的Flyway脚本,让DDL和DML在单独的事务中执行:

  • V210111_1__add_foo_id_column.sql:仅执行添加列和外键的语句
    alter table bar add column foo_id int, add constraint fk_bar_foo_id foreign key (foo_id) references foo (id);
    
  • V210111_2__populate_foo_id.sql:仅执行UPDATE语句
    update bar set foo_id = foo.id from foo where bar.foo_pid = foo.pid;
    
  • V210111_3__set_foo_id_not_null.sql:仅执行设置NOT NULL的语句
    alter table bar alter column foo_id set not null;
    

如果拆分后脚本执行成功,说明原脚本的事务边界是问题根源。

2. 触发器隐性阻止了UPDATE生效

你的update_modified_at触发器逻辑看似没问题,但在测试环境中可能存在隐性错误:比如modified_at字段类型与now()返回值不匹配(比如字段是date但now()返回timestamp),或者row (NEW.*) is distinct from row (OLD.*)的判断在某些场景下返回false,导致触发器返回OLD而非NEW,最终UPDATE的变更被丢弃。

排查步骤

  • 临时禁用bar表的触发器:
    DROP TRIGGER tgr_update_modified_at ON bar;
    
    然后重新执行迁移脚本,如果成功,说明触发器是问题所在。
  • 简化触发器逻辑测试:去掉DISTINCT判断,直接更新modified_at
    CREATE OR REPLACE FUNCTION public.update_modified_at()
    RETURNS trigger
    LANGUAGE plpgsql
    AS $function$
    begin
      NEW.modified_at = now();
      return NEW;
    end;
    $function$ ;
    
    重新创建触发器后执行迁移,看是否生效。

3. Flyway语句解析或版本差异问题

Flyway默认用分号作为语句分隔符,如果脚本中有隐藏特殊字符(比如异常换行、制表符),或者测试环境的Flyway版本与开发环境不一致,可能导致UPDATE语句被跳过或解析错误。

排查步骤

  • 对比开发和测试环境的Flyway版本,确保一致,不一致则升级到相同版本。
  • 在UPDATE语句前后添加日志输出,验证Flyway是否执行了这条语句:
    RAISE NOTICE 'Starting to update bar.foo_id';
    update bar set foo_id = 0;
    RAISE NOTICE 'Updated % rows in bar table', (SELECT COUNT(*) FROM bar WHERE foo_id = 0);
    
    查看Flyway执行日志,如果看不到这些NOTICE信息,说明Flyway没执行这条语句;如果更新行数为0,说明UPDATE没匹配到目标行。

4. 测试环境数据初始化异常

从你提供的测试数据来看,bar表的foo_pid(0000000-0000-0000-0000-000000000000)比foo表的pid少了一个0,这直接导致原UPDATE语句无法匹配到行。即使改成固定值赋值,如果UPDATE没执行或没生效,foo_id还是会保持NULL。

排查步骤

  • 在测试环境中手动执行update bar set foo_id=0;,查看foo_id是否更新。如果手动执行成功,说明是Flyway执行脚本的问题;如果手动执行也失败,检查bar表是否有其他约束(比如行级锁、其他触发器)。
  • 检查前置迁移脚本:确认bar表的foo_pid外键约束是否生效(比如是否被设置为NOT VALID,导致存在无效数据),以及初始化数据时是否正确关联了foo表的pid。

5. 事务隔离级别差异

虽然PostgreSQL默认是READ COMMITTED,但如果测试环境的隔离级别被修改为REPEATABLE READ,可能导致同一个脚本中,UPDATE的变更无法被后续的ALTER TABLE看到。

排查步骤

在迁移脚本开头添加隔离级别设置:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

重新执行迁移,看是否能成功。


另外,你可以做一个快速验证:在测试环境中先手动执行所有前置迁移脚本,再单独执行当前这个迁移脚本。如果单独执行成功,说明问题出在从头执行所有迁移时,某个前置脚本与当前脚本存在交互冲突。

内容的提问来源于stack exchange,提问作者Rad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 07:17:36