Database performance directly impacts application response times, user experience, and infrastructure costs. Whether you run MySQL or PostgreSQL, the principles of performance tuning revolve around query optimization, proper indexing, memory configuration, and connection management. This guide provides practical, production-tested strategies for both engines.
Query Analysis: The Starting Point
Before tuning anything, you must identify what is slow. Both MySQL and PostgreSQL provide tools to analyze query execution.
MySQL: EXPLAIN and Slow Query Log
-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- Log queries taking > 1 second
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- Analyze a specific query
EXPLAIN ANALYZE
SELECT o.order_id, c.name, SUM(oi.quantity * oi.price) AS total
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.created_at >= '2026-01-01'
GROUP BY o.order_id, c.name
ORDER BY total DESC
LIMIT 50;
Key EXPLAIN columns to watch: type (should be ref or range, not ALL), rows (estimated rows scanned), Extra (watch for Using filesort or Using temporary).
PostgreSQL: EXPLAIN (ANALYZE, BUFFERS)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT o.order_id, c.name, SUM(oi.quantity * oi.price) AS total
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON o.order_id = oi.order_id
WHERE o.created_at >= '2026-01-01'
GROUP BY o.order_id, c.name
ORDER BY total DESC
LIMIT 50;
Look for: Seq Scan on large tables (needs index), Buffers: shared hit vs shared read (cache effectiveness), actual vs planned rows (statistics accuracy).
Indexing Strategies
Composite Index Design
The order of columns in a composite index matters significantly. Follow the equality-sort-range rule:
-- For queries like:
-- WHERE status = 'active' AND created_at >= '2026-01-01' ORDER BY total DESC
-- MySQL
CREATE INDEX idx_orders_status_date_total
ON orders (status, created_at, total DESC);
-- PostgreSQL (supports DESC in index)
CREATE INDEX idx_orders_status_date_total
ON orders (status, created_at DESC, total DESC);
Partial Indexes (PostgreSQL)
PostgreSQL supports partial indexes that only index rows matching a condition — extremely useful for hot data:
-- Only index active orders (90% of queries target these)
CREATE INDEX idx_active_orders ON orders (created_at DESC)
WHERE status = 'active';
-- Result: Index is 10x smaller, 10x faster to scan
Covering Indexes
A covering index includes all columns needed by a query, eliminating table lookups entirely:
-- MySQL: INCLUDE not supported, add columns to index
CREATE INDEX idx_covering ON orders (customer_id, status, created_at, total);
-- PostgreSQL: Use INCLUDE clause (v11+)
CREATE INDEX idx_covering ON orders (customer_id, status)
INCLUDE (created_at, total);
Memory and Buffer Configuration
MySQL: InnoDB Buffer Pool
The InnoDB buffer pool is the single most important MySQL setting. It caches table data and indexes in memory.
# my.cnf - For a server with 32GB RAM dedicated to MySQL
[mysqld]
innodb_buffer_pool_size = 24G # 70-80% of total RAM
innodb_buffer_pool_instances = 8 # One per GB (up to 64)
innodb_log_file_size = 2G # Larger = better write performance
innodb_flush_log_at_trx_commit = 1 # Full ACID compliance
innodb_io_capacity = 2000 # Match your SSD IOPS
innodb_read_io_threads = 8
innodb_write_io_threads = 8
Monitor buffer pool hit ratio:
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
-- Hit ratio = 1 - (reads/read_requests) — should be > 99%
PostgreSQL: shared_buffers and work_mem
# postgresql.conf - For a server with 32GB RAM
shared_buffers = 8GB # 25% of total RAM
effective_cache_size = 24GB # 75% of total RAM
work_mem = 64MB # Per-operation sort memory
maintenance_work_mem = 2GB # For VACUUM, CREATE INDEX
wal_buffers = 64MB
random_page_cost = 1.1 # For SSDs (default 4.0 is for HDD)
effective_io_concurrency = 200 # For SSDs
Connection Pooling
Opening a new database connection is expensive (10-50ms). Connection pooling reuses established connections.
PgBouncer for PostgreSQL
# pgbouncer.ini
[databases]
myapp = host=127.0.0.1 port=5432 dbname=myapp
[pgbouncer]
pool_mode = transaction # Best for web applications
max_client_conn = 1000
default_pool_size = 50
reserve_pool_size = 10
reserve_pool_timeout = 3
MySQL: ProxySQL
-- ProxySQL configuration via admin interface
INSERT INTO mysql_servers (hostgroup_id, hostname, port, max_connections)
VALUES (1, '10.0.1.10', 3306, 200);
INSERT INTO mysql_query_rules (rule_id, match_pattern, destination_hostgroup)
VALUES (1, '^SELECT.*FOR UPDATE', 1), -- Writes to primary
(2, '^SELECT', 2); -- Reads to replica
PostgreSQL: VACUUM and Autovacuum Tuning
PostgreSQL uses MVCC (Multi-Version Concurrency Control), which creates dead tuples that must be cleaned by VACUUM:
# Autovacuum tuning for high-write tables
autovacuum_vacuum_scale_factor = 0.05 # Vacuum when 5% dead tuples
autovacuum_analyze_scale_factor = 0.02 # Analyze when 2% changed
autovacuum_vacuum_cost_delay = 2ms # Less aggressive throttling
autovacuum_max_workers = 6 # More parallel workers
Performance Comparison Matrix
| Feature | MySQL (InnoDB) | PostgreSQL |
|---|---|---|
| Default storage engine | InnoDB (B-tree) | Heap + B-tree indexes |
| Partial indexes | Not supported | Supported |
| Parallel queries | Limited (8.0+) | Full parallel scan, join, aggregate |
| JSON performance | Good (JSON type) | Excellent (JSONB with GIN indexes) |
| Replication | Semi-sync, Group Replication | Streaming, Logical Replication |
| Connection overhead | Thread-per-connection | Process-per-connection (use PgBouncer) |
| Window functions | Basic (8.0+) | Advanced with custom aggregates |
Expert Database Services
Database performance tuning requires deep knowledge of query planners, storage engines, and OS-level I/O patterns. At PCCVDI Solutions, our certified DBAs optimize MySQL, PostgreSQL, SQL Server, and Oracle databases for enterprises across India. From emergency performance firefighting to ongoing monitoring and tuning, we ensure your databases run at peak efficiency. Request a free database performance audit to identify bottlenecks and optimization opportunities.
