UNION ALL过多会因内存不足触发磁盘临时表导致IO暴增;应改用分片INSERT INTO ... SELECT写入带主键的中间表,并确保各子查询显式下推WHERE条件。

为什么UNION ALL太多会导致IO暴增
UNION ALL本身不排序、不去重,但MySQL仍需把每个子查询结果暂存到临时表再合并。当子查询超过5个、单个结果集超10万行时,tmp_table_size和max_heap_table_size很容易被撑爆,触发磁盘临时表(On disk temporary table),IO直接翻倍。这不是语法问题,是内存管理机制的硬限制。
用INSERT INTO ... SELECT替代UNION ALL链
把多个SELECT拆成独立语句写入同一张中间表,能绕过UNION ALL的统一缓冲区调度,让每个INSERT走自己的执行路径,减少锁竞争和内存争抢。
- 先建带主键/唯一索引的中间表,避免后续去重开销:
CREATE TABLE tmp_merged (id BIGINT PRIMARY KEY, name VARCHAR(64), ts DATETIME) - 每个
INSERT加IGNORE或ON DUPLICATE KEY UPDATE处理主键冲突,比UNION ALL后手工去重更可控 - 批量插入时用
INSERT INTO ... SELECT ... LIMIT 10000分片,防止单次事务过大卡住binlog - 确认
innodb_buffer_pool_size足够——中间表写入频繁时,它比tmp_table_size影响更大
UNION ALL子查询必须各自带WHERE下推
外部WHERE条件不会自动下推到每个SELECT里。比如SELECT * FROM t1 UNION ALL SELECT * FROM t2 WHERE dt > '2026-01-01',t1那部分仍会全表扫。
- 每个子查询都显式加上相同过滤条件:
SELECT * FROM t1 WHERE dt > '2026-01-01' UNION ALL SELECT * FROM t2 WHERE dt > '2026-01-01' - 如果条件含参数(如
@start_dt),确保每个子查询都引用该变量,别漏掉 - 对日期字段优先用
BETWEEN而非>= AND ,MySQL 8.0+对前者有更好的范围估算
临时表字段类型不一致会隐式转换拖慢速度
UNION ALL要求所有子查询列数、顺序、数据类型严格一致。一旦某张表的status是TINYINT,另一张是VARCHAR(10),MySQL会把整列转成VARCHAR,不仅增大临时表体积,还让索引失效。
- 建中间表前,用
SELECT ... UNION ALL SELECT ... LIMIT 0查出各列实际类型,手动对齐 - 数值类字段统一用
INT或BIGINT,避免DECIMAL混入;字符串统一VARCHAR并设合理长度 - 时间字段全部用
DATETIME(3),别有的用TIMESTAMP有的用DATE
真正卡住性能的往往不是UNION ALL语法本身,而是中间结果在内存与磁盘之间反复倒腾,以及字段类型隐式转换带来的索引失效——这两点在存储过程中最容易被忽略。

















