SQL报表中DISTINCT去重慢的主因是未提前过滤数据,优化需先WHERE后DISTINCT、用GROUP BY替代、建覆盖索引、大数据量时预聚合。

SQL报表中用 DISTINCT 做去重统计慢,核心问题往往不是 DISTINCT 本身,而是它被迫在大量未过滤、未索引、未裁剪的数据上执行全量去重。优化关键在于“提前减少数据量”和“让数据库能走索引”。
先 WHERE 再 DISTINCT,别反过来
常见误区:先 SELECT DISTINCT * FROM table,再在应用层或子查询里加条件。这会让数据库扫描全表、生成巨大中间结果集,再从中去重。
- ✅ 正确做法:把时间范围、状态、类型等强过滤条件写在 WHERE 子句最前面,大幅缩小扫描行数
- ✅ 示例:统计近7天活跃用户数,写成 WHERE create_time >= NOW() - INTERVAL 7 DAY,而不是查全表再用 DATE() 函数过滤
- ⚠️ 注意:WHERE 中避免对字段做函数操作(如 WHERE YEAR(create_time) = 2024),否则索引失效
用 GROUP BY 替代 DISTINCT(当只需计数时)
如果目标只是“有多少个不同值”,比如 COUNT(DISTINCT user_id),多数场景下 COUNT(*) + GROUP BY 的执行计划更可控,尤其配合覆盖索引时。
- ✅ 尝试改写:SELECT COUNT(*) FROM (SELECT user_id FROM t_log WHERE dt = '2024-06-01' GROUP BY user_id) t
- ✅ 优势:GROUP BY 可利用联合索引(如 (dt, user_id)),而 DISTINCT 在某些版本 MySQL 中无法有效使用该索引
- ✅ 补充:MySQL 8.0+ 对 COUNT(DISTINCT) 有优化,但前提仍是字段上有合适索引且 WHERE 条件高效
建好覆盖索引,让 DISTINCT 不碰表数据
DISTINCT 慢的另一个主因是回表——查完索引还要去聚簇索引找完整行。若只涉及几个字段,建覆盖索引可彻底避免 I/O。
- ✅ 场景举例:报表语句为 SELECT DISTINCT dept_id, job_title FROM emp WHERE status = 1
- ✅ 推荐索引:INDEX idx_status_dept_job (status, dept_id, job_title),三个字段顺序按“过滤→分组→输出”排列
- ✅ 验证方式:EXPLAIN 查看 Extra 是否出现 “Using index”(表示索引覆盖),而非 “Using temporary; Using filesort”
大数据量时考虑预聚合或物化中间结果
当单次 DISTINCT 查询稳定耗时 >1s 且被高频调用(如小时级报表),硬优化 SQL 效果有限,应转向架构层面降维。
- ✅ 每小时跑一次:INSERT INTO rpt_user_daily (dt, dept_id, cnt) SELECT CURDATE(), dept_id, COUNT(*) FROM emp WHERE update_time >= SUBDATE(NOW(), INTERVAL 1 HOUR) GROUP BY dept_id
- ✅ 查询时直接 SELECT SUM(cnt) FROM rpt_user_daily WHERE dt = '2024-06-01'
- ✅ 优势:把昂贵的去重/分组计算变成轻量聚合查询,响应从秒级降到毫秒级










