sql 260 lines · 2 tabs

Database maintenance with VACUUM and ANALYZE

Maria Garcia Feb 2026
2 tabs
-- Basic VACUUM (reclaims dead tuple space)
VACUUM users;

-- VACUUM all tables in database
VACUUM;

-- VACUUM VERBOSE (shows progress)
VACUUM VERBOSE users;

-- VACUUM ANALYZE (vacuum + update statistics)
VACUUM ANALYZE users;

-- VACUUM FULL (rewrites table, requires exclusive lock)
VACUUM FULL users;
-- WARNING: Locks table, can take hours on large tables

-- VACUUM specific columns
VACUUM ANALYZE users (email, username);

-- ANALYZE only (update statistics without vacuum)
ANALYZE users;

-- ANALYZE specific columns
ANALYZE users (email);

-- Check dead tuples
SELECT
  schemaname,
  tablename,
  n_live_tup,
  n_dead_tup,
  ROUND(100 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) AS dead_pct,
  last_vacuum,
  last_autovacuum,
  last_analyze,
  last_autoanalyze
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;

-- Estimate table bloat
SELECT
  schemaname,
  tablename,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
  ROUND(100 * pg_relation_size(schemaname||'.'||tablename) /
        NULLIF(pg_total_relation_size(schemaname||'.'||tablename), 0), 2) AS table_pct
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

-- Autovacuum configuration (postgresql.conf)
-- autovacuum = on
-- autovacuum_max_workers = 3
-- autovacuum_naptime = 1min
-- autovacuum_vacuum_threshold = 50
-- autovacuum_vacuum_scale_factor = 0.2
-- autovacuum_analyze_threshold = 50
-- autovacuum_analyze_scale_factor = 0.1

-- Per-table autovacuum settings
ALTER TABLE large_table SET (
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_analyze_scale_factor = 0.02,
  autovacuum_vacuum_threshold = 1000
);

-- Disable autovacuum for specific table (not recommended)
ALTER TABLE staging_table SET (
  autovacuum_enabled = false
);

-- Transaction ID wraparound prevention
SELECT
  datname,
  age(datfrozenxid) AS xid_age,
  2147483647 - age(datfrozenxid) AS xids_until_wraparound,
  ROUND(100.0 * age(datfrozenxid) / 2147483647, 2) AS pct_toward_wraparound
FROM pg_database
ORDER BY age(datfrozenxid) DESC;

-- VACUUM FREEZE (freeze old tuples)
VACUUM FREEZE users;

-- Check when table needs freezing
SELECT
  schemaname,
  tablename,
  age(relfrozenxid) AS xid_age,
  pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
JOIN pg_class ON pg_tables.tablename = pg_class.relname
WHERE schemaname = 'public'
ORDER BY age(relfrozenxid) DESC;

-- Monitor autovacuum activity
SELECT
  pid,
  now() - query_start AS duration,
  query
FROM pg_stat_activity
WHERE query LIKE '%autovacuum%'
  AND query NOT LIKE '%pg_stat_activity%';

-- REINDEX (rebuild indexes)
REINDEX TABLE users;
REINDEX INDEX idx_users_email;
REINDEX DATABASE mydb;  -- Requires exclusive lock

-- REINDEX CONCURRENTLY (PostgreSQL 12+, doesn't lock)
REINDEX INDEX CONCURRENTLY idx_users_email;

-- Check index bloat
SELECT
  schemaname,
  tablename,
  indexname,
  pg_size_pretty(pg_relation_size(indexrelid)) AS index_size,
  idx_scan,
  idx_tup_read,
  idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;
2 files · sql Explain with highlit

VACUUM reclaims storage from dead tuples. Updates and deletes leave dead rows—VACUUM removes them. Autovacuum runs automatically but needs tuning. VACUUM FULL rewrites entire table—requires lock, reclaims most space. Understanding bloat prevents performance degradation. ANALYZE updates statistics for query planner. Statistics accuracy determines query plan quality. Regular maintenance prevents emergency interventions. Monitoring vacuum activity catches problems early. Understanding when to VACUUM FULL vs regular VACUUM saves downtime. Freeze operations prevent transaction ID wraparound. Proper maintenance is essential for long-term database health and performance.


Related snips

sql
-- Simple function
CREATE OR REPLACE FUNCTION get_full_name(
  first_name VARCHAR,
  last_name VARCHAR
)
RETURNS VARCHAR AS $$

Stored procedures and functions in PostgreSQL

postgresql stored-procedures functions
by Maria Garcia 2 tabs
sql
-- EXPLAIN ANALYZE (actual execution statistics)
EXPLAIN ANALYZE
SELECT u.username, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2024-01-01'

Advanced query optimization techniques

database optimization query-performance
by Maria Garcia 2 tabs
sql
-- Publisher: Send notification
NOTIFY new_order, 'Order #12345 created';

-- Subscriber: Listen for notifications
LISTEN new_order;

PostgreSQL LISTEN/NOTIFY for pub-sub messaging

postgresql listen-notify pub-sub
by Maria Garcia 2 tabs
sql
-- Logical backup with pg_dump
-- Single database
-- pg_dump -h localhost -U postgres -d mydb -F c -f mydb_backup.dump

-- All databases
-- pg_dumpall -h localhost -U postgres -f all_databases.sql

Database backup and recovery strategies

database backup recovery
by Maria Garcia 2 tabs
sql
-- Prepared statements basics
-- PostgreSQL syntax
PREPARE get_user (INT) AS
SELECT id, username, email
FROM users
WHERE id = $1;

Query plan caching and prepared statements

postgresql performance query-plans
by Maria Garcia 2 tabs
sql
-- Install PostGIS extension
CREATE EXTENSION IF NOT EXISTS postgis;

-- Create table with geometry column
CREATE TABLE locations (
  id SERIAL PRIMARY KEY,

Geospatial data with PostGIS

postgresql postgis geospatial
by Maria Garcia 2 tabs

Share this code

Here's the card — post it anywhere.

Database maintenance with VACUUM and ANALYZE — share card
Link copied