大表统计查询优化的三条路径

一张点击流水表到了千万行级,按时间范围的统计查询从几百毫秒一路涨到三秒多。记录我从慢到快的三种改法,以及各自适用的场景。

问题的起点很普通:有一张记录点击流水的表,后台需要按日期范围统计每天的点击量。表涨到千万行之后,这个查询开始需要三秒以上,后台首页直接卡住。

第一条路径:让索引覆盖

原先的索引只建在时间字段上,查询又要取用户和渠道两个字段。执行计划里回表的次数太多,索引的意义被抵消掉了。把查询真正用到的字段合并成一个联合索引之后,查询变成覆盖索引扫描,不用回表。这是改动最小、收益最直接的一步。

-- 先看执行计划,确认回表量
EXPLAIN SELECT DATE(created_at) d, COUNT(*) 
FROM clicks 
WHERE created_at >= '2026-06-01' 
GROUP BY d;

-- 把统计要用的维度一起放进索引,避免回表
ALTER TABLE clicks ADD INDEX idx_created_channel (created_at, channel);

第二条路径:预聚合

覆盖索引解决的是"扫描成本",但数据量继续涨,扫描行数本身还是瓶颈。这时候把统计从"查明细"改成"查汇总":定时任务按天把明细归并进一张统计表,后台读统计表。明细表只负责写入,首页查询从千万行级降到几百行级。

预聚合的代价是实时性。折中做法是统计表覆盖到"昨天",今天的部分仍然查明细,再对明细查询加一个时间下限,避免扫描量失控。

第三条路径:改变粒度

如果连预聚合都不够,通常说明统计维度设计得太细。把按天改成按小时累计、或者把渠道维度从几十个归并成几类,往往能在不损失业务价值的前提下把数据量降一个数量级。

怎么选

先做第一条,成本最低;如果查询量本身也在涨,就上第二条;第三条属于数据模型层面的调整,需要在需求侧达成一致再动。三条不是互斥的,我最后的方案是前两条一起用。