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

Posted

技术标签:

【中文标题】MySQL-使用复合索引键改进 count(*) 聚合【英文标题】:MySQL- Improvement on count(*) aggregation with composite index keys 【发布时间】:2019-08-17 21:37:52 【问题描述】:

我有一个具有以下结构的表,几乎有 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 | | +------------------+----------+------+-----+-------------------+-------+

我在以下键上有索引:

user_group_type(user_id,group_id,group_type) 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)

这是新的解释计划。 explain中的行数减少了,但是查询执行时间还是一样的

+----+-------------+------------------+------+---------------------------------+---------+---------+-------+-------+--------------------------+
| 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: 按照 answers/cmets 中的建议添加详细信息

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 |
+----+-------------+------------------+------+---------------+---------+---------+-------+-------+-------------+

【问题讨论】:

count(user_id) 可能比count(*) 慢,因为它会检查user_id 是否不是NULL PRIMARY KEY 是什么?请提供SHOW CREATE TABLE,它比DESC更具描述性。 【参考方案1】:

我认为您的group_type 是错误的。尝试切换属性。

create index ix_type_group on user_group_report(type_id,group_id)

此索引更适合您的查询,因为您在 where 子句中指定了 type_id = 1。因此,查询处理器会在您的索引中找到第一个带有type_id = 1 的记录,然后它会扫描带有此type_id 的索引中的记录并执行聚合。有了这样的索引,只能访问索引中的相关记录,而group_type 索引是不可能的。

【讨论】:

感谢它确实有助于减少评估的行数,但查询执行时间仍然是您看到的任何问题? 它与其他索引有什么关系,虽然我觉得这不是问题。 如果执行时间仍然相同,那么您的环境中肯定存在一些外部问题(网络延迟?) 我认为情况并非如此,因为其他查询没有问题,我直接从 mysql 控制台运行它。【参考方案2】:

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

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

编辑:

从 cmets 开始,我们似乎需要更多地了解瓶颈所在 - 查找记录或分组/求和。

第一步是衡量以下方面的表现:

select count(*)
from user_group_report
where type_id = 1

如果这明显更快,则挑战可能在于分组而不是查找记录。如果这同样慢,那么首先是查找记录。

【讨论】:

谢谢。我也从 otehr 答案中看到了同样的建议。请关注我的评论 虽然评估的行数减少了一半,但我仍然无法改进时间 能否更新您的问题以显示新的查询计划和索引? 在这种情况下,您很可能在其他地方遇到瓶颈 - 您检查过内存、CPU 和磁盘利用率了吗? 我认为这不是问题,因为其他表甚至同一张表上的所有其他查询都运行良好。【参考方案3】:

大多数列真的需要NULLable吗?在适用的情况下更改为NOT NULL

表格中有多少百分比有type_id = 1?如果它是桌子的大部分,那么这就解释了为什么你没有看到太大的改进。同时,EXPLAIN 似乎认为type_id 只有两个不同的值,因此它说只有一半的表会被扫描——这个数字不可信

要更深入地了解正在发生的事情,请执行以下操作:

EXPLAIN FORMAT=JSON SELECT...;

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

我们可以帮助解释您获得的数据。 (Here 是一个简短的讨论。)

【讨论】:

以上是关于MySQL-使用复合索引键改进 count(*) 聚合的主要内容,如果未能解决你的问题,请参考以下文章

复合索引,排第一的效果

mysql的联合主键与复合主键区别

mysql数据库基础(2)索引主键复合主键外键

从不使用以 DATETIME 作为复合键第一部分的主键索引

MySQL 索引

如何修复“MySQL 错误:1822。缺少约束索引”关于创建复合外键