COUNT()是最稳妥通用的行数统计方式,不依赖列是否为NULL,主流数据库均优化其执行;COUNT(id)可能漏计NULL行,COUNT(1)语义等价但可读性差;带WHERE时必须用COUNT(),避免用COUNT(col)导致误过滤;分页应避免重复COUNT(*),可改用游标分页或缓存总数。

直接用 COUNT(*) 就行,别绕弯
想查一张表有多少行,COUNT(*) 是最稳妥、最通用的选择。它不依赖任何列是否为 NULL,也不受索引影响,数据库引擎会走最优路径(比如用元数据或最小索引扫描),不是真去逐行读数据——除非你加了 WHERE 条件。
常见错误是写成 COUNT(id) 或 COUNT(1),以为更快或更“规范”。其实:COUNT(*) 在所有主流数据库(PostgreSQL、MySQL 8.0+、SQL Server、Oracle)中都被明确优化过;而 COUNT(1) 和 COUNT(*) 语义等价,但可读性差;COUNT(id) 如果 id 允许为 NULL,结果就少于真实行数。
-
COUNT(*)统计所有行,包括含NULL值的行 - 带
WHERE条件时,必须写COUNT(*),不能省略* - 在 MyISAM 引擎下,
COUNT(*)可能直接返回缓存值,极快;InnoDB 则需扫描(但仍是最快方式)
带条件统计时,COUNT(*) 和 COUNT(1) 没区别,但别用 COUNT(col)
你要查“已支付订单数”,写 SELECT COUNT(*) FROM orders WHERE status = 'paid' 就够了。COUNT(1) 在这里和 COUNT(*) 完全等效,执行计划一样,没必要换。
真正容易踩坑的是写成 COUNT(status)。如果某行 status 是 NULL,哪怕它满足 WHERE 条件(比如 WHERE status IS NULL),COUNT(status) 也会跳过它——因为 COUNT(col) 只统计非 NULL 值。
- 条件统计必须搭配
WHERE,而不是靠COUNT(col)过滤 -
COUNT(CASE WHEN ... THEN 1 END)才适合“条件内计数”,比如统计不同状态占比 - 如果表很大且无合适索引,
WHERE条件字段没索引,COUNT(*)会变慢——这是查询设计问题,不是COUNT本身的问题
分页场景下,别用 COUNT(*) 查总页数
做列表分页时,很多人习惯先 SELECT COUNT(*) FROM t WHERE ...,再 SELECT * FROM t WHERE ... LIMIT 20 OFFSET 40。这看似合理,实际浪费严重:两次执行几乎相同的扫描逻辑,尤其当 WHERE 条件复杂或数据量大时,延迟翻倍。
更务实的做法是:前端只显示“下一页”按钮,不显示总页数;或者用游标分页(WHERE id > ? ORDER BY id LIMIT 20),彻底避开总数统计。
- 某些 ORM(如 Django 的
QuerySet.count())会悄悄触发一次COUNT(*),要注意调用时机 - PostgreSQL 支持
EXPLAIN (BUFFERS)看实际扫描行数,比COUNT(*)更轻量(但非精确) - 如果业务真需要总数,且表稳定、更新少,可把结果缓存几秒,避免每次请求都算
MySQL 中 SHOW TABLE STATUS 的 Rows 字段不准
有人图快,用 SHOW TABLE STATUS LIKE 'users' 查 Rows 字段,以为这就是实时总行数。错——这个值是 InnoDB 的估算值,来自采样统计,误差可能达 40% 以上,尤其在频繁写入或刚 ANALYZE TABLE 后未刷新时。
它唯一适用的场景是:快速判断一张表“大概是不是空的”或“是不是千万级”,不能用于分页、配额校验、报表统计等需要精确数字的地方。
-
Rows不受事务隔离级别影响,但也不受最新INSERT/DELETE影响 - MyISAM 表的
Rows是准确的,但 MyISAM 已基本淘汰 - 想看估算精度,可执行
SELECT table_rows FROM information_schema.tables WHERE table_name = 't',和SHOW TABLE STATUS本质一样
事情说清了就结束。COUNT 本身没玄机,真正的复杂点永远在“要不要 COUNT”和“COUNT 之前那条 WHERE 写对没”。

















