Database Performance Tuning
Database performance problems almost always come down to two root causes: missing indexes causing full table scans, or queries that are logically correct but execute inefficiently. Before tuning server parameters, find and fix the slow queries — a query that scans 10 million rows without an index will be slow regardless of how much RAM you throw at the server. The right order: identify slow queries, explain them, add indexes, then tune server parameters.
Identifying slow queries
# MySQL: enable slow query log:
sudo mysql -e "SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';"
# Or in /etc/mysql/mysql.conf.d/mysqld.cnf:
# slow_query_log = 1
# long_query_time = 1 ← log queries taking more than 1 second
# log_queries_not_using_indexes = 1
# Analyze the slow query log:
mysqldumpslow -s t /var/log/mysql/slow.log | head -20 # Sort by total time
mysqldumpslow output
Count: 234 Time=5.23s (1223s) Lock=0.00s (0s) Rows=10234.5 (2394813)
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC
# PostgreSQL: enable slow query logging in postgresql.conf:
# log_min_duration_statement = 1000 ← log queries taking more than 1000ms
# log_statement = 'all' ← log all statements (high volume, use carefully)
sudo systemctl reload postgresql
Using EXPLAIN
# MySQL: analyze query execution plan:
EXPLAIN SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC;
EXPLAIN output — shows full table scan (bad)
+----+-------------+--------+------+---------------+------+----------+------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+------+---------------+------+----------+------+----------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | NULL | 10234567 | Using where |
+----+-------------+--------+------+---------------+------+----------+------+----------+-------------+
-- type=ALL means full table scan on 10 million rows — very bad
# EXPLAIN ANALYZE actually runs the query with timing:
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending';
# PostgreSQL EXPLAIN ANALYZE output (more detail):
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE status = 'pending';
Indexing strategies
# Add a missing index (for the query above):
CREATE INDEX idx_orders_status ON orders (status);
CREATE INDEX idx_orders_status_created ON orders (status, created_at DESC); # covering index
# Verify index is now being used:
EXPLAIN SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC;
EXPLAIN after adding index — much better
| type | key | rows | Extra |
| ref | idx_orders_status_created | 245 | Using index |
-- type=ref with rows=245 instead of 10 million — 40,000x improvement
# Check existing indexes:
SHOW INDEXES FROM orders; -- MySQL
# PostgreSQL:
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'orders';
# Find unused indexes (PostgreSQL):
SELECT indexname FROM pg_stat_user_indexes WHERE idx_scan = 0;
MySQL InnoDB buffer pool
# The InnoDB buffer pool is MySQL's main cache for data and indexes
# Default: 128MB — too small for production databases
# Set to 50-70% of available RAM (for dedicated DB server):
# In /etc/mysql/mysql.conf.d/mysqld.cnf:
# innodb_buffer_pool_size = 2G ← for 4GB server
# Check buffer pool hit rate (should be >99%):
sudo mysql -e "SHOW STATUS LIKE 'Innodb_buffer_pool_read%';"
Buffer pool statistics
Innodb_buffer_pool_reads | 1234 ← disk reads (should be low)
Innodb_buffer_pool_read_requests | 9876543 ← cache hits
-- Hit rate: (9876543 - 1234) / 9876543 = 99.98% (good)
Conclusion
The performance tuning workflow: enable slow query logging, identify the worst offenders with mysqldumpslow or PostgreSQL's slow query log, run EXPLAIN on those queries, and add indexes where you see full table scans (type=ALL in MySQL, Seq Scan in PostgreSQL) on large tables. Indexes solve 80% of database performance problems. Once queries are indexed, tune innodb_buffer_pool_size (MySQL) or shared_buffers (PostgreSQL) to match available RAM. These two steps alone typically reduce response time by 10-100x.
Need help with Ubuntu administration?
Work directly with Muhammad Irfan Aslam for Ubuntu Server, Linux, cloud, Docker, DevOps, CI/CD, or infrastructure troubleshooting support.
Work with Muhammad Irfan Aslam