{"post":{"slug":"pg-stat-statements-2026","title":"Query Statistics: The First Thing to Enable on a Database","excerpt":"Cumulative statistics per normalised query answer the only question that matters when a database is slow: what is actually consuming it. Nothing else gets you there as fast.","category":"code","date":"January 12, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">W</span>hen a database is slow, the question is which queries are consuming it. Guessing from application code, reading slow query logs, or asking developers what they think is expensive all take longer and produce worse answers than looking at cumulative statistics.\n\nThe mechanism is simple: track every query executed, normalised so that the same shape with different parameters groups together, and accumulate calls, total time, rows, and cache behaviour per fingerprint.\n\nThe canonical implementation is `pg_stat_statements`, the PostgreSQL extension that exposes exactly this as a view you can query with SQL. MySQL provides the equivalent through `performance_schema` digest tables, and managed platforms wrap both in query-insight dashboards. The interface differs; the columns and the reasoning below are the same everywhere.\n\n## What changed in 2026\n\n- **It became enabled by default on managed platforms.** Query insights shipped as a standard feature rather than something to configure.\n- **Retention improved.** More platforms stored historical snapshots, enabling trend analysis rather than point-in-time views.\n- **Plan capture appeared alongside.** Some implementations began associating plans with statistics entries.\n- **The overhead question settled.** Measured impact proved small enough that leaving it enabled became the default recommendation.\n\n## What the numbers mean\n\n| Column | Question it answers |\n| --- | --- |\n| Total time | What consumes the database |\n| Calls | How often it runs |\n| Mean time | Whether an individual execution is slow |\n| Max time | Whether it is sometimes much worse |\n| Rows per call | Whether it returns more than expected |\n| Cache hit ratio | Whether it reads from disk |\n\n**Total time is the primary sort.** It is calls multiplied by mean time, and it directly answers what is consuming your database. A query with a small mean and an enormous call count outranks a slow rare one, which is the case threshold-based logging misses entirely — see [slow query logs](/blog/slow-query-logs-2026).\n\n**Mean against max** is the variability signal. A low mean with a very high maximum means the query is usually fine and occasionally terrible, which typically indicates lock waiting or a parameter matching far more rows than usual.\n\n**Rows per call** explains a surprising number of slow queries immediately. A query averaging fifty thousand rows returned is slow because it is doing a lot, and the fix is in the application rather than the database.\n\n**Cache hit ratio** distinguishes a query that is slow because it reads from disk from one that is slow because it does too much work in memory.\n\n## Reset to measure\n\nThe counters are cumulative since the last reset, which makes them poor at showing whether a change helped.\n\nIf a query has accumulated hours of total time over three weeks, deploying a fix and checking the next day shows almost no difference — the historical total dominates.\n\nReset the statistics after deploying the change, then compare over a period equivalent to your baseline. That gives a clean before-and-after.\n\nThe same applies to establishing a baseline: reset, let the system run through a representative period including peak, then snapshot. Comparing that snapshot against later ones shows what changed.\n\nWhere the platform stores historical snapshots, this is handled for you and the reset discipline is unnecessary.\n\n## Reading it productively\n\nSort by total time and look at the top ten. That is usually where the opportunity is, and it is frequently not where anyone expected.\n\nCommon findings:\n\n**A trivial query with an enormous call count.** Usually a query inside a loop, or a missing cache — see [GraphQL N+1](/blog/graphql-n-plus-one-2026) and [ORM pitfalls](/blog/orm-pitfalls-explained-2026).\n\n**A query returning far more rows than the application uses.** Missing pagination or an over-broad select.\n\n**A query with a high maximum and low mean.** Lock contention, or a parameter distribution issue producing a bad plan for some values — see [query planners](/blog/query-planner-explained-2026).\n\n**A maintenance or monitoring query in the top ten.** Your own tooling consuming meaningful capacity, which is worth knowing.\n\n## Common mistakes\n\n- **Sorting by mean time.** Finds slow queries, not expensive ones.\n- **Never resetting.** Cannot measure the effect of changes.\n- **Enabling it and never looking.** The collection is not the value.\n- **Ignoring rows per call.** Explains many slow queries at a glance.\n- **Optimising the top query without checking the plan.** Statistics say what; plans say why.\n- **Forgetting your own monitoring queries.** They appear in the statistics too.\n- **Assuming normalised entries are one query.** One fingerprint may come from several code paths.\n\n## FAQ\n\n**What is the overhead?**\nSmall — measured impact is low enough that leaving it on permanently is the standard recommendation. The cost of not having it during an incident is far higher.\n\n**How many queries does it track?**\nA configurable maximum, with least-used entries evicted when full. On a system with very many distinct query shapes, raising the limit prevents useful entries being lost.\n\n**Does it capture parameters?**\nNo, by design — that is what enables normalisation. To reproduce a specific slow execution you need the slow query log alongside.\n\n**Can I see plans?**\nNot from the statistics themselves in most implementations. Take the normalised query, supply representative parameters, and examine the plan separately.\n\n## Where to go next\n\nFor catching individual slow executions, read [slow query logs](/blog/slow-query-logs-2026). For diagnosing why a query is slow, [query planners](/blog/query-planner-explained-2026), and for the application patterns that produce high call counts, [ORM pitfalls](/blog/orm-pitfalls-explained-2026).","hue":306,"metaTitle":"pg_stat_statements: The First Thing to Enable","metaDescription":"How cumulative query statistics identify what consumes a database, why total time beats mean time, and how to use resets to measure improvements.","keywords":["pg_stat_statements","query statistics","database profiling","total execution time","query normalization","performance baseline","database tuning","database operations 2026"],"tldr":["**Rank by total time, not mean time** — frequency is half the equation.","**Queries are normalised into fingerprints** — the same shape with different parameters groups together.","**Reset before and after a change** — cumulative counters hide improvements otherwise.","**Watch rows returned per call** — a query returning far more than expected explains itself.","**Skip:** enabling it and never looking. The value is in the periodic review, not the collection."],"readTime":"4 min read","wordCount":901},"related":[{"slug":"slow-query-logs-2026","title":"Slow Query Logs: Finding What Is Actually Costing You","excerpt":"The slowest query is rarely the most expensive one. A query taking 40ms and running ten thousand times a minute costs more than one taking four seconds once an hour.","category":"code","date":"January 15, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":305},{"slug":"query-planner-explained-2026","title":"Query Planners: Why Your Index Is Being Ignored","excerpt":"You added the index and the query got no faster. The planner looked at it, estimated it would be slower, and chose a sequential scan. It is often right.","category":"code","date":"August 17, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":310},{"slug":"what-is-query-optimization-2026","title":"What is query optimization in 2026: how the planner decides","excerpt":"Query optimization is mostly automatic — the database picks an execution plan before your SQL ever runs. Here is what the optimizer actually does and where it needs help.","category":"code","date":"July 23, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":260}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"momentum-investing-explained-2026","title":"Momentum Investing: Buying What Has Already Gone Up","excerpt":"The idea that recent winners keep winning contradicts most investing intuition and has substantial historical support. It also crashes hard and unpredictably.","category":"finance","date":"January 12, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":286},"older":{"slug":"shredding-schedule-guide-2026","title":"Shredding Schedule: What to Destroy and When","excerpt":"Keeping every document forever creates a pile nobody can search and a target if anyone breaks in. Knowing what to keep, for how long, and how to destroy the rest resolves both.","category":"guides","date":"January 12, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":163}}