直接在存储过程中大表JOIN易OOM,因BNL算法将驱动表载入join_buffer_size内存区,缓冲区不足时频繁刷写致内存溢出;应改用带主键索引的临时表分批驱动JOIN,并调优相关内存参数。

为什么直接在存储过程中写大表JOIN容易OOM
MySQL 存储过程本身不“造内存压力”,真正吃掉内存的是它执行的 SELECT ... JOIN 语句。当两个千万级表直接关联,优化器若选错驱动表或无法走索引,就会触发 Block Nested Loop(BNL)算法——把驱动表批量载入 join_buffer_size 内存区,再逐批去扫描被驱动表。一旦 join_buffer_size 不够(默认仅 256KB),缓冲区反复刷写 + 中间结果膨胀,极易撑爆内存,触发 Created_tmp_disk_tables 暴涨甚至 OOM killer 干掉连接。
用临时表分批驱动JOIN的关键操作
核心不是“建个临时表放数据”,而是用它做**可控边界+精准索引驱动**:
- 先按主键范围切片:比如
SELECT id FROM large_table WHERE id BETWEEN 100001 AND 200000,插入CREATE TEMPORARY TABLE temp_batch (id BIGINT PRIMARY KEY) - 立刻加索引:
ALTER TABLE temp_batch ADD INDEX idx_ref_id (id)—— 没索引的临时表和原表扫描没区别 - 用该临时表作为驱动表做 JOIN:
INSERT INTO target SELECT ... FROM temp_batch t JOIN big_dim d ON t.id = d.ref_id - 每批处理完
TRUNCATE TEMPORARY TABLE temp_batch,再填下一批 ID,避免累积
临时表引擎选 MEMORY 还是 InnoDB?
别迷信 MEMORY。它有硬限制:
- 不支持
TEXT/BLOB字段,一碰就静默转磁盘表 - 大小超
max_heap_table_size时自动降级为磁盘表,多一次转换开销 - 生产环境更稳的选择是
CREATE TEMPORARY TABLE ... ENGINE=InnoDB,配合足够大的innodb_buffer_pool_size - 如果只是中转几万行整数 ID,
MEMORY可用,但必须确认max_heap_table_size≥ 单批数据内存占用(估算:行数 × 行宽)
参数与监控必须同步跟上
光改 SQL 不调参,等于给油车换轮胎却不调胎压:
- 检查当前会话参数:
SELECT @@tmp_table_size, @@max_heap_table_size, @@join_buffer_size,确保三者匹配(tmp_table_size和max_heap_table_size必须设为相同值,取小者生效) - 批次大小别硬套“5 万”:若
EXPLAIN显示rows预估远超你设的批次,说明索引没生效或字段类型不一致(如INT对BIGINT) - 执行前后对比:
SHOW STATUS LIKE 'Created_tmp%',重点盯Created_tmp_disk_tables是否止涨 —— 它才是内存是否兜住的真实体温计 - 别忘了关掉无关干扰:存储过程里避免
DISTINCT、UNION、ORDER BY非索引字段,这些都会强制走临时表
EXPLAIN 里看到 type: ref,问题往往出在 JOIN 条件字段类型隐式转换或 NULL 值干扰上。

















