GROUP BY索引优化:查个统计为啥卡半天?

上周帮邻居老张看数据库,他做销售报表时跑一条 SELECT region, COUNT(*) FROM orders GROUP BY region;,数据才80万行,却要等12秒。他挠头说:‘我加了region字段的索引,咋还这么慢?’

GROUP BY不是光有索引就行

很多人以为只要 GROUP BY 的字段建了索引就万事大吉,其实漏掉了关键点:MySQL(尤其是5.7+默认的InnoDB)执行 GROUP BY 时,优先走的是 松散索引扫描(Loose Index Scan),但前提是——索引能覆盖整个分组逻辑,且没有额外排序或临时表介入。

比如这张表:

CREATE TABLE orders (
id BIGINT PRIMARY KEY,
region VARCHAR(32),
amount DECIMAL(10,2),
create_time DATETIME
);

只给 region 单独建索引:
ALTER TABLE orders ADD INDEX idx_region (region);
——这基本没用。因为 MySQL 还得回表取数据、排序、去重,最后大概率还是用临时表 + filesort。

真正管用的写法是这样

GROUP BY 字段和查询中涉及的聚合字段一起组成联合索引,顺序很重要:

ALTER TABLE orders ADD INDEX idx_region_count (region, id);

注意:这里用 id 是因为 COUNT(*) 只需要行存在性,引擎可直接用索引中的主键值计数,无需回表。如果查的是 COUNT(amount) 且允许 NULL,那最好把 amount 也放进索引里(非空字段更稳妥):

ALTER TABLE orders ADD INDEX idx_region_amount (region, amount);

再跑一遍:
EXPLAIN SELECT region, COUNT(*) FROM orders GROUP BY region;
看到 type: indexExtra: Using index,说明走的是覆盖索引,速度立马从12秒降到0.15秒。

顺手压一压备份体积

这类不光提速,对“压缩备份”栏目也真有用——索引越高效,查询越少触发磁盘临时表(/tmp/#sql_*.MYD),备份时生成的临时快照就更小;而且高频统计语句跑得快,备份窗口期压力也小,不容易卡住 mysqldump 或 xtrabackup 的一致性读。

另外提醒一句:别盲目加 ORDER BY NULL 去抑制排序。MySQL 8.0 后很多场景已自动优化掉冗余排序,硬加反而可能干扰优化器选索引。

最后检查下你的慢查询日志,搜 GROUP BY 关键字,挑出耗时TOP3,按上面方法改索引,比调 buffer_pool 大小见效快多了。