SQL 聚合查询性能优化方法

1次阅读

count(*)慢的主因是where未走索引导致全表扫描;应通过explain检查type是否为all/index,优先使用ref/range;复合索引需按查询顺序创建,避免在索引字段上使用函数。

SQL 聚合查询性能优化方法

WHERE 条件没走索引,COUNT(*) 会慢得离谱

聚合查询卡在 COUNT(*)SUM() 上,大概率不是函数本身的问题,而是数据库先全表扫描再聚合。关键看 WHERE 子句能否命中索引。

  • 检查执行计划:用 EXPLAINtype 是否为 ALL(全表)或 index(全索引扫描),理想是 ref/range
  • 复合索引要注意字段顺序:比如查询 WHERE status = 'active' AND created_at > '2024-01-01',索引应建为 (status, created_at),反过来效果差很多
  • 避免在索引字段上用函数:写成 WHERE date(created_at) = '2024-01-01' 会让索引失效,改成 WHERE created_at >= '2024-01-01' AND created_at

GROUP BY 字段没索引,临时表和 filesort 就来了

GROUP BY 不只是分组逻辑,它直接触发排序、临时表甚至磁盘临时表(using temporary; Using filesort)。索引能绕过大部分开销。

  • 单字段 GROUP BY user_id,确保 user_id 有索引(最好是主键或唯一索引)
  • 多字段 GROUP BY category, region,建联合索引 (category, region),顺序要和 GROUP BY 一致
  • 如果 select 里有非分组字段又没聚合(如 SELECT name, COUNT(*) FROM t GROUP BY user_id),mysql 5.7+ 默认报错,需关掉 sql_mode=ONLY_FULL_GROUP_BY 或改写

用 COUNT(1) 还是 COUNT(*)?其实没区别,但 COUNT(col) 要小心 NULL

很多人以为 COUNT(1)COUNT(*) 快,实际优化器都转成一样的执行路径——只要不涉及具体列,它们性能完全一致。

  • COUNT(*)COUNT(1) 都统计行数,包括 NULL
  • COUNT(col) 只统计 col IS NOT NULL 的行,如果该列允许 NULL 且空值多,性能会略差(要逐行判断)
  • 想快速估算大表总行数?别用 COUNT(*),查 information_schema.tablestable_rows(注意:InnoDB 是估算值,不一定准)

聚合结果集太大导致内存溢出或网络阻塞

不是所有慢都因为计算,有时是结果本身太胖:比如 GROUP BY day 返回 365 行还行,GROUP BY user_id 返回百万行就危险了。

  • LIMIT 前务必确认业务是否允许截断(SELECT COUNT(*) FROM t GROUP BY user_id LIMIT 10 很容易误导)
  • SQL_CALC_FOUND_ROWS 已被弃用,替代方案是单独跑一次 SELECT COUNT(DISTINCT user_id) FROM t
  • 聚合后还要 JOIN 其他表?优先考虑把聚合结果物化成临时表或 CTE,避免重复计算(尤其在子查询里嵌套 GROUP BY

复杂点往往不在聚合函数本身,而在它前面的过滤条件、后面的分组维度,以及整个查询链路中有没有隐式类型转换或隐式排序。一个 EXPLAIN 多看两眼,比调参有用得多。

text=ZqhQzanResources