Skip to main content
☘️ Septvean's Documents
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

PostgreSQL COUNT

内容:用法 -> 行为差异 -> 性能 -> 最佳实践

一、SELECT COUNT() 一共有几种常见用法?

在 PostgreSQL / MySQL(以及 SQL 标准)中,常见 4 种:

写法 是否统计 NULL 是否统计行 是否等价
COUNT(*) ✅ 包含 ⭐ 推荐
COUNT(1) ✅ 包含
COUNT(col) ❌ 排除 NULL ⚠️
COUNT(DISTINCT col) ❌ 排除 NULL

二、每种 COUNT 的详细解释(重点)

1️⃣ COUNT(*) —— 最佳实践

SELECT COUNT(*) FROM orders;

行为

  • 统计 行数
  • 包含 NULL
  • PostgreSQL 内部优化最充分

PostgreSQL 内部实现

  • 不取任何列
  • 不关心列值
  • 只统计 tuple(行)

📌 这是标准、最快、最安全的写法

2️⃣ COUNT(1) —— 语义等价,但不推荐

SELECT COUNT(1) FROM orders;

行为

  • 统计行数
  • 1 永远非 NULL

和 COUNT(*) 的区别?

项目 COUNT(*) COUNT(1)
SQL 标准
语义清晰
优化程度
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐

📌 在 PostgreSQL 中性能几乎相同,但没有任何优势

3️⃣ COUNT(col) —— 只统计非 NULL

SELECT COUNT(email) FROM users;

行为

  • ❌ 忽略 NULL
  • 只统计 email IS NOT NULL

等价写法

SELECT COUNT(*) FROM users WHERE email IS NOT NULL;

使用场景

  • 统计“已填写某字段”的数量
  • 数据完整性分析

⚠️ 千万不要用它代替 COUNT(*)

4️⃣ COUNT(DISTINCT col) —— 去重统计

SELECT COUNT(DISTINCT user_id) FROM orders;

行为

  • 去重
  • 忽略 NULL
  • 性能最差的一种

常见误区

COUNT(DISTINCT a, b)   -- PostgreSQL 不支持

正确写法:

COUNT(DISTINCT (a, b))

三、COUNT 的性能对比(非常重要)

PostgreSQL 中的真实情况

写法 性能 说明
COUNT(*) ⭐⭐⭐⭐⭐ 最优
COUNT(1) ⭐⭐⭐⭐ 无优势
COUNT(col) ⭐⭐⭐ 需判断 NULL
COUNT(DISTINCT col) 排序 / Hash

四、COUNT + WHERE 的典型用法

SELECT COUNT(*)
FROM orders
WHERE created_at >= CURRENT_DATE;

📌 索引 + COUNT(*) 非常高效

五、COUNT + JOIN 的正确姿势(重点)

❌ 错误写法(重复行)

SELECT COUNT(*)
FROM orders o
JOIN order_items i ON o.id = i.order_id;

一条订单多条 item,会被放大

✅ 正确写法(统计主表)

SELECT COUNT(DISTINCT o.id)
FROM orders o
JOIN order_items i ON o.id = i.order_id;

或者:

SELECT COUNT(*)
FROM orders o
WHERE EXISTS (
    SELECT 1 FROM order_items i WHERE i.order_id = o.id
);

六、COUNT 在大表中的优化技巧

1️⃣ 不要频繁 COUNT(*) 超大表

SELECT COUNT(*) FROM logs;  -- 上亿行,慢

替代方案

SELECT reltuples::bigint
FROM pg_class
WHERE relname = 'logs';

📌 估算值,毫秒级

2️⃣ TimescaleDB / 分区表

SELECT COUNT(*) FROM logs
WHERE time >= now() - interval '1 day';

👉 只扫描相关分区

七、COUNT(*) vs EXISTS(业务场景)

❌ 判断是否存在(错误)

SELECT COUNT(*) > 0 FROM users WHERE email = 'a@b.com';

✅ 正确写法

SELECT EXISTS (
    SELECT 1 FROM users WHERE email = 'a@b.com'
);

🚀 EXISTS 会提前返回

八、COUNT 在 ORM 中的最佳实践(SQLAlchemy)

from sqlalchemy import select, func

stmt = select(func.count()).select_from(User)
count = session.scalar(stmt)

📌 永远用 func.count() 而不是 count(id)

九、总结

  • COUNT(*) 统计行数,是 SQL 标准中最优、最安全的写法
  • COUNT(col) 只统计非 NULL
  • COUNT(DISTINCT col) 用于去重但性能最差
  • 判断是否存在应使用 EXISTS 而不是 COUNT(*)

十、最终最佳实践清单(建议收藏)

  • ✅ 统计行数 -> COUNT(*)
  • ✅ 统计非空字段 -> COUNT(col)
  • ✅ 去重统计 -> COUNT(DISTINCT col)
  • ❌ 不要用 COUNT 判断存在性
  • ❌ 不要滥用 COUNT 在超大表

PostgreSQL 统计表的行数有多少条,有几种方式?每天统计一次推荐用什么方式?

在 PostgreSQL 中统计表的行数,确实有多种方式,每种方式适用场景不同,尤其是数据量很大时,效率差异非常大。下面我整理成详细说明:

一、统计表行数的几种方式

方法 示例 SQL 是否精确 适用场景 优缺点
COUNT(*) SELECT COUNT(*) FROM table_name; ✅ 精确 小表、偶尔统计 简单直观,但全表扫描,数据量大时慢
COUNT(1) / COUNT(pk) SELECT COUNT(id) FROM table_name; ✅ 精确 小表、偶尔统计 效果和 COUNT(*) 类似,性能几乎一样
pg_class.reltuples SELECT reltuples::BIGINT AS approx_count FROM pg_class WHERE relname=‘table_name’; ❌ 估算 大表、快速 很快,不访问表,只读取统计信息;不精确,可能与真实值差距较大
ANALYZE + pg_stat_all_tables ANALYZE table_name; SELECT n_live_tup FROM pg_stat_all_tables WHERE relname=‘table_name’; ❌ 估算 大表、快速 需要先执行 ANALYZE,返回的 n_live_tup 基于统计信息;比 pg_class.reltuples 更接近真实值
自建物化计数表 通过触发器维护:INSERT/DELETE 时更新计数表 ✅ 实时 高频统计 数据量极大或高并发场景最稳妥,但需要维护额外表

二、每天统计一次推荐用哪种方式?

  1. 大表(千万条以上):

    • 不推荐直接 COUNT(*),会全表扫描
    • 推荐使用统计信息查询:
    -- 方法 1:pg_stat_all_tables
    ANALYZE table_name;  -- 可放在 cron 执行
    SELECT n_live_tup FROM pg_stat_all_tables WHERE relname='table_name';
    
    • 快速,消耗低

    • 误差通常在 1%-5% 范围内

    • 如果需要更精确,可定时使用 COUNT(*) 在夜间低峰执行

  2. 小表(几万条以下):

    • 直接 SELECT COUNT(*) FROM table_name; 就够
  3. 非常高并发系统:

    • 建议 物化计数表 + 触发器 或 事件队列 维护计数
    • 查询直接读取计数表,毫秒级返回

三、总结建议

  • 统计精确行数 -> COUNT(*)(慢)
  • 快速估算行数 -> pg_stat_all_tables.n_live_tup 或 pg_class.reltuples
  • 每天定时统计大表行数 -> 使用 ANALYZE + n_live_tup,存到监控表
  • 实时统计 + 高并发 -> 建计数表 + 触发器