PG-R14performanceConfidence: 80%Reversibility: safe

Missing Statistics (No ANALYZE)

System views: pg_stat_user_tables, pg_statistic, pg_class

Overview

The Postgres query planner uses column statistics (histograms, most-common-values, null fractions) from pg_statistic to estimate row counts and choose optimal query plans. Stale statistics cause the planner to generate bad plans—for example, choosing a sequential scan when an index would be faster, or choosing a nested loop join where a hash join would be better.

Statistics become stale when a significant fraction of rows are modified since the last ANALYZE. The autoanalyze daemon should handle this automatically, but it can fall behind on high-write tables or be blocked by long transactions. insightral flags tables where more than 10% of rows have been modified since the last successful ANALYZE.

Detection SQL

Run this query with a read-only role. No writes, no locks.

-- Tables with stale or missing statistics relative to row count
SELECT
  s.schemaname,
  s.relname                   AS table_name,
  s.n_live_tup,
  s.n_mod_since_analyze,
  round(
    s.n_mod_since_analyze::numeric / NULLIF(s.n_live_tup, 0) * 100,
    1
  )                           AS modified_pct,
  s.last_autoanalyze,
  s.last_manual_analyze,
  now() - s.last_autoanalyze  AS since_autoanalyze
FROM pg_stat_user_tables s
WHERE s.n_mod_since_analyze > 10000
  AND (
    s.last_autoanalyze IS NULL
    OR now() - s.last_autoanalyze > interval '1 day'
  )
  AND (
    s.last_manual_analyze IS NULL
    OR now() - s.last_manual_analyze > interval '1 day'
  )
ORDER BY s.n_mod_since_analyze DESC;

Fix SQL

Replace placeholders in <angle brackets> with values from the detection output before running.

-- Refresh statistics immediately
ANALYZE <table_name>;

-- For tables with very uneven data distribution, increase statistics target
ALTER TABLE <table_name>
  ALTER COLUMN <column_name> SET STATISTICS 500;
ANALYZE <table_name>;

Worked example

Table: product_views (4.8M rows). A new feature launched, tripling write rate. n_mod_since_analyze = 2.1M (44%) because autoanalyze hadn't run in 26 hours (blocked by a long-running analytics query). The planner estimated 50 rows for a query that returned 400K rows, choosing a nested loop that took 47 seconds instead of the 0.3-second hash join it chose after ANALYZE ran.

Common false positives

  • Bulk loads that complete and then trigger autoanalyze within minutes — the statistics are stale only briefly.
  • Tables where all modifications are in a narrow range and the planner's estimates are still accurate despite high n_mod_since_analyze — verify with EXPLAIN ANALYZE before tuning statistics target.

Check for missing statistics (no analyze) across your whole estate.

Insightral runs this rule — and 98 more — continuously inside your private cloud, with read-only credentials and nothing leaving your network.

Request early access

Last updated: 2026-05-13 · View all published rules