直接GROUP BY大表会卡住,因需将所有匹配行加载至内存或磁盘临时表进行哈希分组或排序,行数越多,CPU、内存、I/O消耗剧增;无索引时更触发Using temporary; Using filesort。
为什么直接 GROUP BY 大表会卡住?
因为数据库执行
时,必须把所有匹配的行拉到内存(或磁盘临时表)里做哈希分组或排序,输入行数越多,CPU、内存、I/O 消耗越不成比例地飙升。2000 万行的表,
后剩 500 万,
就得处理这 500 万;如果没索引,还会触发
——这是性能断崖的典型信号。
分片统计 + 全局汇总的关键实操点
这不是“拆 SQL”,而是用可控的、可并行的子任务替代单点重压。核心是让每个分片能独立完成局部聚合,再合并结果。常见落地方式有:
按时间/区域等业务维度切分:比如日志表按
字段分区,查“近 30 天各城市 UV”,就分别跑
… 直到 5 月 18 日,最后在应用层或临时表里
+ 再
用主键范围分片:对无天然业务维度的表(如用户关系表),按
切成若干段,每段走自己的
,注意避免漏数据或重复(边界要闭合,且用
避免在分片阶段用高开销函数:比如不要在每个分片里都算
再分组,而应提前把归一化字段(如
)建好并索引,否则分片只是把瓶颈从全局挪到每个子查询
汇总层慎用
:分片后全局去重无法简单加总。若必须,可用 HyperLogLog 类似算法(PostgreSQL 的
扩展、MySQL 8.0+ 的
),或改用布隆过滤器在应用层合并
什么时候该停手,换物化方案?
如果你发现同一类
查询每周跑 50+ 次,且容忍 T+1 延迟,那就别再拼分片脚本了。直接建汇总表:
每天凌晨跑一次
给汇总表建主键
,查询时
能走索引范围扫描,响应稳定在毫秒级
MySQL 不支持原生物化视图,但可用事件调度器(
)自动刷新;PostgreSQL 可直接用
+ 定期
分片统计是“临时止痛”,物化是“手术根治”。真正容易被忽略的是:一旦上了物化路径,就得同步维护一致性——比如源表发生 DELETE,汇总表是否及时减掉?这点常被跳过,导致统计数据悄悄漂移。
GROUP BYWHEREGROUP BYUsing temporary; Using filesortdtSELECT city, COUNT(DISTINCT uid) FROM log WHERE dt = '2026-05-01' GROUP BY cityUNION ALLGROUP BY cityid BETWEEN ? AND ?GROUP BYid >= ? AND id 更安全)MD5(phone)city_codeCOUNT(DISTINCT)HLLAPPROX_COUNT_DISTINCTGROUP BYINSERT INTO daily_city_stats SELECT city, COUNT(*), COUNT(DISTINCT uid) FROM log WHERE dt = '2026-05-17' GROUP BY city(dt, city)WHERE dt BETWEEN ... AND ...EVENTCREATE MATERIALIZED VIEWREFRESH