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
问题根源
- 字段类型未明确指定:实体中
last_read_at仅通过@IsDate()做验证,但未在@Column中明确指定数据库字段类型为datetime。TypeORM对比元数据时,可能因数据库返回的字段类型描述与预期不符,判定字段需要重新创建。 - 复合主键与关联关系的元数据冲突:
ChatUsers的复合主键字段同时作为@ManyToOne的关联字段,TypeORM在检测元数据时,可能误判索引或外键的状态,导致重复生成重建约束的命令。 - 元数据缓存未更新:迁移执行后,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
相关产品推荐
相关产品推荐

