MySQL:创建没有索引的外键

时间:2018-05-03 20:43:16

标签: mysql sql indexing foreign-keys database-indexes

在MySQL 5.6.34中是否可以使用没有索引的外键?我想要这样,因为我在 20M 行中创建了一个可以为空的列,并使用外键到另一个表。由于这是一个新功能,只有新行可以使此列填充实际值,并且正如您所料,该索引的基数变得可怕。因此,在大多数情况下,使用该索引实际上是一个坏主意。问题:我有大量的查询共享同样的限制:

[...] 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这是不好的,几乎是“并行的全表扫描”在> 20M 行的表格中,此索引的基数为< 1000 。有关here

中此“问题”的更多详细信息

那么,什么是可行的解决方案?鉴于MySQL“强制”外键来附加索引:

  1. 删除外键和索引。这很糟糕,因为如果应用中存在错误,我们可能会损害参照完整性。

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

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

  4. 那么,我怎么能说:“MySQL,不要为这个外键创建一个索引,而是继续在相关表中验证它的内容,无论如何都要用主键索引” 。我错过了什么吗?到目前为止,最好的想法是删除外键并且只是认为应用程序正在按预期工作,这可能就是这样,但这将开始关于在APP与DATABASE中有约束的经典讨论。有什么想法吗?

3 个答案:

答案 0 :(得分:4)

对于此查询:

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应该将此索引用于表。

我很确定你可以向后做这件事(如果比较NULL以外的其他任何事情,我会100%肯定,但我很确定MySQL在{{1}做了正确的事情也是。)

NULL

MySQL会认识到这个索引适用于外键而不会创建任何其他索引。

答案 1 :(得分:2)

问:我怎么说:" MySQL,不要为这个外键创建索引,但要继续在相关表格中验证它的内容,该表格已编入索引无论如何,通过主键"

答:没有办法。 InnoDB需要一个合适的索引来支持外键约束的执行。

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

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

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

要做到这一点,InnoDB要求child_table上有一个索引,其中foreign_key_col为前导列。

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

正如戈登在他的(通常很好的)答案中已经注意到的那样......问题并没有真正地将索引放在外键列上。 实际问题是效率低下的执行计划。最可能的解决办法是确保有更合适的索引。

复合索引是可行的方法。像这样的索引:

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

将满足外键的要求,即外键列作为前导列的索引。并将(现在冗余的)索引放在单例foreign_key_col上。

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

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

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

并删除singleton tenant_id col。

上的冗余索引

答案 2 :(得分:0)

总结:几乎总是最好有一个复合索引,而不是依赖于“索引合并相交”。

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