【问题标题】:Foreign key not working in MySQL: Why can I INSERT a value that's not in the foreign column?外键在 MySQL 中不起作用:为什么我可以插入一个不在外列中的值?
【发布时间】:2010-09-27 15:51:39
【问题描述】:

我在 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 类型的键,我不确定这意味着什么)。但是,当我尝试输入 questions 或 users 表中不存在的 Q_id 或 U_id 时,MySQL 仍然允许这些值。

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

更新 1

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

错误 1005 (HY000): 无法创建表 './quotes/actions.frm' (errno: 150)

为什么会这样?

更新 2

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

【问题讨论】:

    标签: mysql foreign-keys innodb mysql-error-1005


    【解决方案1】:

    我猜你的默认存储引擎是 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 的一个大错误。没有错误或警告存储引擎不支持它们。

    对于 CHECK 约束也是如此。顺便说一句,没有与 MySQL 一起使用的存储引擎支持 CHECK 约束,但 SQL 解析器毫无怨言地接受它们。


    当它无法创建 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 has better performance than MyISAM(尽管 InnoDB 需要更多关注 tuning the configuration),InnoDB 支持原子更改、事务、外键,并且 InnoDB 更能抵抗崩溃中的数据损坏。

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

    【讨论】:

    • 感谢您的回答——为我节省了大量时间。顺便说一句,要将默认值从 MyISAM 更改为 InnoDB,请将“default-storage-engine = innodb”添加到 [mysqld] 部分下的 my.cnf 文件中。
    • @HDave:很高兴为您提供帮助!是的,在最新版本的 MySQL 中,没有理由不将 innodb 设为默认值,而在 MySQL 5.5 中也是如此。
    • 很好的解释。
    【解决方案2】:

    只是为了节省其他人的头痛时间——正如长颈鹿所说,确保 @FOREIGN_KEY_CHECKS 设置为 1。

    选择@@FOREIGN_KEY_CHECKS

    SET FOREIGN_KEY_CHECKS=1

    【讨论】:

      【解决方案3】:

      我知道这个帖子很久以前就打开了,但我将这个消息发布给未来寻找答案的用户。 我在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;
      

      “ON update cascade”对我来说很神奇。

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

      【讨论】:

        【解决方案4】:

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

        如果你这样声明你的外键

        id  INTEGER UNSIGNED    REFERENCES    A_Table(id)
        

        然后外键似乎被忽略了,要强制执行约束而不(显然)必须使用任何 SET 命令,请使用以下声明。

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

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

        【讨论】:

          【解决方案5】:

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

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

          作者 Edwin Dando 说:

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

          【讨论】:

          • 在旧版本的 MySQL 中,您必须在声明外键约束之前手动在列上创建索引。在较新的版本中,当您声明 FK 时,会隐式(如有必要)为您创建索引。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

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

            (2) 确保通过执行“SET foreign_key_checks = 'ON'”确实检查了外键约束

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

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

            【讨论】:

              【解决方案8】:

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

              在我尝试创建外键约束的情况下,我只遇到了“无法创建表”,而我的本地列与外列的类型不同。

              【讨论】:

                【解决方案9】:

                我认为一些遇到此问题的人可能是从 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;
                

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                    猜你喜欢
                    • 2012-12-07
                    • 2011-01-02
                    • 2011-03-18
                    • 1970-01-01
                    • 2012-09-17
                    • 2019-07-29
                    • 2020-02-20
                    • 2010-11-12
                    • 1970-01-01
                    相关资源
                    最近更新 更多