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 行是 <1000。 here
中有关此 "problem" 的更多详细信息
那么,有哪些可能的解决方案?鉴于 MySQL "forces" 一个附加索引的外键:
删除外键和索引。这很糟糕,因为在应用程序出现错误的情况下,我们可能会损害参照完整性。
FOREIGN_KEY_CHECKS=0;下降指数; FOREIGN_KEY_CHECKS=1. 这很糟糕,因为即使外键仍然存在,MySQL 也不再验证该列以检查该值是否确实存在。这是一个错误吗?
在所有现有查询中使用查询提示以确保我们只使用旧的和高效的 "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
)进行测试,则列在索引定义中的顺序无关紧要。也就是说,基数是无关紧要的。
在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 行是 <1000。 here
那么,有哪些可能的解决方案?鉴于 MySQL "forces" 一个附加索引的外键:
删除外键和索引。这很糟糕,因为在应用程序出现错误的情况下,我们可能会损害参照完整性。
FOREIGN_KEY_CHECKS=0;下降指数; FOREIGN_KEY_CHECKS=1. 这很糟糕,因为即使外键仍然存在,MySQL 也不再验证该列以检查该值是否确实存在。这是一个错误吗?
在所有现有查询中使用查询提示以确保我们只使用旧的和高效的 "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
)进行测试,则列在索引定义中的顺序无关紧要。也就是说,基数是无关紧要的。