高并发下GROUP BY慢需优先检查索引:GROUP BY字段必须为索引最左前缀,避免函数操作;缓存应聚合值而非SQL结果,用Redis HINCRBY配合事件更新;预计算表须按维度与粒度解耦建表,并带updated_at字段。

高并发下 GROUP BY 慢,先看执行计划有没有走索引
没索引的 GROUP BY 在大数据量+高并发时,基本等于现场建临时表再扫一遍,CPU 和 I/O 都会飙高。不是加缓存就能救的。
实操建议:
- 用
EXPLAIN SELECT ... GROUP BY确认key列是否命中索引,type是否为ref或range,避免ALL或index -
GROUP BY字段必须是索引最左前缀;如果写成GROUP BY status, created_at,但索引是(created_at, status),就用不上 - 避免在
GROUP BY字段上用函数,比如GROUP BY DATE(created_at)—— 会强制全表扫描
缓存 COUNT(*) 或 SUM() 结果,别缓存原始 SQL 结果集
直接缓存 SELECT status, COUNT(*) FROM orders GROUP BY status 的 JSON 字符串,看似省事,其实埋雷:一旦有新状态插入、旧数据更新,缓存就过期,且无法精准失效。
更稳的做法是缓存聚合值本身,并配合业务事件更新:
- 用 Redis 的
HINCRBY维护每个分组键的计数,例如HINCRBY order_status_counts 'paid' 1 - 在订单状态变更(如从
pending→paid)时,原子性地HINCRBY order_status_counts 'pending' -1+HINCRBY order_status_counts 'paid' 1 - 冷启动或校验时,用
SELECT status, COUNT(*) ... GROUP BY status全量刷一次,不走缓存
预计算表要按「查询维度 + 时间粒度」拆开建,别堆一起
一张叫 agg_orders_daily_by_status 的表,看着清晰,但只要加个“按城市分组”需求,就得重建整张表或加冗余字段,后续难维护。
预计算的关键是解耦维度和粒度:
- 按天聚合:建
orders_daily_status(字段:date,status,cnt,amount_sum) - 按周聚合:单独建
orders_weekly_status,不要试图用DATE_SUB(date, INTERVAL WEEKDAY(date) DAY)在查时算 - 新增维度(如
region)?另建orders_daily_status_region,而不是往老表加字段 - 所有预计算表必须带
updated_at,方便监控延迟;用定时任务(如 Airflow)跑,别依赖应用层触发
MySQL 8.0+ 可用物化视图替代部分预计算,但要注意刷新策略
MySQL 官方物化视图(CREATE MATERIALIZED VIEW)还没进正式版,但社区方案(如 mysql-mv 工具)或通过触发器+汇总表模拟,容易忽略刷新一致性。
真实踩坑点:
- 用触发器自动更新汇总表时,批量导入(
INSERT ... SELECT)可能绕过触发器,导致数据不一致 - 如果用异步任务刷新,得确保
SELECT ... GROUP BY查询范围与刷新时间窗口对齐,否则漏数据 - 物化结果字段类型要和源表一致,比如源
amount是DECIMAL(12,2),汇总表也得是,否则SUM()出现浮点误差
真正复杂的地方不在怎么建,而在怎么让预计算/缓存/源数据三者始终对得上——尤其当有历史数据重跑、跨库同步、或分库分表时,一个环节断链,统计就偏了。










