临时表必须建索引,否则易引发OOM;应显式创建带索引的临时表而非SELECT INTO,避免循环插入、慎用CTE,大数据量需分批处理并优化内存授予。

临时表没加索引,查着查着就 OOM
SQL Server 或 MySQL 存储过程中建了 #temp_table 但没加索引,后续几十万行数据一 JOIN 就卡住,最后报 Could not allocate space for object 'dbo.#temp_table' in database 'tempdb'——这不是磁盘满,是内存/页分配失败。尤其在循环里反复 INSERT 到同一个临时表却不清理,tempdb 的版本存储和排序操作会疯狂吃内存。
实操建议:
- 在
INSERT INTO #temp_table SELECT ...完成后,立刻建关键查询字段的非聚集索引:CREATE INDEX IX_temp_status ON #temp_table (status_id, created_time) - 避免在循环中反复向同一临时表追加数据;改用表变量
@table_var(小数据量)或分批次处理+显式DROP TABLE #temp_table - SQL Server 中检查
tempdb文件是否均分在多个物理磁盘上,单个tempdb数据文件容易成为瓶颈
SELECT INTO #temp 比 INSERT INTO #temp 更耗资源
SELECT INTO #temp 看似简洁,但它会触发全表扫描+隐式锁+完整日志记录(即使在简单恢复模式下),且无法提前定义索引结构;而 INSERT INTO #temp SELECT ... 可配合已建好索引的空临时表,让 SQL 引擎更早启用并行计划和内存授予预估。
实操建议:
- 先
CREATE TABLE #temp (...)显式声明结构,再CREATE CLUSTERED INDEX(哪怕只是单列),最后INSERT INTO #temp SELECT ... - 对大数据量场景,禁用
SELECT INTO;它在 SQL Server 2019+ 中仍不支持统计信息自动创建,优化器容易低估行数导致内存分配不足 - MySQL 用户注意:
CREATE TEMPORARY TABLE ... SELECT同样跳过索引定义阶段,应拆成CREATE TEMPORARY TABLE+INSERT ... SELECT
CTE 替代临时表?小心递归深度和内存缓存失控
有人看到临时表内存爆了就想换成 WITH cte AS (...),但 CTE 不是“轻量替代”——SQL Server 会对 CTE 结果集做多次物化(尤其被引用多次时),MySQL 8.0 的 CTE 默认强制物化,反而比带索引的临时表更占 tempdb 或 innodb_buffer_pool。
实操建议:
- 仅当 CTE 只被引用一次、且逻辑简单(无窗口函数/聚合嵌套)时才考虑;否则老老实实用带索引的
#temp_table - SQL Server 中加
OPTION (RECOMPILE)可帮优化器重估 CTE 行数,但治标不治本;真正要控内存,得从原始数据过滤下手(比如先 WHERE 再进 CTE) - MySQL 用户:确认
cte_max_recursion_depth没被设成超大值,否则递归 CTE 可能栈溢出而非内存溢出,错误信息是Recursive query aborted after 1001 iterations
存储过程里用 UNION ALL 拼大结果集,内存直接翻倍
常见写法:SELECT * FROM t1 WHERE x=1 UNION ALL SELECT * FROM t2 WHERE x=1,表面看只是合并,但 SQL Server 会为每个分支单独申请内存授予(grant),且无法共享排序/哈希空间。如果两个子查询各需 1GB 内存,最终可能申请 2.5GB 以上,超出 max server memory 就触发溢出到磁盘甚至失败。
实操建议:
- 改用
INSERT INTO #result SELECT ... FROM t1 WHERE x=1; INSERT INTO #result SELECT ... FROM t2 WHERE x=1,复用同一内存上下文 - 确保
#result有合适主键或唯一索引,否则后续 GROUP BY 或 DISTINCT 会再次触发大内存排序 - SQL Server 中用
SET STATISTICS XML ON查执行计划,重点看QueryPlan/@CachedPlanSize和RelOp/@MemoryFractions,别只盯着“警告”图标
临时表不是垃圾桶,建之前得想清楚谁读、怎么读、读几次。索引不是可选项,是内存安全线。最危险的不是数据大,是以为“先跑通再说”的那句 SELECT INTO #tmp。

















