优化通过交集使用索引合并的查询

问题描述

我有一个 MysqL 8 数据库accounts,其中包含以下列:

  • id(主要)
  • city_id(外键)
  • province_id(外键)
  • country_id(外键)
  • school_id(外键)
  • 年龄(已编入索引)

编辑:完整表格结构见底部

现在,想象以下 sql 查询

SELECT
    COUNT(`id`) AS AGGREGATE
FROM
    `accounts`
WHERE
    `city_id` = 1
AND 
    `country_id` = 7
AND 
    `age` = 3

在 100 万条记录时,此查询变慢(约 200 毫秒)。

运行 EXPLAIN 时,我收到以下输出

id 选择类型 分区 类型 possible_keys key_len 参考 过滤 额外
1 简单 帐户 NULL index_merge accounts_city_id_foreign accounts_country_id_foreign accounts_age_index accounts_city_id_foreign accounts_country_id_foreign accounts_age_index 9,2,9 NULL 15542 100.00 使用 intersect(accounts_city_id_foreign,accounts_country_id_foreign,accounts_age_index);使用哪里;使用索引

鉴于 MysqL 似乎正在使用索引,我不确定我能做些什么来缩短执行时间。有人有什么想法吗?

编辑:将来,该表将包含更多列,这将导致无法使用复合索引,因为它将超过 16 列的限制。

编辑:这是完整的表结构:

CREATE TABLE `accounts` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,`city_id` bigint unsigned DEFAULT NULL,`school_id` bigint unsigned DEFAULT NULL,`country_id` bigint unsigned DEFAULT NULL,`province_id` bigint unsigned DEFAULT NULL,`age` tinyint unsigned DEFAULT NULL,PRIMARY KEY (`id`),KEY `accounts_city_id_foreign` (`city_id`),KEY `accounts_school_id_foreign` (`school_id`),KEY `accounts_country_id_foreign` (`country_id`),KEY `accounts_province_id_foreign` (`province_id`),KEY `accounts_age_index` (`age`),CONSTRAINT `accounts_city_id_foreign` FOREIGN KEY (`city_id`) REFERENCES `cities` (`id`) ON DELETE SET NULL,CONSTRAINT `accounts_country_id_foreign` FOREIGN KEY (`country_id`) REFERENCES `countries` (`id`) ON DELETE SET NULL,CONSTRAINT `accounts_province_id_foreign` FOREIGN KEY (`province_id`) REFERENCES `provinces` (`id`) ON DELETE SET NULL,CONSTRAINT `accounts_school_id_foreign` FOREIGN KEY (`school_id`) REFERENCES `schools` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=1000002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

解决方法

尝试在所有三列上创建一个复合索引,例如CREATE INDEX idx_city_country_age ON table (city_id,country_id,age)

,

索引是为了帮助您进行查询。因此,正如 Marko 所建议并得到其他人同意的那样,在 (city_id,age) 上建立索引应该会有很大帮助。现在,是的,您将向表中添加其他列,但是您是否尝试根据 16 个以上的条件进行过滤?我对此表示怀疑。在您将运行的查询中,即使您有多个复合索引来帮助优化这些查询,在任何时候您可能需要多少列? 4、5、6?在那之后,我的意思是你打算如何细化你的数据。国家、州/省、城市、城镇、村庄、社区、街道、房屋?当您的数据如此之低时,您无论如何都会处于页面级别的数据,不是吗?

因此,您对 Country = 7 的查询已经削减了大量内容。然后去那个国家的某个城市?太好了,现在您处于有限级别。

如果您确实要对需要任何聚合的大数据进行查询,并且数据从历史角度来看是相当固定的,那么按一些常见元素预先聚合表可能会有所帮助。

反馈

查询的性能不一定是你会被击中的地方,它会在插入、更新、删除中,因为任何可能的变化都必须更新表上的所有索引 - 单个或复合。如果索引中的列超过 5 列,问问自己,真的吗???优化索引所需的粒度。使用适当的索引查询数据应该非常快。更新索引也很快,但如果您在一个月、一个季度、一年内处理数百万次插入呢?用户做他们的可能会有轻微的延迟(1/4 秒?)但加起来一百万秒开始延迟。但是,无论如何,插入/更新/删除将在多长时间内完成。

,

您询问什么会缩短查询时间,而使用复合索引可以做到这一点。搜索单个复合索引比搜索多个单列索引并对结果执行交集合并要快。

您评论说您将来会添加更多列,最终将超过 16 列。

您不必将所有列都添加到复合索引中!

索引设计并不神奇。它遵循规则。您将创建旨在支持您需要运行的特定查询的索引。除非它们有助于给定的查询,否则不要向索引添加添加列。表中可能有多个复合索引,用于帮助不同的查询。

您可能会喜欢我的演示文稿 How to Design Indexes,Really(或 video)。

重新评论:

我不会提前知道所有可能的查询组合。

是的,确实如此。您只能为您知道的查询创建索引。其他查询不会被优化。如果您以后需要优化查询,您可能需要添加新索引来支持它们。

根据我的经验,这种情况经常发生,我在演示文稿中解决了这个问题。您将不时查看您的查询,因为您的应用程序代码当然会更改并且您需要的查询也会更改。您可以添加新索引,或者用不同的索引替换一个索引,或者删除不再需要的索引。