sql
260 lines · 2 tabs
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;
-- Scheduled maintenance script
DO $$
DECLARE
table_record RECORD;
BEGIN
-- Vacuum tables with high dead tuple ratio
FOR table_record IN
SELECT schemaname, tablename
FROM pg_stat_user_tables
WHERE n_dead_tup > 10000
AND ROUND(100 * n_dead_tup / NULLIF(n_live_tup + n_dead_tup, 0), 2) > 10
LOOP
RAISE NOTICE 'Vacuuming %.%', table_record.schemaname, table_record.tablename;
EXECUTE format('VACUUM ANALYZE %I.%I',
table_record.schemaname,
table_record.tablename);
END LOOP;
END $$;
-- Maintenance window for VACUUM FULL
BEGIN;
-- Lock table
LOCK TABLE large_table IN ACCESS EXCLUSIVE MODE;
-- Vacuum full
VACUUM FULL large_table;
-- Reindex
REINDEX TABLE large_table;
-- Analyze
ANALYZE large_table;
COMMIT;
-- Alternative: pg_repack (online table rewrite)
-- pg_repack -t large_table -k
-- Doesn't require exclusive lock
-- Cluster table by index (physically reorders rows)
CLUSTER users USING idx_users_created_at;
-- Future inserts won't maintain order
-- Check if table needs clustering
SELECT
schemaname,
tablename,
pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;
-- Update statistics sampling
ALTER TABLE large_table
ALTER COLUMN search_vector
SET STATISTICS 1000; -- Default is 100
-- Increase statistics for better query plans
ANALYZE large_table (search_vector);
-- Check statistics quality
SELECT
schemaname,
tablename,
attname,
n_distinct,
most_common_vals,
most_common_freqs
FROM pg_stats
WHERE tablename = 'users'
AND schemaname = 'public';
-- Maintenance monitoring query
WITH maintenance_status AS (
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,
NOW() - last_autovacuum AS time_since_autovacuum,
last_analyze,
last_autoanalyze,
NOW() - last_autoanalyze AS time_since_autoanalyze
FROM pg_stat_user_tables
)
SELECT *
FROM maintenance_status
WHERE dead_pct > 10
OR time_since_autovacuum > INTERVAL '1 day'
OR time_since_autoanalyze > INTERVAL '1 day'
ORDER BY dead_pct DESC;
-- Automated maintenance with pg_cron
CREATE EXTENSION pg_cron;
-- Schedule daily VACUUM ANALYZE at 2 AM
SELECT cron.schedule(
'nightly-vacuum',
'0 2 * * *',
$$VACUUM ANALYZE$$
);
-- Schedule weekly REINDEX at 3 AM Sunday
SELECT cron.schedule(
'weekly-reindex',
'0 3 * * 0',
$$REINDEX DATABASE mydb$$
);
-- MySQL maintenance equivalent
/*
-- Optimize table (defragment)
OPTIMIZE TABLE users;
-- Analyze table (update statistics)
ANALYZE TABLE users;
-- Check table integrity
CHECK TABLE users;
-- Repair table
REPAIR TABLE users;
*/
-- Space reclamation summary
SELECT
pg_size_pretty(SUM(pg_total_relation_size(schemaname||'.'||tablename))) AS total_size,
pg_size_pretty(SUM(pg_relation_size(schemaname||'.'||tablename))) AS table_size,
pg_size_pretty(SUM(pg_total_relation_size(schemaname||'.'||tablename) -
pg_relation_size(schemaname||'.'||tablename))) AS index_size,
COUNT(*) AS table_count
FROM pg_tables
WHERE schemaname = 'public';
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.