SELECT count(*) FROM events;
-- Time: 38412.551 ms
Thirty-eight seconds to count rows. Doesn't the database know how many rows a table has? In MySQL's MyISAM (the old engine) it really did, so people coming from there are especially surprised. In PostgreSQL, and in InnoDB too, it doesn't, and there's a good reason.
Why there's no stored row count
PostgreSQL uses MVCC: different transactions can see different versions of the table at the same moment. Transaction A has inserted 1,000 rows it hasn't committed yet; transaction B began before a big delete committed. There is no single correct row count. Each transaction has its own answer, depending on which row versions are visible to its snapshot.
So count(*) has to visit rows and check visibility. On a 200-million-row table, that's a full scan.
Make the exact count cheaper
1. Let it scan in parallel. On a big table the planner uses a Parallel Seq Scan. Make sure max_parallel_workers_per_gather isn't 0.
2. Index-only scans. With an index on a small column, PostgreSQL can count by reading the (smaller) index instead of the table, but only for pages marked all-visible in the visibility map, which VACUUM maintains:
EXPLAIN (ANALYZE, BUFFERS) SELECT count(*) FROM events;
-- Index Only Scan ... Heap Fetches: 0 <- good: the table wasn't touched
A high Heap Fetches count means vacuum is behind. Tuning autovacuum for the table helps the count, and a lot else.
3. Count less. count(*) with a selective, indexed WHERE clause is fast. Counting the entire table on every page load is the real problem.
By the way, count(*) is slower than or . is the fastest form in PostgreSQL. has to check each value for NULL.
