使用 mysql 工作台创建 CHAR 类型的外键时出错:错误 1005:无法创建表(错误号:150)

Posted

技术标签:

【中文标题】使用 mysql 工作台创建 CHAR 类型的外键时出错:错误 1005:无法创建表(错误号:150)【英文标题】:Error when creating foreign key of type CHAR with mysql workbench: Error 1005: Can't create table (errno: 150) 【发布时间】:2015-11-30 21:12:23 【问题描述】:

我定义了以下 2 个表:

记录状态

显示创建表记录状态

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

用户

显示创建表用户

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.

问题 我的目的是让 status 列清楚地显示每个用户的当前状态(ACTIVE、INACTV、DELETD),同时仍然可以灵活地使用 record_status_id 将 record_status 表与用户表连接起来,以便更好地查找具有给定状态的任何行性能。

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

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

我还打算将状态的外键添加到其他几个表中。我只想知道这会如何影响性能,还是会增加任何限制?

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

【问题讨论】:

如果您为我们发布实际的CREATE 声明[最好是未删节的SHOW CREATE 输出],我们可以看看我们是否可以在我们自己的系统上重新创建和解决问题。 我已经编辑了我的问题以包含 SHOW CREATE 输出详细信息。谢谢! 【参考方案1】:

您面临的问题实际上与排序规则无关(尽管排序规则可能是您在不同情况下遇到的错误的原因)。

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

添加FOREIGN KEY 约束会隐式在引用表上创建必要的索引,但您仍必须确保在被引用表上具有相应的索引。

因此,鉴于您现在所拥有的,如果您在 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 的情况下,我无法确定您的应用程序代码是否需要它。但是,如果您希望将实际的status 值轻松地提供给其他表,并且它只是CHAR(6),那么您实际上可能不需要将record_status_id 作为整数值。毕竟,如果status 字符串是唯一的,那么它完全可以单独充当PRIMARY KEY,而无需任何自增整数键。

在这种情况下,您的record_status 表如下所示,并且您的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 显示成功创建表并添加了 FK 约束。

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

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

【讨论】:

以上是关于使用 mysql 工作台创建 CHAR 类型的外键时出错:错误 1005:无法创建表(错误号:150)的主要内容,如果未能解决你的问题,请参考以下文章

如何修复 MySQL 中的外键错误?

使用char ID删除后的Mysql触发器

MySQL的外键,修改表,基本数据类型,表级别操作,其他(条件,通配符,分页,排序,分组,联合,连表操作)

此表中的外键约束有啥问题?

JDBCTemplate + MySQL 主键到外键

( 10 )MySQL中的外键