MySQL 8.0 的 COUNT() 等聚合查询几乎从不走并行扫描,因其需满足极苛刻条件:必须是普通 InnoDB 表、无任何索引可用的全表扫描(type=ALL)、无 GROUP BY/LIMIT/ORDER BY/DISTINCT、仅最外层为 COUNT()、数据量超300万行且无法被缓冲池容纳;否则优化器插入聚合节点导致并行失效。

MySQL 8.0 的 COUNT(*)、SUM() 等聚合查询**几乎从不走并行扫描**,除非满足极苛刻的“无索引 + 全表扫描 + 无 GROUP BY + 无 LIMIT + 最外层节点”条件。盲目调大 innodb_parallel_read_threads 对这类查询基本无效。
为什么 COUNT(*) 通常不并行?
MySQL 8.0 的并行能力只下推到 InnoDB 存储引擎的物理页读取层,且仅对**最外层执行节点为全表扫描**的语句生效。而聚合函数一旦出现,优化器会强制插入一个聚合节点(如 stream_aggregate),导致扫描被包裹在子节点中——EXPLAIN FORMAT=TREE 就看不到 Parallel scan 提示。
常见失效场景包括:
-
COUNT(id)或COUNT(col):因需判空,触发回表或额外过滤,优化器跳过并行路径 -
COUNT(*) FROM t WHERE status = 'active':若status有索引,直接走索引查找;若无索引,仍可能因统计信息误判或行数不足百万而拒绝并行 -
SELECT SUM(amount) FROM huge_table:即使无索引,8.0.37 前版本默认不将 SUM 下推至 worker,仍单线程累加
哪些聚合查询真能触发 Parallel scan?
必须同时满足以下全部条件,才可能看到 EXPLAIN FORMAT=TREE 输出中的 Parallel scan:
- 表是普通 InnoDB 表(非分区、非临时表)
- WHERE 条件完全无法使用任何索引(即
type=ALL),且无函数、类型转换、OR 等复杂逻辑 - 查询不含
GROUP BY、LIMIT、ORDER BY、DISTINCT、子查询、窗口函数 - 聚合仅是
COUNT(*),且出现在最外层(不能嵌套在派生表或 CTE 中) - 表数据量足够大(实测通常 > 300 万行,且
innodb_buffer_pool_size无法缓存整张表)
例如:SELECT COUNT(*) FROM logs_2025 WHERE level = 'ERROR' —— 仅当 level 列无索引、logs_2025 是普通 InnoDB 表、且该查询是独立语句时,才有可能并行。
如何验证并行是否真正工作?
光看 EXPLAIN FORMAT=TREE 不够,必须结合运行时行为确认:
- 执行
EXPLAIN ANALYZE SELECT COUNT(*) FROM t,搜索输出中是否有"parallel_workers": N(N > 1)和"rows_examined_per_worker"字段;若缺失或 N == 1,说明未启用 - 查询执行期间,运行
SELECT THREAD_ID, PROCESSLIST_INFO FROM performance_schema.threads WHERE PROCESSLIST_INFO LIKE '%COUNT%',观察是否出现多个query exec类型线程(而非仅一个) - 对比开启/关闭前后
SHOW STATUS LIKE 'Innodb_rows_read'的增长速率:并行下该值应显著高于Handler_read_rnd_next,说明读取被分摊
注意:innodb_parallel_read_threads 是会话级参数,设完后必须执行**新查询**才生效;SET GLOBAL 不影响已有连接,也不推荐用于此参数。
更现实的提速路径是什么?
对聚合类查询,与其纠结并行扫描,不如优先检查三件事:
- 这张表有没有合适的覆盖索引?比如
COUNT(*)可建INDEX(status),SUM(amount)可建INDEX(status, amount)—— 覆盖索引让扫描变快十倍,比并行有效得多 - 统计信息是否过期?执行
ANALYZE TABLE t,避免优化器因误估行数而放弃并行或选错索引 - 是不是真需要实时精确聚合?考虑用汇总表(如按天预计算)、物化视图(8.0.23+)或应用层定时任务替代高频
COUNT(*)
并行扫描不是银弹,它只在“别无选择只能全扫”的边缘场景里起效。绝大多数慢聚合,根源不在扫描不够快,而在不该扫、不该这么扫。


















