Database Connection Best Practices: Ensuring Reliable Data Access
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 secondsQuery 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.