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

Optimising a query that uses index merge by intersection

我有一个 MySQL 8 数据库 table accounts 有以下列:

编辑:完整的table结构见底部。

现在,想象以下 SQL 查询:

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

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

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

id select_type table 分区 类型 possible_keys 关键 key_len 参考 过滤 额外
1 简单 帐户 index_merge 账户_city_id_外国账户_country_id_外国accounts_age_index 账户_city_id_外国账户_country_id_外国accounts_age_index 9,2,9 15542 100.00 使用相交(accounts_city_id_foreign,accounts_country_id_foreign,accounts_age_index);在哪里使用;使用索引

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

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

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

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, country_id, age) 上建立索引应该会有很大帮助。现在,是的,您将向 table 添加其他列,但您是否尝试过滤超过 16 个条件???我对此表示怀疑。在查询中,您将 运行,即使您有多个复合索引来帮助优化这些查询,您在任何时候可能需要多少列? 4、5、6?在那之后,我的意思是您计划获取数据的粒度如何。国家/地区、State/Province、城市、城镇、村庄、街区、街道、房屋?当您的数据量如此之低时,您无论如何都会处于页面级数据,不是吗?

所以,您对 Country = 7 的查询已经砍掉了很多东西。然后到那个国家的某个特定城市?太好了,现在你在一个有限的层次上。

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

反馈

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

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

您评论说以后会添加更多栏目,最终会超过16栏目。

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

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

您可能会喜欢我的介绍How to Design Indexes, Really (or the video)。

回复您的评论:

I won't know every possible query combination ahead of time.

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

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