后续迁移“无法创建 table(错误号:150 "Foreign key constraint is incorrectly formed")”
Sequelize migration 'Can't create table (errno: 150 "Foreign key constraint is incorrectly formed")'
将 Sequelize 添加到我的项目时,我在开始迁移和添加外键约束方面遇到了很大的困难。
文档...好吧,还有改进的余地!
根据我的网上搜索,其他人似乎也在苦苦挣扎。
想要以一对多关系关联两个表:
模型 1:用户状态(值:'init'、'active'、'inactive' ...)
模型 2:用户(用户名、电子邮件、pwdhash、...)
首先,创建迁移文件:
$ sequelize model:generate --name Userstatus --attributes name:string,value:tinyint,comment:string
$ sequelize model:generate --name User --attributes username:string,pwdhash:string,email:string,statusId:tinyint
最终得到这四个文件(包括修改):
- migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
name: {
type: Sequelize.STRING,
},
comment: {
type: Sequelize.STRING,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Userstatus");
},
};
- migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.BIGINT,
},
username: {
type: Sequelize.STRING,
},
pwdhash: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
statusId: {
type: Sequelize.TINYINT,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "SET DEFAULT",
},
statusUntil: {
allowNull: false,
type: Sequelize.DATE,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Users");
},
};
- models/userstatus.js(运行时,与迁移无关)
- models/user.js(运行时,与迁移无关)
正在执行命令:
$ sequelize db:migrate
Sequelize CLI [Node: 14.17.0, CLI: 6.2.0, ORM: 6.9.0]
Loaded configuration file "db\config\config.js".
Using environment "development".
== xxxxxxxxxxxxx1-create-userstatus: migrating =======
== xxxxxxxxxxxxx1-create-userstatus: migrated (0.101s)
== xxxxxxxxxxxxx2-create-users: migrating =======
ERROR: Can't create table myDb
.Users
(errno: 150 "Foreign key constraint is incorrectly formed")
添加详细的logging to config表明这是一个MySQL由onDelete(不接受SET DEFAULT)引起的错误:
Can't create foreign key with ON DELETE SET DEFAULT
在某处,我也遇到了由类型不匹配(TINYINT 与 INTEGER)引起的完全相同的错误响应:
- migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
:
};
- migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
:
statusId: {
type: Sequelize.INTEGER,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
},
:
};
经验教训:发生迁移错误时,首先关注 DB 和 SQL - 确保生成的 SQL 在手动执行时确实有效。
这是我个人最终花费比需要更多时间的地方 - 我在知道发生了什么之前尝试 'fix' Sequelize 迁移......真正的修复 正在打开日志记录以便直接访问 SQL。
UPDATE:直接在创建 table 的地方定义外键约束,产生另一个需要处理的问题 - 为了撤消此类迁移,'undo'-部分迁移需要持有额外的queryInterface.removeConstraint(....)命令,引入需要使用Promise.all([...])(example here)
也许这只是个人喜好问题...我现在选择在单独的迁移文件中定义外键约束,如下所示:
migrations/xxxxxxxxxxxxxx-fk-userstatus-associate.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
return await queryInterface.addConstraint("Users", {
type: "FOREIGN KEY",
fields: ["statusId"], // field name of the foreign key
name: "fk_users_statusId",
references: {
table: "Userstatus", // Target model
field: "id", // key in Target model
},
onUpdate: "CASCADE",
onDelete: "RESTRICT",
});
},
down: async (queryInterface, Sequelize) => {
return await queryInterface.removeConstraint(
"Users", // Source model
"fk_users_statusId" // key to remove
);
},
};
获胜:
- 平滑的撤消处理:
$ sequelize db:migrate:undo
- 我可以随意命名约束条件
将 Sequelize 添加到我的项目时,我在开始迁移和添加外键约束方面遇到了很大的困难。
文档...好吧,还有改进的余地!
根据我的网上搜索,其他人似乎也在苦苦挣扎。
想要以一对多关系关联两个表:
模型 1:用户状态(值:'init'、'active'、'inactive' ...)
模型 2:用户(用户名、电子邮件、pwdhash、...)
首先,创建迁移文件:
$ sequelize model:generate --name Userstatus --attributes name:string,value:tinyint,comment:string
$ sequelize model:generate --name User --attributes username:string,pwdhash:string,email:string,statusId:tinyint
最终得到这四个文件(包括修改):
- migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
name: {
type: Sequelize.STRING,
},
comment: {
type: Sequelize.STRING,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Userstatus");
},
};
- migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.BIGINT,
},
username: {
type: Sequelize.STRING,
},
pwdhash: {
type: Sequelize.STRING,
},
email: {
type: Sequelize.STRING,
},
statusId: {
type: Sequelize.TINYINT,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
onDelete: "SET DEFAULT",
},
statusUntil: {
allowNull: false,
type: Sequelize.DATE,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
allowNull: true,
type: Sequelize.DATE,
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Users");
},
};
- models/userstatus.js(运行时,与迁移无关)
- models/user.js(运行时,与迁移无关)
正在执行命令:
$ sequelize db:migrate
Sequelize CLI [Node: 14.17.0, CLI: 6.2.0, ORM: 6.9.0]
Loaded configuration file "db\config\config.js".
Using environment "development".
== xxxxxxxxxxxxx1-create-userstatus: migrating =======
== xxxxxxxxxxxxx1-create-userstatus: migrated (0.101s)== xxxxxxxxxxxxx2-create-users: migrating =======
ERROR: Can't create table
myDb
.Users
(errno: 150 "Foreign key constraint is incorrectly formed")
添加详细的logging to config表明这是一个MySQL由onDelete(不接受SET DEFAULT)引起的错误:
Can't create foreign key with ON DELETE SET DEFAULT
在某处,我也遇到了由类型不匹配(TINYINT 与 INTEGER)引起的完全相同的错误响应:
- migrations/xxxxxxxxxxxxx1-create-userstatus.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Userstatus", {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.TINYINT,
},
:
};
- migrations/xxxxxxxxxxxxx2-create-users.js
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable("Users", {
:
statusId: {
type: Sequelize.INTEGER,
references: {
model: "Userstatus",
key: "id",
},
onUpdate: "CASCADE",
},
:
};
经验教训:发生迁移错误时,首先关注 DB 和 SQL - 确保生成的 SQL 在手动执行时确实有效。
这是我个人最终花费比需要更多时间的地方 - 我在知道发生了什么之前尝试 'fix' Sequelize 迁移......真正的修复 正在打开日志记录以便直接访问 SQL。
UPDATE:直接在创建 table 的地方定义外键约束,产生另一个需要处理的问题 - 为了撤消此类迁移,'undo'-部分迁移需要持有额外的queryInterface.removeConstraint(....)命令,引入需要使用Promise.all([...])(example here)
也许这只是个人喜好问题...我现在选择在单独的迁移文件中定义外键约束,如下所示:
migrations/xxxxxxxxxxxxxx-fk-userstatus-associate.js"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
return await queryInterface.addConstraint("Users", {
type: "FOREIGN KEY",
fields: ["statusId"], // field name of the foreign key
name: "fk_users_statusId",
references: {
table: "Userstatus", // Target model
field: "id", // key in Target model
},
onUpdate: "CASCADE",
onDelete: "RESTRICT",
});
},
down: async (queryInterface, Sequelize) => {
return await queryInterface.removeConstraint(
"Users", // Source model
"fk_users_statusId" // key to remove
);
},
};
获胜:
- 平滑的撤消处理:
$ sequelize db:migrate:undo
- 我可以随意命名约束条件