MySQL-使用复合索引键改进count(*)聚合

时间:2019-03-27 10:38:40

标签: mysql sql query-optimization aggregation sql-query-store

我有一个具有以下结构的表,其中包含近120000行,

desc user_group_report +------------------+----------+------+-----+-------------------+-------+ | Field | Type | Null | Key | Default | Extra | +------------------+----------+------+-----+-------------------+-------+ | user_id | int | YES | MUL | NULL | | | group_id | int(11) | YES | MUL | NULL | | | type_id | int(11) | YES | | NULL | | | group_desc | varchar(128)| NO| | NULL | | status | enum('open','close')|NO| | NULL | | | last_updated | datetime | NO | | CURRENT_TIMESTAMP | | +------------------+----------+------+-----+-------------------+-------+

我在以下键上有索引:

  • 用户组类型(用户ID,组ID,组类型)
  • group_type(group_id,type_id)
  • user_type(user_id,type_id)
  • user_group(user_id,group_id)

我的问题是我正在按group_id在上面的表组上运行count(*)聚合,并且在type_id上​​有一个子句

这是查询:

select count(*) user_count, group_id
from user_group_report
where type_id = 1
group by group_id;

这是解释计划(查询平均需要0.3秒):

+----+-------------+------------------+-------+---------------------------------+---------+---------+------+--------+--------------------------+
| id | select_type | table            | type  | possible_keys                   | key     | key_len | ref  | rows   | Extra                    |
+----+-------------+------------------+-------+---------------------------------+---------+---------+------+--------+--------------------------+
|  1 | SIMPLE      | user_group_report | index | user_group_type,group_type,user_group | group_type | 10      | NULL | 119811 | Using where; Using index |
+----+-------------+------------------+-------+---------------------------------+---------+---------+------+--------+--------------------------+

据我所知,由于索引复杂,查询几乎会进行全表扫描,当我尝试在group_id上添加索引时,说明计划中的行显示的数字较少(几乎是行的一半),但花费时间查询执行时间增加到0.4-0.5秒。

我尝试了不同的方式来添加/删除索引,但是没有一种方法可以减少花费的时间。

假设表结构无法更改,并且查询独立于其他表,有人可以建议我一种更好的方法来优化上述查询,或者如果我在这里遗漏任何内容。

PS: 我已经尝试将查询修改为以下内容,但找不到任何改进。

select count(user_id) user_count, group_id
from user_group_report
where type_id = 1
group by group_id;

不胜感激。

编辑:

根据建议,我添加了一个新索引

type_group on(type_id,group_id)

这是新的解释计划。说明中的行数减少了,但查询执行时间仍然相同

+----+-------------+------------------+------+---------------------------------+---------+---------+-------+-------+--------------------------+
| id | select_type | table            | type | possible_keys                   | key     | key_len | ref   | rows  | Extra                    |
+----+-------------+------------------+------+---------------------------------+---------+---------+-------+-------+--------------------------+
|  1 | SIMPLE      | user_group_report | ref  | user_group_type,type_group,user_group | type_group | 5       | const | 59846 | Using where; Using index |
+----+-------------+------------------+------+---------------------------------+---------+---------+-------+-------+--------------------------+

编辑2: 根据答案/评论中的建议添加详细信息

select count(*)
from user_group_report
where type_id = 1

此查询本身需要0.25秒才能执行。

这是解释计划:

+----+-------------+------------------+------+---------------+---------+---------+-------+-------+-------------+
| id | select_type | table            | type | possible_keys | key     | key_len | ref   | rows  | Extra       |
+----+-------------+------------------+------+---------------+---------+---------+-------+-------+-------------+
|  1 | SIMPLE      | user_group_report | ref  | type_group       | type_group | 5       | const | 59866 | Using index |
+----+-------------+------------------+------+---------------+---------+---------+-------+-------+-------------+

3 个答案:

答案 0 :(得分:3)

我相信您的group_type是错误的。尝试切换属性。

create index ix_type_group on user_group_report(type_id,group_id)

此索引更适合您的查询,因为您在type_id = 1子句中指定了where。因此,查询处理器找到索引中带有type_id = 1的第一条记录,然后扫描带有该type_id的索引中的记录并执行聚合。使用这种索引,只能访问索引中的相关记录,而group_type索引则无法访问。

答案 1 :(得分:1)

如果type_id是选择性的(即,它大大减少了搜索空间),则在type_id, group_id上创建索引应该会很有帮助。

这是因为它减少了需要首先分组的记录数(删除了type_id!= 1的所有内容),然后才进行分组/求和。

编辑:

在评论之后,似乎我们需要找出更多瓶颈所在-查找记录或分组/求和。

第一步是评估以下各项的效果:

select count(*)
from user_group_report
where type_id = 1

如果这要快得多,则与查找记录相比,分组中的挑战可能更大。如果那样慢,那就是首先查找记录。

答案 2 :(得分:0)

大多数列是否真的需要NULLable?更改为NOT NULL

type_id = 1占表的百分之几?如果这是表格的大部分内容,那么这将解释为什么您看不到太多改进。同时,EXPLAIN似乎在想type_id只有两个不同的值,因此它说只扫描了一半的表-该数字不可信

要对正在发生的事情有更多的了解,请执行以下操作:

EXPLAIN FORMAT=JSON SELECT...;

FLUSH STATUS;
SELECT ...
SHOW SESSION STATUS LIKE 'Handler%';

我们可以帮助您解释到达那里的数据。 (Here是对此的简短讨论。)