用 md5() 对长文本计算摘要后再 group by 可大幅降低内存占用,因摘要长度固定(36 字节),远小于原始 mb 级文本;但需注意避免隐式转换、截断无效、哈希碰撞等陷阱。

为什么 GROUP BY 长文本会爆内存?
PostgreSQL 对 GROUP BY 字段做哈希分组时,会把整个字段值(比如 TEXT 类型的 10MB 日志片段)完整载入内存构建哈希表。哪怕只分组几百行,也可能触发 work_mem 超限、降级为磁盘排序,甚至 OOM。这不是数据量大才出问题,而是单个值太大就扛不住。
关键点:PostgreSQL 不会对 TEXT 或 VARCHAR 做自动截断或哈希预处理——它认的就是原始值。
用 md5() 替代原始长文本分组
对长文本字段计算固定长度摘要再分组,是最直接有效的内存减负方式。只要业务上允许“内容相同 → 摘要相同”,就可安全使用。
SELECT md5(content), COUNT(*) FROM logs GROUP BY md5(content);- 注意:不能写成
GROUP BY content再选md5(content),否则仍会加载全文本 - 如果需返回原始内容示例,加
MIN(content)或用DISTINCT ON (md5(content))配合子查询 -
md5()返回 32 字符TEXT,但实际只占 36 字节(含类型头),比动辄 KB/MB 的原文小两个数量级以上
避免隐式类型转换导致全文本参与哈希
常见陷阱:字段是 TEXT,但 WHERE 或 JOIN 中用了 ::VARCHAR 或拼接操作,可能让 planner 放弃索引、同时阻止哈希优化路径。
- 检查执行计划:运行
EXPLAIN (ANALYZE, BUFFERS),确认Group Key显示的是md5(content)而非content - 不要在
GROUP BY里用substring(content, 1, 200)—— 截断不改变内存占用,PostgreSQL 仍需加载全文本再截 - 若必须按前缀分组(如 URL 域名),先建生成列:
ALTER TABLE logs ADD COLUMN domain TEXT GENERATED ALWAYS AS (split_part(url, '//', 2)) STORED;,再在该列上建索引并GROUP BY domain
什么时候不该用摘要分组?
摘要方案不是万能解药。以下情况需谨慎:
- 存在哈希碰撞风险(极低但非零):MD5 在海量唯一文本下理论碰撞概率上升,生产环境若需强一致性,改用
sha256()(性能略低但更安全) - 需要支持模糊分组(如“相似日志”聚类),此时摘要无意义,应换用
pg_trgm+similarity()配合自定义逻辑 - 字段本身有索引且分组基数极小(work_mem 并强制哈希分组更简单
真正省内存的关键,从来不是“多大 work_mem”,而是让 PostgreSQL 别把整段日志塞进哈希桶里——你得主动替它把数据变小。










