Node.js中Sequelize迁移互引用表(roles与users)的问题排查
解决Sequelize中互相引用表的迁移问题
你的问题出在循环外键约束的创建顺序:创建roles表时,users表还不存在,created_by的外键约束会直接报错;反过来先创建users,roles_id的外键又依赖不存在的roles表。数据库不允许在未创建的表上建立外键,必须拆分表创建和外键添加的步骤,处理流程如下:
处理步骤
- 先创建两张不含外键约束的表(或将外键字段暂时设为允许为空)
- 两张表创建完成后,再分别添加外键约束
- (可选)若需要外键字段非空,需先插入满足约束的初始数据,再修改字段为非空
- 回滚(down方法)时,必须先删除外键约束,再删除表,避免删除表时因外键关联报错
修改后的迁移代码
"use strict" module.exports = { up: async (queryInterface, Sequelize) => { // 1. 创建roles表,暂时不设置created_by的外键,先允许为空 await queryInterface.createTable("roles", { id: { type: Sequelize.BIGINT, primaryKey: true, autoIncrement: true, }, name: { type: Sequelize.STRING, allowNull: false, unique: true }, created_by: { type: Sequelize.BIGINT, allowNull: true, // 先设为允许为空,后续添加外键后再修改 }, }) await queryInterface.addIndex("roles", { fields: ["name"], }) // 2. 创建users表,暂时不设置roles_id的外键,先允许为空 await queryInterface.createTable("users", { id: { type: Sequelize.BIGINT, allowNull: false, primaryKey: true, autoIncrement: true, }, roles_id: { type: Sequelize.BIGINT, allowNull: true, // 先设为允许为空 } }) // 3. 添加外键约束 // 给roles表的created_by添加外键到users.id await queryInterface.addConstraint("roles", { fields: ["created_by"], type: "foreign key", name: "roles_created_by_fkey", // 自定义外键名称,方便后续删除 references: { table: "users", field: "id", }, onDelete: "CASCADE", onUpdate: "CASCADE", }) // 给users表的roles_id添加外键到roles.id await queryInterface.addConstraint("users", { fields: ["roles_id"], type: "foreign key", name: "users_roles_id_fkey", references: { table: "roles", field: "id", }, onDelete: "CASCADE", onUpdate: "CASCADE", }) // 4. 插入初始数据,然后将字段改为非空 // 先插入超级管理员角色 const [roleId] = await queryInterface.bulkInsert("roles", [{ name: "super_admin", created_by: null }], { returning: ["id"] }) // 插入对应用户 const [userId] = await queryInterface.bulkInsert("users", [{ roles_id: roleId }], { returning: ["id"] }) // 更新roles的created_by为刚创建的用户id await queryInterface.update("roles", { created_by: userId }, { where: { id: roleId } }) // 将两个字段改为非空 await queryInterface.changeColumn("roles", "created_by", { type: Sequelize.BIGINT, allowNull: false, }) await queryInterface.changeColumn("users", "roles_id", { type: Sequelize.BIGINT, allowNull: false, }) }, down: async (queryInterface, Sequelize) => { // 回滚时先删除外键约束,再删表 await queryInterface.removeConstraint("roles", "roles_created_by_fkey") await queryInterface.removeConstraint("users", "users_roles_id_fkey") await queryInterface.dropTable("users") await queryInterface.dropTable("roles") }, }
关键说明
- 使用
addConstraint而非在createTable中直接定义外键,确保两张表都存在后再建立关联 - 自定义外键名称,方便回滚时准确删除约束
- 由于两个外键字段都要求非空,必须先插入满足循环依赖的初始数据(如超级管理员角色和用户),才能将字段改为非空
- 回滚顺序要先删外键再删表,避免数据库因外键关联阻止表删除
内容的提问来源于stack exchange,提问作者Twigu
相关产品推荐
相关产品推荐

