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

TypeORM迁移重复生成相同变更的问题求助

TypeORM执行迁移后持续生成重复迁移文件的问题分析

TypeORM在成功执行迁移后,仍持续生成内容完全相同、仅时间戳不同的迁移文件,以下是相关代码及执行日志:

重复生成的迁移代码

import { MigrationInterface, QueryRunner } from "typeorm";

export class Healthnutrition1708498657688 implements MigrationInterface {
    name = 'Healthnutrition1708498657688'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP FOREIGN KEY \`fk_social_chat_users_chat_id\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP FOREIGN KEY \`fk_social_chat_users_user_id\``);
        await queryRunner.query(`DROP INDEX \`idx_social_chat_users_chat_id\` ON \`social_chat_users\``);
        await queryRunner.query(`DROP INDEX \`idx_social_chat_users_user_id\` ON \`social_chat_users\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP COLUMN \`last_read_at\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD \`last_read_at\` datetime NULL`);
        await queryRunner.query(`CREATE INDEX \`idx_social_chat_users_chat_id\` ON \`social_chat_users\` (\`chat_id\`)`);
        await queryRunner.query(`CREATE INDEX \`idx_social_chat_users_user_id\` ON \`social_chat_users\` (\`user_id\`)`);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD CONSTRAINT \`fk_social_chat_users_chat_id\` FOREIGN KEY (\`chat_id\`) REFERENCES \`social_chat\`(\`chat_id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD CONSTRAINT \`fk_social_chat_users_user_id\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`user_id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP FOREIGN KEY \`fk_social_chat_users_user_id\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP FOREIGN KEY \`fk_social_chat_users_chat_id\``);
        await queryRunner.query(`DROP INDEX \`idx_social_chat_users_user_id\` ON \`social_chat_users\``);
        await queryRunner.query(`DROP INDEX \`idx_social_chat_users_chat_id\` ON \`social_chat_users\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` DROP COLUMN \`last_read_at\``);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD \`last_read_at\` datetime NULL`);
        await queryRunner.query(`CREATE INDEX \`idx_social_chat_users_user_id\` ON \`social_chat_users\` (\`user_id\`)`);
        await queryRunner.query(`CREATE INDEX \`idx_social_chat_users_chat_id\` ON \`social_chat_users\` (\`chat_id\`)`);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD CONSTRAINT \`fk_social_chat_users_user_id\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`user_id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
        await queryRunner.query(`ALTER TABLE \`social_chat_users\` ADD CONSTRAINT \`fk_social_chat_users_chat_id\` FOREIGN KEY (\`chat_id\`) REFERENCES \`social_chat\`(\`chat_id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`);
    }
}

对应的实体文件

@UseInterceptors(ClassSerializerInterceptor)
@Entity('social_chat_users')
export class ChatUsers {
    @PrimaryColumn({ name: 'chat_id' })
    chat_id!: number

    @ManyToOne(() => Chat)
    @JoinColumn({ name: 'chat_id' })
    chat?: Chat

    @PrimaryColumn({ name: 'user_id' })
    user_id!: number

    @ManyToOne(() => User)
    @JoinColumn({ name: 'user_id' })
    user?: User

    @Column({ default: null, nullable: true })
    @IsDate()
    last_read_at?: Date

    constructor(partial: Partial<ChatUsers>) {
        Object.assign(this, partial)
    }
}

迁移执行日志

query: SELECT VERSION() AS `version`
query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'healthnutrition' AND `TABLE_NAME` = 'migrations'
query: SELECT * FROM `healthnutrition`.`migrations` `migrations` ORDER BY `id` DESC
4 migrations are already loaded in the database.
5 migrations were found in the source code.
Healthnutrition1708498611623 is the last executed migration. It was executed on Wed Feb 21 2024 06:56:51 GMT+0000 (Coordinated Universal Time).
1 migrations are new migrations must be executed.
query: START TRANSACTION
query: ALTER TABLE `social_chat_users` DROP FOREIGN KEY `fk_social_chat_users_chat_id`
query: ALTER TABLE `social_chat_users` DROP FOREIGN KEY `fk_social_chat_users_user_id`
query: DROP INDEX `idx_social_chat_users_chat_id` ON `social_chat_users`
query: DROP INDEX `idx_social_chat_users_user_id` ON `social_chat_users`
query: ALTER TABLE `social_chat_users` DROP COLUMN `last_read_at`
query: ALTER TABLE `social_chat_users` ADD `last_read_at` datetime NULL
query: CREATE INDEX `idx_social_chat_users_chat_id` ON `social_chat_users` (`chat_id`)
query: CREATE INDEX `idx_social_chat_users_user_id` ON `social_chat_users` (`user_id`)
query: ALTER TABLE `social_chat_users` ADD CONSTRAINT `fk_social_chat_users_chat_id` FOREIGN KEY (`chat_id`) REFERENCES `social_chat`(`chat_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
query: ALTER TABLE `social_chat_users` ADD CONSTRAINT `fk_social_chat_users_user_id` FOREIGN KEY (`user_id`) REFERENCES `users`(`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
query: INSERT INTO `healthnutrition`.`migrations`(`timestamp`, `name`) VALUES (?, ?) -- PARAMETERS: [1708498657688,"Healthnutrition1708498657688"]
Migration Healthnutrition1708498657688 has been  executed successfully.
query: COMMIT

问题根源

  1. 字段类型未明确指定:实体中last_read_at仅通过@IsDate()做验证,但未在@Column中明确指定数据库字段类型为datetime。TypeORM对比元数据时,可能因数据库返回的字段类型描述与预期不符,判定字段需要重新创建。
  2. 复合主键与关联关系的元数据冲突:ChatUsers的复合主键字段同时作为@ManyToOne的关联字段,TypeORM在检测元数据时,可能误判索引或外键的状态,导致重复生成重建约束的命令。
  3. 元数据缓存未更新:迁移执行后,TypeORM的元数据缓存未同步最新的数据库状态,下次生成迁移时仍基于旧缓存对比,从而生成重复内容。

解决办法

  • 明确字段类型:修改last_read_at的@Column配置,指定数据库类型:
    @Column({ type: 'datetime', default: null, nullable: true })
    @IsDate()
    last_read_at?: Date
    
  • 清理缓存:删除TypeORM缓存目录(通常为node_modules/.cache/typeorm),或重启应用,确保元数据基于最新数据库状态生成。
  • 优化实体配置:尝试将复合主键与关联字段的配置合并(部分TypeORM版本支持),避免重复定义导致的元数据检测异常:
    @PrimaryColumn({ name: 'chat_id' })
    @ManyToOne(() => Chat)
    @JoinColumn({ name: 'chat_id' })
    chat_id!: number
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 12:44:53