优化使用按交集合并索引的查询
Optimising a query that uses index merge by intersection
我有一个 MySQL 8 数据库 table accounts
有以下列:
- id(主要)
- city_id(外键)
- province_id(外键)
- country_id(外键)
- school_id(外键)
- 年龄(索引)
编辑:完整的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.
是的,没错。您只能为您知道的查询创建索引。不会优化其他查询。如果您以后需要优化查询,您可能需要添加新的索引来支持它们。
根据我的经验,这种情况经常发生,我在演示文稿中解决了这个问题。您会不时查看您的查询,因为您的应用程序代码当然会更改,您需要的查询也会更改。您可以添加新索引,或者用不同的索引替换索引,或者删除不再需要的索引。
我有一个 MySQL 8 数据库 table accounts
有以下列:
- id(主要)
- city_id(外键)
- province_id(外键)
- country_id(外键)
- school_id(外键)
- 年龄(索引)
编辑:完整的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.
是的,没错。您只能为您知道的查询创建索引。不会优化其他查询。如果您以后需要优化查询,您可能需要添加新的索引来支持它们。
根据我的经验,这种情况经常发生,我在演示文稿中解决了这个问题。您会不时查看您的查询,因为您的应用程序代码当然会更改,您需要的查询也会更改。您可以添加新索引,或者用不同的索引替换索引,或者删除不再需要的索引。