mysql中COUNT、SUM等聚合函数的优化方法

来源:这里教程网 时间:2026-02-28 20:42:35 作者:

为什么 COUNT(*) 比 COUNT(字段) 快得多

MySQL 对

COUNT(*)
有专门优化:在 InnoDB 中,如果没加
WHERE
条件且表无虚拟列、无全文索引,优化器可能直接读取聚簇索引的行数缓存(如
INFORMATION_SCHEMA.TABLES
中的
TABLE_ROWS
值),甚至跳过全表扫描。而
COUNT(字段)
必须逐行判断该字段是否为
NULL
,无法跳过数据页读取。

COUNT(*)
替代
COUNT(id)
COUNT(1)
—— 三者语义不同:
COUNT(1)
COUNT(*)
行为一致,但
COUNT(id)
会过滤掉
id IS NULL
的行
避免在大表上执行
COUNT(字段)
且该字段无索引、允许 NULL —— 这会强制回表或全扫描二级索引
若业务真需统计非空值数量,优先给该字段建
NOT NULL
约束 + 索引,让优化器有机会走覆盖索引

SUM/AVG 在没有索引时为什么慢得离谱

SUM()
AVG()
必须遍历所有匹配行并累加/计数,若过滤条件无法命中索引,就会触发全表扫描。更隐蔽的问题是:即使 WHERE 条件走了索引,只要 SELECT 列不在索引中,仍要回表读取数值字段 —— 回表放大 I/O 开销,尤其在高并发下容易成为瓶颈。

对高频聚合字段(如
amount
score
)建立覆盖索引,例如:
CREATE INDEX idx_user_status_amount ON orders (status, amount);
这样
SELECT SUM(amount) FROM orders WHERE status = 'paid'
可完全走索引,无需回表
避免在聚合函数中使用表达式,如
SUM(price * quantity)
—— 无法利用索引,且计算开销翻倍;考虑新增持久化计算列并为其建索引
注意
AVG()
实际等价于
SUM() / COUNT()
,但 MySQL 不会自动拆解优化;若分母可能为 0,记得用
NULLIF()
防止除零错误:
COALESCE(SUM(amount) / NULLIF(COUNT(*), 0), 0)

用近似值替代精确 COUNT 的真实场景

很多业务场景其实不需要精确总数,比如后台分页显示“共约 24.5 万条”,或监控大盘展示趋势。硬查

COUNT(*)
在千万级表上可能耗时数秒,而采样估算可在毫秒级返回。

启用
innodb_stats_method = 'nulls_unequal'
并调大
innodb_stats_persistent_sample_pages
(默认 20),让统计信息更准,提升执行计划可靠性
SELECT TABLE_ROWS FROM INFORMATION_SCHEMA.TABLES
查预估行数——仅适用于无频繁删改的静态表,且该值不实时更新
对超大表做分桶采样:
SELECT CEIL(COUNT(*) * 1000 / 10) FROM t_sample TABLESAMPLE SYSTEM (0.1);
注意
TABLESAMPLE
是 MySQL 8.0.23+ 才支持,且 SYSTEM 模式按页采样,结果偏差可控

GROUP BY + 聚合导致临时表和文件排序的典型诱因

GROUP BY
字段未被索引覆盖,或聚合字段与分组字段不在同一索引中,MySQL 很可能创建内部临时表(
Using temporary; Using filesort
),严重拖慢响应。

确保
GROUP BY
字段是联合索引最左前缀,且聚合字段包含在该索引中(覆盖索引),例如:
SELECT category, SUM(sales) FROM products GROUP BY category;
对应索引:
INDEX idx_category_sales (category, sales)
避免在
GROUP BY
中使用函数或表达式,如
GROUP BY DATE(created_at)
—— 会导致索引失效;改用生成列 + 索引
检查
tmp_table_size
max_heap_table_size
是否足够大,防止内存临时表被强制落盘成磁盘临时表(性能暴跌)

实际线上优化中最容易被忽略的一点:聚合查询的执行计划里出现

Using where; Using index for group-by
才算真正走覆盖索引;只要看到
Using temporary
,就说明当前索引设计没对上聚合模式,别只盯着单字段索引。

相关推荐