使用 mysql workbench 创建 CHAR 类型的外键时出错:错误 1005:无法创建 table (errno: 150)

Error when creating foreign key of type CHAR with mysql workbench: Error 1005: Can't create table (errno: 150)

我定义了以下2个table:

record_status

显示创建 TABLE record_status

CREATE TABLE `record_status` (  
  `record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  `status` char(6) NOT NULL,  
  `status_description` varchar(15) NOT NULL,  
  `created_at` datetime NOT NULL,  
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  PRIMARY KEY (`record_status_id`,`status`)  
  ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

用户

显示创建 TABLE 用户

CREATE TABLE `user` (  
  `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  `handle` varchar(45) NOT NULL,  
  `email` varchar(255) NOT NULL,  
  `password` char(64) DEFAULT NULL,  
  `password_salt` binary(1) DEFAULT NULL,  
  `first_name` varchar(50) NOT NULL,  
  `last_name` varchar(50) NOT NULL,  
  `gender` char(1) DEFAULT NULL,  
  `birthday` date NOT NULL,  
  `created_at` datetime NOT NULL,  
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  `user_status` char(6) DEFAULT NULL,  
  PRIMARY KEY (`user_id`),  
  KEY `usr_status_idx` (`user_status`)  
) ENGINE=InnoDB DEFAULT CHARSET=latin1

我尝试使用 mysql Workbench 添加 CHAR 类型的外键 user_status,如下所示:

ALTER TABLE `mydatabase`.`user`  
  ADD CONSTRAINT `usr_status`  
    FOREIGN KEY (`user_status`)  
    REFERENCES `mydatabase`.`record_status` (`status`)
    ON DELETE NO ACTION  
    ON UPDATE NO ACTION;

但我收到以下错误:

错误:

Executing SQL script in server
ERROR: Error 1005: Can't create table 'mydatabase.#sql-420_1b0' (errno: 150)

ALTER TABLE 'mydatabase'.'user'
ADD CONSTRAINT 'usr_status'
 FOREIGN KEY ('user_status')
 REFERENCES 'mydatabase'.'record_status'('status')
 ON DELETE NO ACTION
 ON UPDATE NO ACTION

SQL script execution finished: statements: 4 succeeded, 1 failed.

问题
我的目的是让状态栏清楚地显示每个用户的当前状态(ACTIVE、INACTV、DELETD),同时仍然可以灵活地加入 record_status table 与用户 table 使用record_status_id 以查找具有给定状态的任何行以获得更好的性能。

我在这里找到了一个类似的post Adding foreign key of type char in mysql 这建议更改我的主键排序规则,但是,这将如何影响我的用户 table?

我是否也必须将排序规则更改为我的用户 table 中的 user_status 字段?每次用户登录时都会查询用户 table,我担心性能或这可能导致的任何限制。

我还打算为其他几个 table 的状态添加一个外键。我只想知道这对性能有何影响,或者它是否增加了任何限制?

任何关于我的设计的意见也将不胜感激。感谢您的帮助!

您遇到的问题实际上与整理无关(尽管整理可能是导致您在不同情况下遇到错误的原因)。

您的 FOREIGN KEY 约束失败,因为您在 record_status.status 上没有 单独 的索引。您将该列作为复合 PRIMARY KEY (record_status_id, status) 的一部分,但要成功创建外键约束,引用 table 和被引用 table 都必须在 上准确地具有索引键关系中使用的列(除了相同的数据类型)。

添加 FOREIGN KEY 约束会在引用 table 上隐式创建必要的索引,但您仍必须确保在引用的 table.[=45= 上有相应的索引]

鉴于您现在拥有的,如果您在 record_status.status 上添加单个索引,将正确创建约束。

CREATE TABLE `record_status` (  
  `record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  `status` char(6) NOT NULL,  
  `status_description` varchar(15) NOT NULL,  
  `created_at` datetime NOT NULL,  
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  PRIMARY KEY (`record_status_id`,`status`),
  -- This would make your relationship work...
  KEY (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

但是,我认为这不是最好的做法。我认为 (record_status_id, status) 上不需要复合主键,主要是因为 record_status_id 本身就是 AUTO_INCREMENT 并且保证是唯一的。该列单独可以是 PRIMARY KEY,同时仍然在 status 上添加额外的 UNIQUE KEY 以满足外键约束的索引要求。毕竟不是record_status_idstatus组合唯一标识每一行(做主键)

CREATE TABLE `record_status` (  
  `record_status_id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  `status` char(6) NOT NULL,  
  `status_description` varchar(15) NOT NULL,  
  `created_at` datetime NOT NULL,  
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  -- Primary only on record_status_id
  PRIMARY KEY (`record_status_id`),
  -- Additional UNIQUE index on status
  UNIQUE KEY (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

关于设计 -- 消除 record_status_id...

在不知道您的应用程序的其余部分当前如何使用 record_status_id 的情况下,我无法确定您的应用程序代码是否需要它。但是,如果您希望其他 table 可以轻松使用实际的 status 值,而它只是 CHAR(6),您实际上可能不需要 record_status_id 作为一个整数值。毕竟,如果 status 字符串是唯一的,那么它完全可以单独用作 PRIMARY KEY,而无需任何自动递增整数键。

在这种情况下,您的 record_status table 将如下所示,并且您的 FOREIGN KEY 约束将正确添加到 users.

CREATE TABLE `record_status` (
  -- Remove the auto_increment column!!
  `status` char(6) NOT NULL,  
  `status_description` varchar(15) NOT NULL,  
  `created_at` datetime NOT NULL,  
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  
  -- Status is unique, and therefore can be the PK on its own
  PRIMARY KEY (`status`)  
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

鉴于此设置,here's a sample 显示成功创建了 table 并添加了 FK 约束。

您询问了将状态 FK 添加到其他 table 的性能影响。在不知道目的的情况下很难推测这一点,但如果其他 table 共享相同的 status 值,那么将它们的 FK 约束创建到 link 是有意义的假设您正在使用 users。如果是这种情况,我建议以相同的方式进行操作,其中 status 列为 CHAR(6)(或考虑将其中的 all 更改为 VARCHAR(6)). record_status.status 的值作为真正的主键仍然有意义,并且可以根据需要在尽可能多的相关 table 中用作外键。

除了最庞大的规模之外,使用 INT 值和 CHAR(6)/VARCHAR(6) 值作为外键之间应该没有明显的性能差异。而且它们之间的存储大小差异同样很小。除非您必须将其扩展到非常大的比例,否则无需担心。