COUNT()统计所有行(含NULL),COUNT(字段)跳过该字段为NULL的行;JOIN导致重复时COUNT()虚高,应改用COUNT(DISTINCT主键)或EXISTS校验。

为什么 COUNT(*) 和 COUNT(字段) 结果不一致?
因为 COUNT(*) 统计所有行(包括含 NULL 的行),而 COUNT(字段) 会自动跳过该字段值为 NULL 的记录。这不是 bug,是 SQL 标准定义的行为。
- 当你怀疑数据“少算”了,先检查目标字段是否存在大量
NULL - 聚合前没加
WHERE过滤,但业务上其实只关心非空状态(比如COUNT(email)统计“有邮箱的用户数”,不是“总用户数”) - 在宽表或 ETL 中间层,字段补空逻辑不统一,导致同一语义字段在不同来源中
NULL含义混乱
JOIN 后 COUNT 突然翻倍,怎么定位?
本质是笛卡尔积副作用:主表一行匹配从表多行时,COUNT(*) 会把每条 JOIN 结果都算一次。
- 先用
SELECT *+LIMIT 10看实际 JOIN 后有多少重复主键 - 改用
COUNT(DISTINCT 主键)是最直接的修复,但注意性能——大表上DISTINCT可能触发临时磁盘排序 - 如果只是校验关联完整性,优先用
EXISTS或LEFT JOIN ... WHERE 右表.主键 IS NULL,比聚合更轻量
时间字段用 BETWEEN 还是 >= AND
用 >= AND 更可靠,尤其涉及 TIMESTAMP 或带毫秒的字段。
-
BETWEEN '2024-01-01' AND '2024-01-31'实际等价于(取决于类型精度),容易漏掉最后一秒的微秒级数据 created_at >= '2024-01-01' AND created_at 明确闭左开右,边界无歧义,也方便拼接时间窗口- 某些数据库(如 MySQL 5.6)对
BETWEEN在索引使用上更保守,可能导致本可走索引的查询退化为全表扫描
为什么加了索引,执行计划还是没走?
索引存在 ≠ 查询会用。常见干扰项:
- 条件里对字段用了函数,比如
WHERE YEAR(order_time) = 2024,导致索引失效;应改写为order_time >= '2024-01-01' AND order_time - 谓词选择率太高(比如
WHERE status != 'cancelled'匹配 95% 行),优化器判定全表扫描更快 - 复合索引字段顺序和查询条件不匹配,例如索引是
(a, b, c),但查询只用了b和c,无法命中 - 统计信息陈旧,执行
ANALYZE TABLE(MySQL)或VACUUM ANALYZE(PostgreSQL)再看执行计划
可信不是靠“写得出来”,是靠每次结果可复现、边界可推演、变更可预判。最容易被忽略的,是把“查得出来”当成“查得对”——而真正的断点,往往藏在 NULL、时区、隐式类型转换和统计信息滞后这些安静的地方。









