外键在 MySQL 中不起作用:为什么我可以插入不在外键列中的值?

发布于 2024-07-09 20:23:10 字数 853 浏览 13 评论 0原文

我已经在 MySQL 中创建了一个表:

CREATE TABLE actions ( A_id int NOT NULL AUTO_INCREMENT,
type ENUM('rate','report','submit','edit','delete') NOT NULL,
Q_id int NOT NULL,
U_id int NOT NULL,
date DATE NOT NULL,
time TIME NOT NULL,
rate tinyint(1),
PRIMARY KEY (A_id),
CONSTRAINT fk_Question FOREIGN KEY (Q_id) REFERENCES questions(P_id),
CONSTRAINT fk_User FOREIGN KEY (U_id) REFERENCES users(P_id));

这很好地创建了我想要的表(尽管“DESCRIBE actions;”命令显示外键是 MUL 类型的键,我不确定这意味着什么)。 但是,当我尝试输入问题或用户表中不存在的 Q_id 或 U_id 时,MySQL 仍然允许这些值。

我做错了什么? 如何防止具有外键的表接受无效数据?

更新 1

如果我将 TYPE=InnoDB 添加到末尾,则会收到错误:

错误 1005 (HY000):无法创建表“./quotes/actions.frm”(错误号:150)

为什么会发生这种情况?

UPDATE 2

我被告知使用功能外键强制数据完整性很重要,而且 InnoDB 不应该与 MySQL 一起使用。 你有什么建议吗?

I've created a table in MySQL:

CREATE TABLE actions ( A_id int NOT NULL AUTO_INCREMENT,
type ENUM('rate','report','submit','edit','delete') NOT NULL,
Q_id int NOT NULL,
U_id int NOT NULL,
date DATE NOT NULL,
time TIME NOT NULL,
rate tinyint(1),
PRIMARY KEY (A_id),
CONSTRAINT fk_Question FOREIGN KEY (Q_id) REFERENCES questions(P_id),
CONSTRAINT fk_User FOREIGN KEY (U_id) REFERENCES users(P_id));

This created the table I wanted just fine (although a "DESCRIBE actions;" command showed me that the foreign keys were keys of type MUL, and I'm not sure what this means). However, when I try to enter a Q_id or a U_id that does not exist in the questions or users tables, MySQL still allows these values.

What did I do wrong? How can I prevent a table with a foreign key from accepting invalid data?

UPDATE 1

If I add TYPE=InnoDB to the end, I get an error:

ERROR 1005 (HY000): Can't create table './quotes/actions.frm' (errno: 150)

Why might that happen?

UPDATE 2

I'm told that it's important to enforce data integrity with functional foreign keys, but also that InnoDB should not be used with MySQL. What do you recommend?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

何其悲哀 2024-07-16 20:23:10

我猜你的默认存储引擎是MyISAM,它忽略外键约束。 它默默地接受外键的声明,但不存储约束或随后强制执行它。

但是,它确实会在您为外键声明的列上隐式创建索引。 在 MySQL 中,“KEY”是“INDEX”的同义词。 这就是 DESCRIBE 输出中显示的内容:索引,但不是约束。

您现在可以向表中插入无效值,因为没有约束。 要获得强制引用完整性的约束,必须使用 InnoDB 存储引擎:

CREATE TABLE actions (
  A_id int NOT NULL AUTO_INCREMENT,
  ...
  CONSTRAINT fk_Question FOREIGN KEY (Q_id) REFERENCES questions(P_id),
  CONSTRAINT fk_User FOREIGN KEY (U_id) REFERENCES users(P_id)
) ENGINE=InnoDB;

我一直认为 MySQL 默默地忽略外键约束声明是一个很大的错误。 没有错误或警告表明存储引擎不支持它们。

MySQL 8.0.16之前的CHECK约束也是如此。 MySQL 使用的存储引擎均不支持 CHECK 约束,但 SQL 解析器毫无怨言地接受它们。 在8.0.16及更高版本中,InnoDB表确实支持CHECK约束,但其他存储引擎仍然忽略它们。


当无法创建 InnoDB 表时,会出现 errno 150 问题,因为它无法理解外键约束。 您可以通过以下方式获得更多信息:

SHOW ENGINE INNODB STATUS;

InnoDB 外键的一些要求:

  • 引用的表也必须是 InnoDB。
  • 引用的表必须有索引和主键。
  • FK 列和引用的 PK 列的 SQL 数据类型必须相同。 例如,INT 与 BIGINT 或 INT UNSIGNED 不匹配。

您可以更改其中包含数据的表的存储引擎:

ALTER TABLE actions ENGINE=InnoDB;

这会有效地将整个 MyISAM 表复制到 InnoDB 表,然后一旦成功,它就会删除 MyISAM 表并将新的 InnoDB 表重命名为以前的 MyISAM 表的名称。 这称为“表重组”,它可能非常耗时,具体取决于表中的数据量。 表重组发生在 ALTER TABLE 期间,即使在某些情况下似乎没有必要。


回复您的更新2:

有人告诉我,使用功能外键强制数据完整性很重要,但 InnoDB 不应该与 MySQL 一起使用。 你有什么推荐?

谁告诉你的? 这绝对是假的。 InnoDB 的性能优于MyISAM(尽管InnoDB需要更多关注调整配置),InnoDB 支持原子更改、事务、外键,并且 InnoDB 在崩溃时更能抵抗数据损坏。

除非您运行的是旧的、不受支持的 MySQL 版本(5.0 或更早版本),否则您应该使用 InnoDB 作为您的默认存储引擎选择,并且仅当您可以演示受益于 MyISAM 的特定工作负载时才使用 MyISAM 。

I would guess that your default storage engine is MyISAM, which ignores foreign key constraints. It silently accepts the declaration of a foreign key, but does not store the constraint or enforce it subsequently.

However, it does implicitly create an index on the columns you declared for the foreign key. In MySQL, "KEY" is a synonym for "INDEX". That's what's being shown in the DESCRIBE output: an index, but not a constraint.

You are able to insert invalid values to the table right now because there is no constraint. To get a constraint that enforces referential integrity, you must use the InnoDB storage engine:

CREATE TABLE actions (
  A_id int NOT NULL AUTO_INCREMENT,
  ...
  CONSTRAINT fk_Question FOREIGN KEY (Q_id) REFERENCES questions(P_id),
  CONSTRAINT fk_User FOREIGN KEY (U_id) REFERENCES users(P_id)
) ENGINE=InnoDB;

I've always thought it was a big mistake on MySQL's part to silently ignore foreign key constraint declarations. There's no error or warning that the storage engine doesn't support them.

The same is true for CHECK constraints before MySQL 8.0.16. No storage engine used with MySQL supported CHECK constraints, but the SQL parser accepts them with no complaint. In 8.0.16 and later, InnoDB tables do support CHECK constraints but other storage engines still ignore them.


The errno 150 issue occurs when it cannot create the InnoDB table, because it couldn't make sense of the foreign key constraint. You can get some more information with:

SHOW ENGINE INNODB STATUS;

Some requirements for InnoDB foreign keys:

  • Referenced table must also be InnoDB.
  • Referenced table must have an index and a primary key.
  • SQL data types of FK column and referenced PK column must be identical. For example, INT does not match BIGINT or INT UNSIGNED.

You can change the storage engine of a table that has data in it:

ALTER TABLE actions ENGINE=InnoDB;

This effectively copies the entire MyISAM table to an InnoDB table, then once that succeeds it drops the MyISAM table and renames the new InnoDB table to the name of the former MyISAM table. This is called a "table restructure" and it can be time-consuming, depending on how much data is in the table. A table restructure occurs during ALTER TABLE, even in some cases where it may seem unnecessary.


Re your update 2:

I'm told that it's important to enforce data integrity with functional foreign keys, but also that InnoDB should not be used with MySQL. What do you recommend?

Who told you that? It's absolutely false. InnoDB has better performance than MyISAM (though InnoDB needs more attention to tuning the configuration), InnoDB supports atomic changes, transactions, foreign keys, and InnoDB is much more resistant to corrupting data in a crash.

Unless you're running an old, unsupported version of MySQL (5.0 or earlier) you should use InnoDB as your default storage engine choice, and use MyISAM only if you can demonstrate a specific workload that benefits from MyISAM.

对岸观火 2024-07-16 20:23:10

只是为了避免我经历过的头痛 - 正如 giraffa 所提到的,确保 @FOREIGN_KEY_CHECKS 设置为 1。

SELECT @@FOREIGN_KEY_CHECKS

SET FOREIGN_KEY_CHECKS=1

Just to save other's of the hours of headache I've been thru - as giraffa touches upon, ensure @FOREIGN_KEY_CHECKS is set to 1.

SELECT @@FOREIGN_KEY_CHECKS

SET FOREIGN_KEY_CHECKS=1

夜巴黎 2024-07-16 20:23:10

我知道这个线程很久以前就打开了,但我发布此消息是为了将来寻找答案的用户。
我在 mysql 中的外键上遇到了同样的问题。
以下事情对我有用。

父表:

CREATE TABLE NameSubject (
  Autonumber INT NOT NULL AUTO_INCREMENT,
  NameorSubject nvarchar(255),
  PRIMARY KEY (Autonumber)
 ) ENGINE=InnoDB;

子表:

CREATE TABLE Volumes (
  Autonumber INT NOT NULL,
  Volume INT,
  Pages nvarchar(50),
  Reel int,
  Illustrations bit,
  SSMA_TimeStamp timestamp,
  Foreign KEY (Autonumber) references NameSubject(Autonumber)
  ON  update cascade 
)engine=innodb;

“更新级联”对我来说很神奇。

我希望这对其他人有用。 祝你好运。

I know this thread was opened long time ago, but I am posting this message for future users who will look for the answer.
I was having the same problem with foreign key in mysql.
The following thing worked for me.

Parent table:

CREATE TABLE NameSubject (
  Autonumber INT NOT NULL AUTO_INCREMENT,
  NameorSubject nvarchar(255),
  PRIMARY KEY (Autonumber)
 ) ENGINE=InnoDB;

Child Table:

CREATE TABLE Volumes (
  Autonumber INT NOT NULL,
  Volume INT,
  Pages nvarchar(50),
  Reel int,
  Illustrations bit,
  SSMA_TimeStamp timestamp,
  Foreign KEY (Autonumber) references NameSubject(Autonumber)
  ON  update cascade 
)engine=innodb;

"ON update cascade" did the magic for me.

I hope this works for others. Best of luck.

诗酒趁年少 2024-07-16 20:23:10

对于那些仍然遇到 mysql 忽略外键约束问题的人,以及那些上面的答案或任何其他相关问题没有解决他们的难题的人,这就是我发现的问题。

如果您这样声明外键,

id  INTEGER UNSIGNED    REFERENCES    A_Table(id)

那么外键似乎会被忽略,要强制执行约束而无需(显然)使用任何 SET 命令,请使用以下声明。

id  INTEGER UNSIGNED,
CONSTRAINT fk_id  FOREIGN KEY (id)  REFERENCES A_Table(id)

这种方式为我解决了问题。 不知道为什么,正如许多人所说,第一个声明只是第二个变体的简写。

For those who still have problems with mysql ignoring foreign keys constraints and for those who the answers above or in any other related question didn't solve teir puzzle, here is what I found to be the issue.

If you declare your foreign keys as such

id  INTEGER UNSIGNED    REFERENCES    A_Table(id)

Then the foreign key seems to be ignored, to enforce the constraint without (apparently) having to use any of the SET commands, use the following declaration.

id  INTEGER UNSIGNED,
CONSTRAINT fk_id  FOREIGN KEY (id)  REFERENCES A_Table(id)

This way solved the problem for me. Not sure why, as many say the first declaration is only a shorthand to the second variant.

凉城 2024-07-16 20:23:10

有同样的问题。 我的案例的解决方案是将存储引擎从 MyISAM 更改为 InnoDB,之后它就完美运行了。 对于我作为一个非 mysql 专业人士来说,这似乎很愚蠢,但也许更高级的程序员知道这种行为的原因。

Had the same problem. The solution in my case was changing the Storage Engine from MyISAM to InnoDB and after that it worked perfectly. For me as a non-mysql-professional it seems stupid but maybe the more advanced programmers know the reason for this behaviour.

离线来电— 2024-07-16 20:23:10

我找到了下面的文章。 目前我没有时间测试它,但它可能会有所帮助:

http://forums.mysql.com/read.php?22,19755,43805

作者 Edwin Dando 说:

两个表都必须是 INNODB。 外键字段必须有索引。 外部键字段和被引用的字段必须具有相同的类型(我只使用整数),并且经过几个小时的痛苦,它们必须是无符号的。

I found the following article. I don't have time to test it out, currently, but it may be helpful:

http://forums.mysql.com/read.php?22,19755,43805

The author,Edwin Dando, says:

both tables must be INNODB. The foreign key field must have an index on it. The foeign key field and the field being referenced must be of the same type (I only use integer) and, after hours of pain, they must be UNSIGNED.

谁对谁错谁最难过 2024-07-16 20:23:10

问题很可能是 questions.p_id 和 users.p_id 没有定义为 INT NOT NULL。 为了使外键发挥作用,外键两侧的列定义必须完全匹配,但 auto_increment 和 default 除外。

the problem is most likely that questions.p_id and users.p_id are not defined as INT NOT NULL. for foreign keys to work, the definition of the columns on both side of the foreign key must match exactly, with the exception of auto_increment and default.

古镇旧梦 2024-07-16 20:23:10

如果我先看到这个答案,我会节省很多时间。 尝试以下三个步骤,我按照新手错误的频率排序:

(1) 通过将“ENGINE=InnoDB”附加到“CREATE TABLE”语句中,将表更改为 InnoDB。

其他引擎(可能是默认引擎)不支持外键约束,但它们也不会抛出错误或警告告诉您它们不受支持。

(2) 通过执行“SETforeign_key_checks = 'ON'”确保实际上正在检查外键约束。

(3) 将“ON UPDATE CASCADE”附加到外键声明中。

注意:确保级联是您想要的行为。 还有其他选择...

This answer would have saved me a lot of time if I'd seen it first. Try the following three steps, which I've ordered by frequency of newbie mistakes:

(1) Change the table to be InnodDB by appending "ENGINE=InnoDB" to your "CREATE TABLE" statements.

Other engines, which may be the default, do not support foreign key constraints, but neither do they throw an error or warning telling you they're not supported.

(2) Make sure foreign key constraints are in fact being checked by executing "SET foreign_key_checks = 'ON'"

(3) Append "ON UPDATE CASCADE" to your foreign key declaration.

Note: Make sure that cascading is the behavior you want. There are other options...

酒浓于脸红 2024-07-16 20:23:10

如前所述,您的表必须是 InnoDB 才能强制执行 FK 约束。

我仅在尝试创建外键约束(其中本地列与外部列的类型不同)的情况下遇到“无法创建表”。

As noted, your table have to be InnoDB for FK constraints to be enforced.

I've only run into the 'Can't create table' in the case where I'm trying to create a foreign key constraint where my local column is a different type from the foreign column.

调妓 2024-07-16 20:23:10

我认为一些遇到此问题的人可能会从 ORACLE 网站上提供的 MYSQL 示例数据库(例如 sakila DB)开始。 不要忘记在脚本末尾“重新打开外键约束”(例如,在 sakila DB 脚本的开头,它们被关闭)

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

在此处创建表,

然后不要忘记这一点:

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

I think some of the folks having this problem might be starting out with some of the sample databases provided on the ORACLE website for MYSQL (e.g. sakila DB). Don't forget to "turn the foreign key constraints back on" at the end of your script (e.g. at the beginning of sakila DB script they are turned OFF)

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

create your tables here

then don't forget this:

SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
黑白记忆 2024-07-16 20:23:10

好吧,我的猜测是“跳过创建 FORIEN KEYS”选项被选中,它可能发生在“正向工程”过程的“选项”部分。

正向工程 - 选项部分

Well, my guess is somehow the "Skip creation of FORIEN KEYS" option is checked, it can happen in the "options" section of the "Forward Engineering" process.

Forward Engineering - Options section

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文