MySQL: 创建一个没有索引的外键

MySQL: Create a Foreign key without an Index

在MySQL 5.6.34 中是否可以使用没有索引的外键?我想要它是因为我在 20M 行中创建了一个可为空的列,其中包含另一个 table 的外键。由于这是一项新功能,只有新行可能会在该列中填充实际值,而且正如您所料,该索引的基数变得很糟糕。因此,在大多数情况下,使用该索引实际上是一个坏主意。问题:我有大量查询具有相同的限制:

[...] from large_table where tenant_id = ? and nullable_foreign_key_with_index is null and [...]

问题? MySQL 认为使用 index_merge/intersect 策略 进行查询解析是个好主意。在这种情况下,MySQL 将并行执行 2 个查询:一个使用 tenant_id(使用有效且良好的索引),另一个使用 nullable_foreign_key_with_index,这是不好的,几乎是 "full table scan in parallel" 鉴于此索引的基数在 table 中 >20M 行是 <1000here

中有关此 "problem" 的更多详细信息

那么,有哪些可能的解决方案?鉴于 MySQL "forces" 一个附加索引的外键:

  1. 删除外键和索引。这很糟糕,因为在应用程序出现错误的情况下,我们可能会损害参照完整性。

  2. FOREIGN_KEY_CHECKS=0;下降指数; FOREIGN_KEY_CHECKS=1. 这很糟糕,因为即使外键仍然存在,MySQL 也不再验证该列以检查该值是否确实存在。这是一个错误吗?

  3. 在所有现有查询中使用查询提示以确保我们只使用旧的和高效的 "tenant_id_index"。这很糟糕,因为我必须搜索所有现有查询,并且还记得在构建新闻查询时再次使用它。

所以,我怎么说:"MySQL, don't bother creating an index for this foreign key, but keep validating it's content in the related table, which is indexed by primary key anyway"。我错过了什么吗?到目前为止最好的想法是删除外键并相信该应用程序按预期工作,可能是这样,但这将开始关于在 APP 与 DATABASE 中具有约束的经典讨论。有什么想法吗?

对于此查询:

from large_table
where tenant_id = ? and
      nullable_foreign_key_with_index is null and [...]

只需添加索引large_table(tenant_id, nullable_foreign_key_with_index)

MySQL 应将此索引用于 table。

我很确定你可以倒着做(我 100% 确定比较的对象不是 NULL,但我很确定 MySQL 做对了NULL 也一样。)

large_table(nullable_foreign_key_with_index, tenant_id)

并且 MySQL 将识别此索引适用于外键而不创建任何其他索引。

问:我怎么说:"MySQL, don't bother creating an index for this foreign key, but keep validating it's content in the related table, which is indexed by primary key anyway"

答:不行。 InnoDB 需要一个 suitable 索引来支持外键约束的实施。

考虑它的另一面...如果我们要删除父行中的一行 table,那么 InnoDB 需要检查外键约束。

这意味着 InnoDB 需要检查子 table 的内容,以查找在外键列中具有特定值的行。本质上等同于

SELECT ... FROM child_table c WHERE c.foreign_key_col = ? 

为此,InnoDB 要求 child_table 上有一个以 foreign_key_col 作为前导列的索引。

问题中建议的选项(禁用或删除外键)将起作用,因为 InnoDB 不会强制执行外键。但正如问题中所指出的,这意味着外键没有被强制执行。这违背了外键的目的。应用程序代码可以负责实施参照完整性,或者我们可以编写一些 ug-gghhh-ly 触发器(不,我们不想去那里)。


正如 Gordon 在他的(一如既往的出色)回答中所指出的那样...问题并不是真正删除外键列上的索引。 实际问题是低效的执行计划。最可能的解决方法是确保有一个更合适的索引table可用。

复合索引是必经之路。像这样的索引:

... ON child_table (foreign_key_col,tenant_id,...)

满足外键要求,以外键列为前导列的索引。并将(现在多余的)索引仅放在单例 foreign_key_col.

该索引还可用于满足使用可怕的索引合并访问计划的查询。 (用 EXPLAIN 验证。)

此外,考虑向以 tenant_id 作为前导列的索引添加列(例如 foreign_key_col)

... ON child_table (tenant_id,...,foreign_key_col,...)

并在单例 tenant_id col.

上删除冗余索引

总结:几乎总是有一个复合索引比依赖"index merge intersect"更好。

如果两个列都使用 =(或 IS NULL)进行测试,则列在索引定义中的顺序无关紧要。也就是说,基数是无关紧要的。