Back to Blog
Performance

Database Optimization for PHP Developers

Practical tips for query optimization, indexing strategies, and caching techniques to boost your application's database performance.

David Miller
December 25, 2025
11 min read

Database Optimization for PHP Developers

Database performance can make or break your application. Learn practical optimization techniques every PHP developer should know.

Query Optimization Basics

Use EXPLAIN

sql
1EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';

Look for:

  • type: ALL (bad - full table scan)
  • type: ref (good - using index)
  • rows: Number of rows examined
  • Select Only What You Need

    php
    1// Bad
    2$users = $db->query('SELECT * FROM users');
    3
    4// Good
    5$users = $db->query('SELECT id, name, email FROM users');

    Use LIMIT

    php
    1// Always paginate
    2$page = (int) ($_GET['page'] ?? 1);
    3$perPage = 20;
    4$offset = ($page - 1) * $perPage;
    5
    6$users = $db->query(
    7    'SELECT id, name, email FROM users LIMIT ? OFFSET ?',
    8    [$perPage, $offset]
    9);

    Indexing Strategies

    Single Column Index

    sql
    1CREATE INDEX idx_email ON users(email);

    Composite Index

    sql
    1CREATE INDEX idx_user_status ON users(status, created_at);

    Rule: Put the most selective column first!

    Covering Index

    sql
    1CREATE INDEX idx_user_lookup ON users(email, name, status);
    2
    3-- This query uses only the index
    4SELECT name, status FROM users WHERE email = 'john@example.com';

    N+1 Query Problem

    The Problem

    php
    1// Bad - N+1 queries
    2$posts = $db->query('SELECT * FROM posts');
    3
    4foreach ($posts as $post) {
    5    // This runs a query for EACH post!
    6    $author = $db->query('SELECT * FROM users WHERE id = ?', [$post['user_id']]);
    7}

    The Solution

    php
    1// Good - Single query with JOIN
    2$posts = $db->query('
    3    SELECT 
    4        p.id, p.title, p.content,
    5        u.name as author_name, u.email as author_email
    6    FROM posts p
    7    INNER JOIN users u ON p.user_id = u.id
    8');

    Caching Strategies

    Query Result Caching

    php
    1class CachedDatabase
    2{
    3    public function __construct(
    4        private Database $db,
    5        private Cache $cache
    6    ) {}
    7    
    8    public function query(string $sql, array $params = [], int $ttl = 300): array
    9    {
    10        $cacheKey = 'query:' . md5($sql . serialize($params));
    11        
    12        $cached = $this->cache->get($cacheKey);
    13        if ($cached !== null) {
    14            return $cached;
    15        }
    16        
    17        $result = $this->db->query($sql, $params);
    18        $this->cache->set($cacheKey, $result, $ttl);
    19        
    20        return $result;
    21    }
    22}

    Object Caching

    php
    1class UserRepository
    2{
    3    public function find(int $id): ?User
    4    {
    5        $cacheKey = "user:$id";
    6        
    7        $cached = $this->cache->get($cacheKey);
    8        if ($cached) {
    9            return $cached;
    10        }
    11        
    12        $users = $this->db->query('SELECT * FROM users WHERE id = ?', [$id]);
    13        
    14        if (empty($users)) {
    15            return null;
    16        }
    17        
    18        $user = User::fromArray($users[0]);
    19        $this->cache->set($cacheKey, $user, 3600);
    20        
    21        return $user;
    22    }
    23}

    Connection Pooling

    php
    1class DatabasePool
    2{
    3    private static ?PDO $connection = null;
    4    
    5    public static function getConnection(): PDO
    6    {
    7        if (self::$connection === null) {
    8            self::$connection = new PDO(
    9                'mysql:host=localhost;dbname=myapp',
    10                'user',
    11                'pass',
    12                [
    13                    PDO::ATTR_PERSISTENT => true,
    14                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    15                ]
    16            );
    17        }
    18        
    19        return self::$connection;
    20    }
    21}

    Batch Operations

    Batch Inserts

    php
    1// Bad - Multiple queries
    2foreach ($users as $user) {
    3    $db->execute('INSERT INTO users (name, email) VALUES (?, ?)', 
    4        [$user['name'], $user['email']]);
    5}
    6
    7// Good - Single query
    8$values = [];
    9$params = [];
    10
    11foreach ($users as $user) {
    12    $values[] = '(?, ?)';
    13    $params[] = $user['name'];
    14    $params[] = $user['email'];
    15}
    16
    17$sql = 'INSERT INTO users (name, email) VALUES ' . implode(', ', $values);
    18$db->execute($sql, $params);

    Query Profiling

    php
    1class ProfilingDatabase
    2{
    3    private array $queries = [];
    4    
    5    public function query(string $sql, array $params = []): array
    6    {
    7        $start = microtime(true);
    8        $result = parent::query($sql, $params);
    9        $duration = microtime(true) - $start;
    10        
    11        $this->queries[] = [
    12            'sql' => $sql,
    13            'params' => $params,
    14            'duration' => $duration,
    15            'rows' => count($result)
    16        ];
    17        
    18        if ($duration > 0.1) {
    19            error_log("Slow query ($duration s): $sql");
    20        }
    21        
    22        return $result;
    23    }
    24    
    25    public function getQueryLog(): array
    26    {
    27        return $this->queries;
    28    }
    29    
    30    public function getTotalTime(): float
    31    {
    32        return array_sum(array_column($this->queries, 'duration'));
    33    }
    34}

    Best Practices Checklist

  • ✅ Index foreign keys
  • ✅ Use appropriate data types
  • ✅ Avoid SELECT *
  • ✅ Always use LIMIT for large results
  • ✅ Fix N+1 query problems
  • ✅ Cache frequently accessed data
  • ✅ Use connection pooling
  • ✅ Batch insert/update operations
  • ✅ Profile slow queries
  • ✅ Regular database maintenance
  • Conclusion

    Database optimization is crucial for application performance. Profile your queries, add appropriate indexes, and cache aggressively!

    Enjoyed this article?

    Share it with your network!