UNION ALL 本身不并行,但各子查询可因资源不争用而近似并发执行;关键在于优化每个子查询的索引、WHERE下推、列裁剪和覆盖索引,避免全表扫描与磁盘临时表。

UNION ALL 本身不并行,但子查询可以各自走索引并发执行
MySQL 8.0 的 UNION ALL 操作本身没有“并行执行计划”——优化器不会把一个 UNION ALL 拆成多个线程同时跑完再合并。但它天然具备并发潜力:每个 SELECT 子查询是独立的,只要它们访问的表、索引、缓冲池资源不严重争用,InnoDB 就可能让这些子查询在不同内核上近似并发执行(取决于系统负载、innodb_thread_concurrency、I/O 调度等)。关键不是“MySQL 并行 UNION”,而是“别拖慢任何一个子查询”。
- 每个子查询必须能快速定位数据,否则快的等慢的,整体就卡住
- 如果某个子查询触发全表扫描或临时磁盘表,它会成为瓶颈,其他子查询再快也白搭
-
EXPLAIN看到的仍是串行计划(type=ALL 或 Extra=Using temporary),但这不代表实际执行时不能并发读页
为什么 EXPLAIN 显示不出并行,却仍要关注每个子查询的执行效率
EXPLAIN 输出的是逻辑执行计划,不是物理调度视图。它告诉你“怎么算”,不告诉你“几个核一起算”。所以你看到 UNION ALL 的 EXPLAIN 结果里每个 SELECT 都是独立的 select_type=PRIMARY 或 UNION,但无法判断它们是否真正在 OS 级别被调度为并发任务。真正影响响应时间的是最慢那个子查询的 I/O 和 CPU 开销。
- 用
SHOW PROFILE或 performance_schema 查看各阶段耗时,比只看EXPLAIN更准 - 检查
innodb_buffer_pool_size是否足够——如果多个子查询都要刷脏页或等待 buffer pool mutex,就会隐式串行化 - 避免子查询共用同一张大表且都走相同二级索引,容易引发索引页争用(尤其是高并发下)
真正提升大型 UNION 效率的实操动作
与其期待 MySQL 自动并行,不如把力气花在让每个分支都轻量、确定、可预测。
- 强制
WHERE下推:不要写(SELECT ... FROM t1) UNION ALL (SELECT ... FROM t2) WHERE x > 100,而要拆成SELECT ... FROM t1 WHERE x > 100 UNION ALL SELECT ... FROM t2 WHERE x > 100 - 删掉所有
SELECT *,只取业务真正需要的列;宽表 +UNION ALL是内存杀手 - 为每个子查询中
WHERE/ORDER BY涉及的字段建覆盖索引,例如INDEX(status, created_at, id)覆盖WHERE status = ? AND created_at > ? ORDER BY created_at DESC LIMIT 10 - 如果结果集最终要
ORDER BY或LIMIT,且子查询数据量差异极大,考虑在快的子查询里先LIMIT,慢的子查询再单独优化
tmp_table_size 和 max_heap_table_size 不只是“够不够”,而是“要不要落盘”
当 UNION ALL 后接 ORDER BY 或 DISTINCT(哪怕你没写,某些隐式场景也会触发),MySQL 必须把全部结果暂存再处理。这时如果中间结果超过 tmp_table_size 或 max_heap_table_size 中的较小值,就会写磁盘临时表——性能断崖式下跌。
- 这两个值必须设为相等,否则以小者为准;建议从
256M起调,根据SHOW STATUS LIKE 'Created_tmp_disk_tables'增加 - 注意:这是会话级限制,
SET SESSION tmp_table_size = 512*1024*1024可临时生效,但别在应用层无差别设置,易引发内存争用 - 更治本的办法是避免在
UNION ALL外套排序——把排序逻辑下沉到每个子查询内部,或改用应用层归并排序
UNION 的,往往不是语法本身,而是某个子查询悄悄走了全表扫描,或者临时表被迫落盘后反复读写 SSD。优化动作要落在每一行 SQL 的执行路径上,而不是寄希望于数据库“自动并行”。


















