MySQL:用 IN(...) 的索引强制不同的 access_type
MySQL: Force different access_type with an index for IN(...)
我正在尝试调整一个非常简单的查询:
select * from log where user_id in (...) order by id desc limit 25
我只想显示一组不同用户 ID(大约 40 个 ID)的最后 25 个事件。此查询大约需要 50 秒 运行(table 中有超过 8000 万条记录)。
通过执行 EXPLAIN format=json
我可以看到 access_type
是 range
。经过一番探索,A 了解到,如果我将 ID 的数量更改为 9,查询计划器将使用另一种访问方式:index
.
所以我假设对于大量 ID MySQL 将在组的较小和较大 ID 之间进行范围扫描,如果 ID 是 'close',这可能有意义,但情况并非总是如此。也许不知何故,这种额外的数据量在进行排序时会成为一个问题(如下面的解释计划所示)。
40个ID说明
{
"query_block": {
"select_id": 1,
"ordering_operation": {
"using_filesort": true,
"table": {
"table_name": "log",
"access_type": "range",
"possible_keys": [
"app_log_user_id"
],
"key": "log_user_id",
"used_key_parts": [
"user_id"
],
"key_length": "4",
"rows": 6150,
"filtered": 100,
"index_condition": "(`app`.`log`.`user_id` in (<43 different ids from 12000 to 330000>))"
}
}
}
}
9个ID说明
{
"query_block": {
"select_id": 1,
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "log",
"access_type": "index",
"possible_keys": [
"app_log_user_id"
],
"key": "PRIMARY",
"used_key_parts": [
"id"
],
"key_length": "4",
"rows": 6901,
"filtered": 4552.8,
"attached_condition": "(`app`.`log`.`user_id` in (< 9 ids from 12000 to 18000))"
}
}
}
}
我做了一个实验:我将该查询分成 5 个只有 9 个或更少 ID 的子查询,并对所有子查询应用 UNION
,最后以 ORDER 和 LIMIT 子句结束。这个查询的查询计划变得有点混乱,即使有奇怪的值表明其中一个子查询的搜索行数将是 86737713(我认为这是一个非常错误的估计,所有其他的都在 10246 左右)。你猜怎么着?查询用了 "only" 6 秒,比 50 秒好。
我不知道使用了哪些策略来优化这种查询,但据我所知,如果我可以告诉优化器使用 index
的 acess_type
来代替range
,它会表现得更好。这可能吗?
额外详情
user_id
有一个外键和一个索引。
- 我们使用MySQL 5.6 (InnoDB)
- Table 大约有 80kk 行。
显示创建 TABLE
CREATE TABLE `app_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`user_id` int(11) NOT NULL,
`content_type_id` int(11) NOT NULL,
`object_id` int(10) unsigned NOT NULL,
`status` int(11) DEFAULT NULL,
`type` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `app_log_content_type_id` (`content_type_id`),
KEY `app_log_144dd2a9` (`timestamp`),
KEY `app_log_user_id` (`user_id`, `id`)
)
ENGINE = InnoDB
AUTO_INCREMENT = 108628300
DEFAULT CHARSET = latin1
可能的解释:
您使用的 MySQL/MariaDB 是什么版本?我猜您使用的是 MySQL 5.6? (您使用 FORMAT=JSON
确认 "at least 5.6.5"。)
- 5.6.5引入
eq_range_index_dive_limit
,默认为10.
- 5.7.4
eq_range_index_dive_limit
默认值提高到 200 - 影响 IN()
可能的解决方法:
此注释可能 解释了 IN
列表中的第 9 项与第 43 项。建议你玩 eq_range_index_dive_limit
.
知识问答
KK = 千千
M、对会计师='mille'=千
MM,对会计师来说 = 百万,a la KK
十万,对印度人来说 = 100K
克罗,对印度人来说 = 10M(一千万)
十亿,对英国人来说曾经意味着百万;幸运的是,这种困惑似乎已经消失了。
出于所有实际目的,本论坛可以忽略 1000 和 1024(以及 KB 与 KiB)之间的区别。
我正在尝试调整一个非常简单的查询:
select * from log where user_id in (...) order by id desc limit 25
我只想显示一组不同用户 ID(大约 40 个 ID)的最后 25 个事件。此查询大约需要 50 秒 运行(table 中有超过 8000 万条记录)。
通过执行 EXPLAIN format=json
我可以看到 access_type
是 range
。经过一番探索,A 了解到,如果我将 ID 的数量更改为 9,查询计划器将使用另一种访问方式:index
.
所以我假设对于大量 ID MySQL 将在组的较小和较大 ID 之间进行范围扫描,如果 ID 是 'close',这可能有意义,但情况并非总是如此。也许不知何故,这种额外的数据量在进行排序时会成为一个问题(如下面的解释计划所示)。
40个ID说明
{
"query_block": {
"select_id": 1,
"ordering_operation": {
"using_filesort": true,
"table": {
"table_name": "log",
"access_type": "range",
"possible_keys": [
"app_log_user_id"
],
"key": "log_user_id",
"used_key_parts": [
"user_id"
],
"key_length": "4",
"rows": 6150,
"filtered": 100,
"index_condition": "(`app`.`log`.`user_id` in (<43 different ids from 12000 to 330000>))"
}
}
}
}
9个ID说明
{
"query_block": {
"select_id": 1,
"ordering_operation": {
"using_filesort": false,
"table": {
"table_name": "log",
"access_type": "index",
"possible_keys": [
"app_log_user_id"
],
"key": "PRIMARY",
"used_key_parts": [
"id"
],
"key_length": "4",
"rows": 6901,
"filtered": 4552.8,
"attached_condition": "(`app`.`log`.`user_id` in (< 9 ids from 12000 to 18000))"
}
}
}
}
我做了一个实验:我将该查询分成 5 个只有 9 个或更少 ID 的子查询,并对所有子查询应用 UNION
,最后以 ORDER 和 LIMIT 子句结束。这个查询的查询计划变得有点混乱,即使有奇怪的值表明其中一个子查询的搜索行数将是 86737713(我认为这是一个非常错误的估计,所有其他的都在 10246 左右)。你猜怎么着?查询用了 "only" 6 秒,比 50 秒好。
我不知道使用了哪些策略来优化这种查询,但据我所知,如果我可以告诉优化器使用 index
的 acess_type
来代替range
,它会表现得更好。这可能吗?
额外详情
user_id
有一个外键和一个索引。- 我们使用MySQL 5.6 (InnoDB)
- Table 大约有 80kk 行。
显示创建 TABLE
CREATE TABLE `app_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`timestamp` datetime NOT NULL,
`user_id` int(11) NOT NULL,
`content_type_id` int(11) NOT NULL,
`object_id` int(10) unsigned NOT NULL,
`status` int(11) DEFAULT NULL,
`type` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `app_log_content_type_id` (`content_type_id`),
KEY `app_log_144dd2a9` (`timestamp`),
KEY `app_log_user_id` (`user_id`, `id`)
)
ENGINE = InnoDB
AUTO_INCREMENT = 108628300
DEFAULT CHARSET = latin1
可能的解释:
您使用的 MySQL/MariaDB 是什么版本?我猜您使用的是 MySQL 5.6? (您使用 FORMAT=JSON
确认 "at least 5.6.5"。)
- 5.6.5引入
eq_range_index_dive_limit
,默认为10. - 5.7.4
eq_range_index_dive_limit
默认值提高到 200 - 影响IN()
可能的解决方法:
此注释可能 解释了 IN
列表中的第 9 项与第 43 项。建议你玩 eq_range_index_dive_limit
.
知识问答
KK = 千千
M、对会计师='mille'=千
MM,对会计师来说 = 百万,a la KK
十万,对印度人来说 = 100K
克罗,对印度人来说 = 10M(一千万)
十亿,对英国人来说曾经意味着百万;幸运的是,这种困惑似乎已经消失了。
出于所有实际目的,本论坛可以忽略 1000 和 1024(以及 KB 与 KiB)之间的区别。