对大字段分组必然慢,因其无法高效参与b+树索引排序,比较与哈希开销大,易触发temporary和filesort;应改用确定性哈希值建索引并前置计算,配合合理复合索引顺序,超大规模需预聚合。

为什么对大字段分组必然慢?
大字段(如 TEXT、VARCHAR(2000)、JSON)本身无法高效参与 B+ 树索引排序,MySQL/PostgreSQL 即使建了单列索引,也会因前缀长度限制或存储方式(如外部溢出页)导致索引无法支撑分组扫描。更关键的是:分组操作依赖字段值的可比性与有序性,而大字段的比较成本高、哈希计算开销大,极易触发 Using temporary 和 Using filesort——这不是“有点慢”,是查询执行模型层面的硬瓶颈。
别直接 GROUP BY remark, content —— 改用哈希降维
对原始大字段分组等于让数据库逐行读取并完整比对字符串,毫无优化空间。真实可行的做法是前置生成确定性哈希值,并在该哈希列上建索引:
- MySQL 5.7+ 可用生成列:
ALTER TABLE logs ADD COLUMN remark_hash CHAR(32) AS (MD5(remark)) STORED;
再建索引:CREATE INDEX idx_remark_hash ON logs(remark_hash);
- PostgreSQL 可用表达式索引:
CREATE INDEX idx_remark_md5 ON logs USING btree (md5(remark));
- 查询时改写为:
SELECT remark_hash, COUNT(*) FROM logs WHERE remark_hash IS NOT NULL GROUP BY remark_hash;
(注意:哈希碰撞概率极低但存在,业务需接受“近似去重”或加二次校验)
WHERE 条件没索引?先过滤再分组,顺序不能错
即使分组字段本身难索引,只要能通过其他高区分度字段(如 status、created_at)大幅缩小数据集,就能避免全表扫描。此时复合索引顺序必须是:WHERE 字段 → 分组哈希字段:
- 错误索引:
INDEX(remark_hash, status)——status不在最左,WHERE 无法利用 - 正确索引:
INDEX(status, remark_hash)—— 先按status快速定位数据块,再在子集内按哈希值有序分组 - 若还需
SUM(amount),扩展为:INDEX(status, remark_hash, amount)(覆盖索引,避免回表)
千万级后,哈希也扛不住?该切预聚合了
哈希降维能缓解中等规模压力,但当单日新增百万级大字段记录、且分组维度固定(如“按错误日志内容归类统计”),实时 SQL 已无优化余地。此时必须跳出单条查询思维:
- 用定时任务(如每小时)跑:
INSERT INTO log_summary (hour_key, remark_hash, cnt, sample_id) SELECT HOUR(created_at), MD5(remark), COUNT(*), MIN(id) FROM logs WHERE created_at >= ? AND created_at
- 查询直接走
log_summary表的主键或二级索引,响应稳定在毫秒级 - 注意:不要试图在大表上加唯一约束防重复哈希——写入性能会断崖下跌;用应用层或异步去重更可控
真正卡住的从来不是语法,而是把“必须实时算”的执念,和“字段太大没法索引”的事实混在一起想解法。哈希是过渡,预聚合才是终点。










