在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“强制”外键来附加索引:
删除外键和索引。这很糟糕,因为如果应用中存在错误,我们可能会损害参照完整性。
FOREIGN_KEY_CHECKS = 0;下降指数; FOREIGN_KEY_CHECKS = 1。这很糟糕,因为即使外键仍然存在,MySQL也不再对该列进行验证以检查该值是否确实存在。这是一个错误吗?
在所有现有查询中使用查询提示,以确保我们仅使用旧的高效“tenant_id_index”。这很糟糕,因为我必须查找所有现有查询,并且还记得在构建新闻查询时再次使用它。
那么,我怎么能说:“MySQL,不要为这个外键创建一个索引,而是继续在相关表中验证它的内容,无论如何都要用主键索引” 。我错过了什么吗?到目前为止,最好的想法是删除外键并且只是认为应用程序正在按预期工作,这可能就是这样,但这将开始关于在APP与DATABASE中有约束的经典讨论。有什么想法吗?
答案 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)
答:没有办法。 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
)测试两列,则列在索引定义中的顺序无关紧要。也就是说,基数是无关紧要的。