SQLite ANALYZE

The ANALYZE command scans the database and collects statistics about the distribution of values in each indexed column. SQLite's query planner uses these statistics to choose the fastest execution plan. Without fresh statistics, the planner may pick a slower index or skip one entirely.

Why the Query Planner Needs Statistics

TABLE: orders (1 million rows)
  - 999,000 rows have status = 'delivered'
  -     500 rows have status = 'pending'
  -     500 rows have status = 'cancelled'

INDEX: idx_status ON orders(status)

QUERY: SELECT * FROM orders WHERE status = 'pending';

Without statistics:
  Planner doesn't know 'pending' is rare.
  It might do a full table scan.   ← slow

With ANALYZE:
  Planner knows 'pending' = 500 rows out of 1M.
  It uses the index — retrieves 500 rows directly. ← fast

Running ANALYZE

-- Analyze the entire database
ANALYZE;

-- Analyze one specific table
ANALYZE employees;

-- Analyze one specific index
ANALYZE idx_dept_salary;

Where Statistics Are Stored

-- ANALYZE writes results to internal tables:
SELECT * FROM sqlite_stat1;

tbl       │ idx             │ stat
──────────┼─────────────────┼──────────────────
employees │ idx_dept_salary  │ 5000 500 50
employees │ idx_email        │ 5000 1

Reading "5000 500 50" for idx_dept_salary(dept_id, salary):
  5000 → total rows in table
   500 → average rows matching a given dept_id value
    50 → average rows matching a dept_id + salary combination

The planner uses these numbers to estimate selectivity.

Verifying ANALYZE Ran Successfully

-- Check that stats exist for your table
SELECT tbl, idx, stat FROM sqlite_stat1
WHERE tbl = 'employees';

tbl       │ idx              │ stat
──────────┼──────────────────┼──────────────────
employees │ idx_email        │ 5000 1
employees │ idx_dept_salary  │ 5000 500 50

If this table is empty, ANALYZE has not been run yet.

ANALYZE + EXPLAIN QUERY PLAN Together

-- Before ANALYZE: planner might choose wrong index
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE status = 'pending' AND amount > 1000;

detail
────────────────────────────────────────────────────────────────
SEARCH orders USING INDEX idx_amount (amount>?)
-- Chose idx_amount even though status is highly selective

-- Run ANALYZE:
ANALYZE;

-- After ANALYZE: planner picks the better index
EXPLAIN QUERY PLAN
SELECT * FROM orders WHERE status = 'pending' AND amount > 1000;

detail
────────────────────────────────────────────────────────────────
SEARCH orders USING INDEX idx_status (status=?)

When to Run ANALYZE

RUN ANALYZE WHEN:
──────────────────────────────────────────────────────────
✔ After bulk INSERT, UPDATE, or DELETE operations
✔ After adding new indexes to an existing table
✔ After major data distribution changes
✔ Queries are slower than expected despite having indexes
✔ EXPLAIN QUERY PLAN shows unexpected index choices
✔ Scheduled maintenance (monthly or after large data loads)

ANALYZE is fast — it reads index pages, not all data rows.
On a table with 1M rows it typically runs in seconds.

Automatic Statistics (SQLite 3.38+)

-- Enable automatic statistics collection:
PRAGMA analysis_limit = 1000;

-- SQLite will automatically update stats for the
-- most important index entries during normal operations.
-- Set to 0 to disable (default in older SQLite).

ANALYZE vs VACUUM — Different Jobs

COMMAND     PURPOSE
────────────────────────────────────────────────────────────────
VACUUM      Reclaim disk space from deleted rows
ANALYZE     Update statistics for the query planner

They solve different problems. Run both during maintenance.
ANALYZE does not compact the file.
VACUUM does not update query statistics.

Practical Maintenance Script

-- Run after a major data operation:
PRAGMA optimize;   -- auto-analyze if stats are stale
ANALYZE;           -- force fresh statistics
VACUUM;            -- compact the file

-- Then verify:
SELECT tbl, stat FROM sqlite_stat1 ORDER BY tbl;
PRAGMA freelist_count;  -- should be 0 after VACUUM

Key Takeaways

  • ANALYZE collects statistics about value distributions in indexed columns
  • The query planner uses these statistics to choose the fastest execution plan
  • Statistics are stored in sqlite_stat1 — query it to confirm ANALYZE ran
  • Run ANALYZE after bulk data changes or after adding new indexes
  • ANALYZE updates the plan; VACUUM reclaims space — they do different things

Leave a Comment

Your email address will not be published. Required fields are marked *