哈希溢出是查询计划严重误估的明确告警,而非单纯内存不足;它发生在GROUP BY、HASH JOIN等操作中,因实际数据远超优化器预估导致哈希表溢出到磁盘,须通过执行计划(如SpillLevel>0、Using temporary)和运行时指标(如internal_objects_alloc_page_count、Created_tmp_disk_tables)交叉验证,并优先优化索引、下推过滤、精简字段及拆分倾斜key。

哈希溢出不是内存不够的信号,而是查询计划严重误估的明确告警。 它发生在 GROUP BY、HASH JOIN 或 COUNT(DISTINCT) 等操作中,数据库试图把分组键或连接键全量载入内存哈希表,但实际数据规模远超优化器预估——此时不是加内存就能解决,而是要让优化器“看清楚”真实数据分布。
怎么确认是哈希溢出而不是普通慢查询
别靠感觉判断。必须从执行计划和运行时指标交叉验证:
- MySQL:EXPLAIN 输出中出现
Using temporary,且SHOW STATUS LIKE 'Created_tmp_disk_tables'持续上涨;SELECT @@tmp_table_size, @@max_heap_table_size返回值不一致(取小者生效) - PostgreSQL:
EXPLAIN (ANALYZE, BUFFERS)显示HashAgg节点有Spill to disk,或Sort Method: external merge Disk - SQL Server:图形化执行计划里
Hash Match或Hash Aggregate算子属性中SpillLevel > 0,或 Extended Events 捕获到hash_warning事件 - 所有数据库:监控临时空间使用——MySQL 查
information_schema.INNODB_TEMP_TABLE_INFO,PG 查pg_stat_statements中blk_read_time异常高,SQL Server 查sys.dm_db_session_space_usage的internal_objects_alloc_page_count
为什么调大 tmp_table_size / work_mem / max server memory 往往无效
这些参数只是“延缓崩溃”,不是根治手段。根本问题在于哈希表内存占用 = 分组数 ×(分组键体积 + 聚合值体积)。几个关键事实:
- MySQL 的
tmp_table_size和max_heap_table_size必须设为相同值才真正生效,否则按较小者限制;设 1GB 但另一项是 64MB,实际仍卡在 64MB - PostgreSQL 的
work_mem是每个操作独占上限,一个查询含 JOIN + GROUP BY + ORDER BY,会申请三份,50 并发 × 128MB = 6.4GB,极易触发系统级 OOM - SQL Server 的
max server memory是全局缓冲池上限,调高反而挤压buffer pool,加剧PAGEIOLATCH_SH等争用 - 哈希溢出发生前,内存可能已被 Windows 或 Linux OOM Killer 杀掉进程——
hash_warning只在已 spill 到 tempdb 时才触发,此前无预警
真正有效的调优动作优先级
从高到低排序,越靠前效果越直接:
-
索引驱动的分组/连接规避:对
GROUP BY a, b且带WHERE c = ?的查询,建联合索引INDEX(c, a, b);对JOIN t1 ON t1.x = t2.y,确保t1.x和t2.y各自都有索引,避免优化器被迫选 Hash Match -
字段精简与确定性截断:禁止
SELECT *参与聚合;对大文本字段,改用GROUP BY SHA2(long_text, 256)或SUBSTRING_INDEX(url, '/', 3),并确保该表达式能走函数索引(MySQL 8.0+/PG 12+) -
过滤条件必须下推:写成
JOIN (SELECT * FROM orders WHERE status = 'active') o ON ...,而不是JOIN orders o ON ... WHERE o.status = 'active';同样,GROUP BY 前先用子查询或 CTE 过滤掉 90% 数据 -
倾斜 key 拆分处理:若 1% 的 user_id 占了 90% 的订单行,先单独查热 key:
SELECT ... FROM orders WHERE user_id IN (SELECT id FROM hot_users),再查剩余:... WHERE user_id NOT IN (...),最后UNION ALL合并
最容易被忽略的是:哈希溢出往往不是单点配置问题,而是统计信息过期 + 索引缺失 + 查询写法耦合导致的连锁反应。修复一个环节可能没用,必须同步检查执行计划里的 EstimateRows 是否比 ActualRows 小两个数量级以上——这才是真正的起点。










