mysql 8.0 支持 count() over(),但不替代 count() 全局统计:它保留明细行,每行返回当前结果集总行数(where 后、limit 前),适用于需同时展示明细与总数的场景,而非单纯获取一行总数。

MySQL 8.0 确实支持 COUNT(*) OVER(),但不能替代 COUNT(*) 做全局行数统计
COUNT(*) OVER() 是窗口函数,它不会压缩结果集——每行都会返回当前窗口内的计数值。如果你期望“查一次得总数”,它会返回 N 行、每行都是同一个数字,而不是一行结果。这和传统聚合函数行为完全不同。
- 窗口函数不改变行数,只在每行上追加计算列
-
COUNT(<em>) OVER()</em>默认等价于COUNT() OVER(ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING),即全表范围统计 - 若未加
PARTITION BY,它统计的是整个结果集的行数(不含WHERE过滤前的原始表) - 实际执行时,MySQL 需扫描全部匹配行,性能开销接近全表扫描,不是“轻量级总数获取”
例如:
SELECT id, name, COUNT(*) OVER() AS total FROM users WHERE status = 'active';
若 127 行满足条件,结果就是 127 行,每行 total 都是 127。
什么时候该用 COUNT(*) OVER(),而不是 SELECT COUNT(*)?
核心判断依据:你是否需要同时保留明细数据 + 附带总数。
- ✅ 适合场景:分页列表页显示“共 XX 条”,且后端不希望多查一次
SELECT COUNT(*) - ✅ 适合场景:报表中每行显示「当前用户占总活跃用户比例」,需同时访问明细和总数
- ❌ 不适合场景:仅需总数(如后台管理仪表盘),此时
SELECT COUNT(*) FROM ...更快、更省内存 - ❌ 不适合场景:配合
LIMIT做“前 10 条 + 总数”,因为OVER()仍会处理所有匹配行,LIMIT在窗口计算之后才生效
注意:OVER() 的计算发生在 ORDER BY 和 LIMIT 之前,所以即使你写:
SELECT id, COUNT(*) OVER() FROM t ORDER BY id LIMIT 5;
MySQL 依然会统计整个 t 中符合条件的全部行数,不是只算前 5 行。
COUNT(*) OVER() 和 COUNT(*) OVER(PARTITION BY ...) 容易混淆的点
初学者常误以为 OVER() 没写 PARTITION BY 就等于“按整张表统计”,其实它统计的是当前查询结果集的行数(即 FROM+WHERE 后的结果),不是原表总行数。
-
COUNT(*) OVER()统计的是该 SELECT 语句最终输出行数(未被LIMIT截断前) -
COUNT(*) OVER(PARTITION BY category)每个 category 内单独计数,行数不变,只是每行的值变成其所属组的大小 - 如果
WHERE过滤掉大量数据,OVER()结果会显著小于SELECT COUNT(*) FROM table - 使用
EXPLAIN可观察到:窗口函数通常触发Using temporary; Using filesort,尤其在无合适索引时性能下降明显
替代方案:想高效获总数,优先考虑覆盖索引或缓存
COUNT(*) OVER() 不是性能优化手段,反而是潜在瓶颈。真实业务中更推荐:
- 对高频访问的总数(如“文章总数”),用单独字段维护(如
stats.total_posts),写时更新 - 若必须实时,且表有主键或非空唯一索引,
SELECT COUNT(*) FROM t在 MySQL 8.0 中对 InnoDB 很快(走索引统计) - 复杂条件下的总数,可建物化视图(MySQL 8.0 不原生支持,可用汇总表+触发器或应用层双写)
- 分页场景下,用
SQL_CALC_FOUND_ROWS已被弃用,改用SELECT COUNT(*)单独查 + 缓存 5–10 秒
最后提醒:窗口函数虽强大,但别为“省一次查询”强行塞进不匹配的场景。总数统计本身很简单,复杂的是它的上下文——到底是静态总量、条件动态量,还是分组内占比。搞清这个,才能选对函数。











