count(*)比count(字段)快因只统计行数、可走索引或元数据,而后者需判null并读取列内容;但字段允许null且业务需排除空值时不可替换,否则结果错误。

为什么 COUNT(*) 比 COUNT(字段) 快,但有时又不能换?
因为 COUNT(*) 只统计行数,优化器可直接走索引或元数据;而 COUNT(字段) 会跳过 NULL 值,必须读取实际列内容。在宽表、大分区表上,后者可能多扫几倍 I/O。
- 如果字段允许
NULL且业务逻辑真需要排除空值,别硬套COUNT(*)—— 结果错比慢更严重 - 常见误用:用
COUNT(id)替代COUNT(*),以为主键非空就安全;但若id是INT类型且含0(非NULL),COUNT(id)仍会统计它,和业务语义可能不一致 - 验证方法:执行
EXPLAIN FORMAT=JSON看rows_examined和是否出现Using index for count
GROUP BY 里加了 ORDER BY 却没生效?检查隐式排序失效场景
MySQL 8.0+ 默认关闭隐式排序,PostgreSQL 从不保证 GROUP BY 输出顺序;即使旧版 MySQL 曾“看起来有序”,也不该依赖——这是典型伪稳定。
- 显式写
ORDER BY:哪怕和GROUP BY字段相同,也必须重复写一遍,例如GROUP BY dept_id ORDER BY dept_id - 报表导出时顺序错乱,常因中间层(如 BI 工具缓存)或连接池复用导致执行计划变化,不是 SQL 写错了,而是没加
ORDER BY - 如果只想要最新一条记录的聚合(比如每个部门最新一笔销售额),别用
GROUP BY + ORDER BY + LIMIT,改用窗口函数:ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY create_time DESC)
WHERE 条件里用函数导致索引失效,但有些情况能绕过去
像 WHERE DATE(create_time) = '2024-01-01' 会让 create_time 索引完全失效;但不是所有函数都不可优化。
- 优先改写为范围查询:
WHERE create_time >= '2024-01-01' AND create_time -
YEAR()、MONTH()这类确定性函数,在 MySQL 5.7+ 支持函数索引(需建INDEX idx_year ON t1 ((YEAR(create_time)))),但注意 PostgreSQL 不支持函数索引字段直接用于=查询,得配合表达式索引+重写条件 - 避免在大表
WHERE中用LIKE '%xxx'或IN (子查询)—— 前者无法走 B-tree 索引,后者可能触发嵌套循环,查千万级数据时秒变分钟级
聚合前先 FILTER 还是聚合后 HAVING?性能差十倍不止
HAVING 是对已聚合结果再过滤,意味着所有分组都得算完;而 WHERE 或 FILTER(PostgreSQL)是在聚合前筛掉无关行,I/O 和 CPU 都省得多。
- 想查“订单金额超 10 万的客户数”,别写
HAVING SUM(amount) > 100000,先用WHERE amount > 0(或其他高选择率条件)缩小扫描集 - PostgreSQL 支持
COUNT(*) FILTER (WHERE status = 'paid'),比先GROUP BY再HAVING快得多,且语义清晰 - MySQL 没原生
FILTER,可用SUM(IF(status='paid', 1, 0))替代,但注意IF在WHERE里不生效,必须放在聚合函数内
最常被忽略的是统计口径和执行计划的耦合:同一句 SQL 在不同数据分布下,优化器可能选错索引或临时表策略,尤其当分区表里某几个分区数据量突增时。上线前一定要用真实数据量级压测,而不是只看开发环境 EXPLAIN 的预估行数。










