【问题标题】:Consequences of removing index used for foreign keys in MySQL在 MySQL 中删除用于外键的索引的后果
【发布时间】:2018-04-15 10:32:05
【问题描述】:

似乎可以删除为 MySQL 5.5 中的外键创建的索引,使用如下所示的小“技巧”​​:

mysql > create table commands (
  id int primary key auto_increment, name  varchar(255));
mysql > create table data (
        dim_command int, cnt int NOT NULL, 
        CONSTRAINT FOREIGN KEY (dim_command) references commands(id));

现在创建了一个无法删除的索引:

mysql > show create table data;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                          |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| data  | CREATE TABLE `data` (
  `dim_command` int(11) DEFAULT NULL,
  `cnt` int(11) NOT NULL,
  KEY `dim_command` (`dim_command`),
  CONSTRAINT `data_ibfk_1` FOREIGN KEY (`dim_command`) REFERENCES `commands` (`id`)
) ENGINE=InnoDB

mysql > alter table data drop index dim_command;
ERROR 1553 (HY000): Cannot drop index 'dim_command': needed in a foreign key constraint

但它可以被欺骗移除:

mysql > set foreign_key_checks=Off;
Query OK, 0 rows affected (0.00 sec)
mysql > alter table data drop index dim_command;
Query OK, 0 rows affected (0.51 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql > set foreign_key_checks=On;
Query OK, 0 rows affected (0.00 sec)

此时:

  • data 表仍然具有外键约束规范(通过执行例如 show create table data 来显示)
  • 但似乎没有强制执行该约束,即可以 将行插入到引用不存在的行的data 表中 commands 表。

我的问题是,以这种方式在 InnoDB 表上删除用于外键约束的索引是否还有其他后果?

(背景是我有一个数据仓库,其中有人将外键添加到包含数亿行的事实表中,该表引用了只有少数行的其他表 - 使此类列上的索引对查询无用性能,同时占用 大量 磁盘空间并严重影响插入性能。完整性不太受关注,由数据仓库中的 ETL 过程强制执行 - 但保持外键约束对于文档和 3. 派对可视化工具)

【问题讨论】:

  • 几年前,我遇到过这样一种情况,我们在关闭外键检查的情况下对表进行了一些结构性工作,结果导致表陷入了未完成的状态表现如预期。 mysql 守护程序之类的东西无法重新启动,或者无法删除并重新创建表......,抱歉,我记不清了。无论如何,我只想说,你正在徘徊在我相信 mysql 开发人员无意让你旅行的水域中。如果您从不查询这些列,我只需删除外键并为文档添加列注释。
  • 呼应 Jeff Richards... 我的建议是 DROP 外键约束。对于文档,请添加列注释。对于我们没有定义外键的事实表,对于列注释,我们使用单词“ref”,后跟引用的 table_name.column_name,例如COMMENT 'ref customer.id'。 (你问了一个有趣的问题,但这个问题我永远不想知道答案。我永远不想在薄冰的湖里那么远。
  • 哦,FK 的缺点。人们仍然使用它们是一个奇迹。 DROP 约束。处理应用程序中的完整性。

标签: mysql indexing foreign-keys


【解决方案1】:

如果不强制执行 FK,其中一个缺点与查询性能无关。

如果您可以将不存在于commands 中其引用的PK 列中的值插入FK 列,那么您将不知道您的数据是否有用。它可能包含一堆损坏的引用,您无法阻止它们发生。

这就是外键约束的目的——它是为了保持数据的完整性。

【讨论】:

    【解决方案2】:

    这似乎是错误 #16896810。显然这是为5.7.2 (2013-09-21, Milestone 12) 修复的——在5.7.9 (2015-10-21, General Availability) 之后。它也在MySQL 5.6.14 (2013-09-20, General Availability)中修复了

    • InnoDB:删除具有多个索引的列上的所有索引时,当外键约束需要索引时,InnoDB 无法阻止 DROP INDEX 操作。 (错误号 16896810)

    这并没有明确说明该修复适用于foreign_key_checks=OFF,但DROP 不允许在foreign_key_checks=ON 时使用,因此推测OFF 是他们正在修复的情况。

    尽管允许DROP INDEX,但重新启动服务器correctly gives a missing foreign key index error。因此,利用该错误不会让您处于非常有用的状态。当然,您可以自动删除它;但是由于这是一个错误,因此您不能依赖该行为,并且您不应该期望/猜测您的系统在它下的行为方式,无论其他问题是记录/观察到还是未记录/观察到。无论如何,为了完整性,您应该通过 DBMS 以一种或另一种方式强制执行与该外键声明相关的约束——不要强制执行或依赖应用程序。

    (与往常一样,Minimal, Complete, and Verifiable Example 会有所帮助。)

    【讨论】:

    • 好的,谢谢。 (但是除了显示我正在为 MCVE 做什么的 SQL 语句之外,我还应该提供什么?)
    • 我在 cmets 和每个 MCVE 的近距离投票中解决的方面是,您使用的 MySQL 的确切版本以及 DDL 和 DML 表明“约束似乎没有被强制执行”。 (在你有机会解决我的评论和近距离投票之前,我碰巧调查了这个问题。示例表很有帮助,但不可执行。)这里的测试代码很简单,但很多人不提供 MCVE,我只是不想写。但是,创建 MCVE 也是在被问及之前调试和排除问题的一个不可或缺的步骤。
    • PS 我通过谷歌搜索“mysql 5.7“删除索引”允许外键(没有或没有)索引错误'之类的东西来回答这个问题。我经常评论“谷歌对您的问题/问题/desiderata的许多清晰、简洁、具体的陈述,有&没有标签/关键字,有&没有你的应用程序特定名称,&阅读很多点击;如果你仍然找不到一个答案,然后发布一个以一次搜索为标题的问题”,因为这通常是人们所需要的。 How to Ask(显然 Oracle 并未将所有错误都放入 MySQL 错误数据库中。您可以通过 Amazon、MariaDB 等找到更多信息)
    • 好的,我可以看到mysql版本很重要,我使用的是v 5.5,但是我问题中的7个SQL语句是我从机器上复制粘贴的逐字SQL,用于演示我的问题。 (我现在明白这在 MySQL 的每个版本中都不是可执行的,但它在我拥有的服务器上是可重复的)。 (请注意,我只是在问未来,所以我可以提出更好的问题,但在这种情况下,我看不出我能提供更好的例子)
    • 我可以编辑提示,但我需要编写插入值的代码并尝试更新以进行测试。 (正如我所说,“简单”、“但是”。) PS 给出完整的版本号会有所帮助,例如 5.7.2——正如您在上面看到的那样——还有分发号以及客户端和服务器版本可能相关的数字,请参阅 mysql -vSHOW VARIABLES LIKE "%version%"
    猜你喜欢
    • 2012-01-18
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 2018-10-10
    • 2010-12-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多