A slow database makes everything slow. Before you throw money at bigger servers, try these optimization techniques that can deliver 10x or even 100x performance improvements.
1. Index Your WHERE Clauses
This is the lowest-hanging fruit. If you're filtering or sorting by a column, it needs an index.
-- Find slow queries
SELECT * FROM orders WHERE customer_email = 'test@example.com';
-- Add index
CREATE INDEX idx_orders_email ON orders(customer_email);
A single index can turn a 30-second query into a 30-millisecond query.
2. Use EXPLAIN to Understand Queries
Before optimizing, understand what's happening:
EXPLAIN SELECT * FROM orders WHERE customer_id = 123;
Look for "table scan" or "filesort" ā these indicate missing indexes or inefficient queries.
3. Avoid SELECT *
Only fetch columns you need. Wide tables with SELECT * waste bandwidth and memory:
-- Bad
SELECT * FROM customers;
-- Good
SELECT id, name, email FROM customers;
4. Fix N+1 Query Problems
The silent killer. Your code fetches a list, then queries for each item:
-- N+1 problem: 1 query + 100 queries
$orders = query("SELECT * FROM orders");
foreach ($orders as $order) {
$customer = query("SELECT * FROM customers WHERE id = ?", $order['customer_id']);
}
-- Fixed: 2 queries total
$orders = query("SELECT o.*, c.name FROM orders o JOIN customers c ON o.customer_id = c.id");
5. Use Connection Pooling
Opening database connections is expensive. Connection pools reuse connections:
- PHP: Use persistent connections or PDO connection pooling
- Node.js: Use pg-pool or mysql2 pool
- General: Consider PgBouncer for PostgreSQL
6. Partition Large Tables
Tables with millions of rows benefit from partitioning:
-- Partition orders by year
CREATE TABLE orders (
id INT,
order_date DATE,
...
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026)
);
Queries that filter by date only scan relevant partitions.
7. Optimize Data Types
Use the smallest data type that fits:
- Use INT instead of BIGINT when possible
- Use VARCHAR(100) instead of VARCHAR(1000) if 100 chars is enough
- Use ENUM for columns with fixed options
- Use appropriate date types (DATE vs DATETIME vs TIMESTAMP)
8. Cache Expensive Queries
Don't hit the database for data that rarely changes:
- Application cache: Redis or Memcached
- Query cache: Built into some databases (use carefully)
- Materialized views: Pre-computed results for complex queries
9. Monitor Slow Queries
Enable slow query logging and review regularly:
-- MySQL
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- Log queries over 1 second
-- PostgreSQL
log_min_duration_statement = 1000 -- Log queries over 1 second
10. Consider Read Replicas
For read-heavy applications, send reads to replicas:
- Master handles writes
- Replicas handle reads
- Application routes queries appropriately
This can double or triple your read capacity with minimal code changes.
When to Call for Help
Database optimization is an art. If you've tried the basics and still have performance issues, or if you're not sure where to start, reach out. We've optimized databases handling billions of rows ā we can help with yours.