Loading PasteShare...

Database Connection Best Practices: Ensuring Reliable Data Access

By Sarah Sutherland Nov 08, 2025 3 mins read 22 views

Why Database Connections Matter

Database connections are the lifeline of your application. Poor connection management can lead to performance bottlenecks, connection leaks, and application failures. In 2025, with applications handling thousands of concurrent users, proper connection management is more critical than ever.

Common Connection Issues

  • Connection Leaks: Connections not properly closed
  • Connection Exhaustion: Too many concurrent connections
  • Timeout Problems: Long-running queries blocking connections
  • Poor Error Handling: Connections left in inconsistent states

Connection Pooling Strategies

Node.js with PostgreSQL

const { Pool } = require('pg');

// Configure connection pool
const pool = new Pool({
  user: process.env.DB_USER,
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT || 5432,
  
  // Pool configuration
  max: 20, // Maximum number of clients in the pool
  min: 5,  // Minimum number of clients in the pool
  idleTimeoutMillis: 30000, // Close idle clients after 30 seconds
  connectionTimeoutMillis: 2000, // Return an error after 2 seconds if connection could not be established
  acquireTimeoutMillis: 60000, // Return an error after 60 seconds if a client could not be acquired
  
  // SSL configuration for production
  ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
});

// Graceful shutdown
process.on('SIGINT', async () => {
  console.log('Shutting down database pool...');
  await pool.end();
  process.exit(0);
});

// Example usage with proper error handling
async function getUserById(userId) {
  const client = await pool.connect();
  try {
    const result = await client.query('SELECT * FROM users WHERE id = $1', [userId]);
    return result.rows[0];
  } catch (error) {
    console.error('Database error:', error);
    throw error;
  } finally {
    client.release(); // Always release the client back to the pool
  }
}

PHP with PDO

 PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
            
            // Connection pooling options
            PDO::ATTR_PERSISTENT => true, // Use persistent connections
            PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci"
        ];
        
        try {
            $this->pdo = new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], $options);
        } catch (PDOException $e) {
            throw new Exception("Database connection failed: " . $e->getMessage());
        }
    }
    
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function getConnection() {
        return $this->pdo;
    }
    
    public function query($sql, $params = []) {
        try {
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            error_log("Database query error: " . $e->getMessage());
            throw new Exception("Database query failed");
        }
    }
}

// Usage example
try {
    $db = DatabaseConnection::getInstance();
    $stmt = $db->query("SELECT * FROM users WHERE id = ?", [1]);
    $user = $stmt->fetch();
} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

Advanced Connection Management

Connection Health Monitoring

// Health check endpoint
app.get('/health/database', async (req, res) => {
  const client = await pool.connect();
  try {
    const start = Date.now();
    await client.query('SELECT 1');
    const duration = Date.now() - start;
    
    res.json({
      status: 'healthy',
      responseTime: duration + 'ms',
      activeConnections: pool.totalCount,
      idleConnections: pool.idleCount,
      waitingClients: pool.waitingCount
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    });
  } finally {
    client.release();
  }
});

Automatic Retry Logic

async function executeWithRetry(query, params, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const client = await pool.connect();
      try {
        const result = await client.query(query, params);
        return result;
      } finally {
        client.release();
      }
    } catch (error) {
      lastError = error;
      
      // Check if error is retryable
      if (isRetryableError(error) && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
        console.log(`Query failed, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }
      
      throw error;
    }
  }
  
  throw lastError;
}

function isRetryableError(error) {
  const retryableErrors = [
    'ECONNRESET',
    'ECONNREFUSED',
    'ETIMEDOUT',
    'ENOTFOUND'
  ];
  
  return retryableErrors.some(code => error.code === code);
}

Performance Optimization

Connection Pool Monitoring

// Monitor pool metrics
setInterval(() => {
  console.log('Pool Status:', {
    total: pool.totalCount,
    idle: pool.idleCount,
    waiting: pool.waitingCount
  });
}, 30000); // Log every 30 seconds

Query Optimization

// Use prepared statements for better performance
const getUserQuery = 'SELECT * FROM users WHERE id = $1';
const getUsersQuery = 'SELECT * FROM users WHERE created_at > $1 LIMIT $2';

// Cache prepared statements
const preparedStatements = new Map();

async function executePrepared(query, params) {
  if (!preparedStatements.has(query)) {
    preparedStatements.set(query, query);
  }
  
  const client = await pool.connect();
  try {
    return await client.query(preparedStatements.get(query), params);
  } finally {
    client.release();
  }
}

Best Practices Summary

  • Use Connection Pooling: Always implement connection pooling for production
  • Set Appropriate Limits: Configure min/max connections based on your needs
  • Implement Health Checks: Monitor connection health regularly
  • Handle Errors Gracefully: Implement retry logic for transient failures
  • Use Prepared Statements: Improve performance and security
  • Monitor Metrics: Track connection usage and performance
  • Graceful Shutdown: Always close connections properly

Common Pitfalls

  • Not releasing connections: Always release connections back to the pool
  • Too many connections: Set appropriate pool limits
  • No error handling: Implement comprehensive error handling
  • Ignoring timeouts: Set reasonable timeout values
  • No monitoring: Monitor connection health and performance

Remember: Proper database connection management is the foundation of a reliable, scalable application.

Comments (0)

Please login or register to leave a comment.

No comments yet. Be the first to share your thoughts!