2026-02-14•ToolBox Team
数据库连接池与性能优化的科学
🔧 返回工具箱 | Back to Tools
浏览所有工具 | View All Toolsdatabaseperformanceconnection-poolsql
数据库连接池与性能优化的科学
数据库往往是应用性能的最大瓶颈。无论前端和中间层管理得多好,一个低效的数据库设计会让整个系统崩溃。本文深入讨论如何通过连接池、索引策略和查询优化来提升数据库性能。
1. 为什么需要连接池?
问题:频繁建立数据库连接
请求 1: 建立连接 (200ms) → 执行查询 (50ms) → 关闭连接 (30ms) = 280ms
请求 2: 建立连接 (200ms) → 执行查询 (50ms) → 关闭连接 (30ms) = 280ms
请求 3: 建立连接 (200ms) → 执行查询 (50ms) → 关闭连接 (30ms) = 280ms
...
70% 的时间用于建立和关闭连接!
解决方案:连接池
连接池(维护 10 个长期连接)
请求 1: 从池借用连接 (5ms) → 执行查询 (50ms) → 归还连接 (5ms) = 60ms
请求 2: 从池借用连接 (3ms) → 执行查询 (50ms) → 归还连接 (3ms) = 56ms
请求 3: 从池借用连接 (4ms) → 执行查询 (50ms) → 归还连接 (4ms) = 58ms
...
性能提升 80%!
2. Node.js 中配置连接池
PostgreSQL 连接池 (pg)
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
host: process.env.DB_HOST,
port: process.env.DB_PORT,
database: process.env.DB_NAME,
// 连接池配置
max: 20, // 最大连接数
min: 5, // 最小连接数
idleTimeoutMillis: 30000, // 30秒未使用则关闭
connectionTimeoutMillis: 2000, // 连接超时 2 秒
});
// 使用连接池
async function getUser(id) {
try {
const result = await pool.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return result.rows[0];
} catch (error) {
console.error('查询错误:', error);
}
}
// 在应用关闭前,关闭池
process.on('SIGTERM', async () => {
await pool.end();
process.exit(0);
});
MySQL 连接池 (mysql2)
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
// 连接池配置
connectionLimit: 10, // 最大连接数
queueLimit: 0 // 无限等待队列
});
// 使用
async function getUserById(id) {
const connection = await pool.getConnection();
try {
const [rows] = await connection.query(
'SELECT * FROM users WHERE id = ?',
[id]
);
return rows[0];
} finally {
connection.release();
}
}
ORM (Sequelize 或 TypeORM) 中的连接池
// Sequelize
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASSWORD,
{
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
pool: {
max: 20,
min: 5,
acquire: 30000,
idle: 10000
}
}
);
// TypeORM
const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
extra: {
max: 20, // 最大连接数
min: 5 // 最小连接数
}
});
3. 连接池配置最佳实践
计算最优连接数
最优连接数 = (核心线程数 × 2) + 有效磁盘数
例子:
- CPU: 8 核
- 磁盘: 2 块
连接数 = (8 × 2) + 2 = 18
在实践中,通常设置 10-20 个连接。
监控连接池状态
const { Pool } = require('pg');
const pool = new Pool({...});
// 监控连接状态
setInterval(() => {
console.log('活跃连接数:', pool.activeCount);
console.log('等待连接数:', pool.waitingCount);
console.log('总连接数:', pool.totalCount);
}, 10000);
// 在仪表板中展示
app.get('/api/metrics/db-pool', (req, res) => {
res.json({
active: pool.activeCount,
waiting: pool.waitingCount,
total: pool.totalCount,
available: pool.idleCount
});
});
4. 查询优化
问题 1:N+1 查询
// ❌ N+1 问题:1 次查询单据 + N 次查询行项目
const orders = await Order.find(); // 1 次查询
for (const order of orders) {
const items = await OrderItem.find({ orderId: order.id }); // N 次查询
order.items = items;
}
// ✅ 解决:Join 或批量查询
const orders = await Order.find().populate('items'); // 1 次 Join 查询
// 或使用 SQL Join
const orders = await pool.query(`
SELECT o.*, json_agg(oi.*) as items
FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id
`);
问题 2:未优化的 WHERE 条件
// ❌ 无索引,全表扫描
const user = await User.findOne({
createdAt: { $gte: new Date('2026-01-01') },
status: 'active'
});
// ✅ 添加索引
db.users.createIndex({ createdAt: 1, status: 1 });
// ✅ 更好的查询
const user = await User.findOne({
status: 'active',
createdAt: { $gte: new Date('2026-01-01') }
});
使用 EXPLAIN 分析查询
-- 查看执行计划
EXPLAIN ANALYZE
SELECT u.id, u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id
ORDER BY order_count DESC;
-- 输出示例:
-- Seq Scan on users u (cost=0.00..1000.00 rows=5000)
-- ↓ 提示:缺少索引,执行全表扫描
创建索引:
CREATE INDEX idx_users_status ON users(status);
CREATE INDEX idx_orders_user_id ON orders(user_id);
5. 缓存策略
Redis 缓存热点数据
const redis = require('redis');
const client = redis.createClient();
async function getUser(id) {
// 1. 尝试从缓存读取
const cached = await client.get(`user:${id}`);
if (cached) {
return JSON.parse(cached);
}
// 2. 缓存未命中,从数据库读取
const user = await pool.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
// 3. 存入缓存(1 小时过期)
await client.setex(
`user:${id}`,
3600,
JSON.stringify(user)
);
return user;
}
缓存失效策略
// 用户更新时清除缓存
async function updateUser(id, data) {
await pool.query(
'UPDATE users SET ... WHERE id = $1',
[id]
);
// 清除该用户的缓存
await client.del(`user:${id}`);
}
// 定期刷新热点数据
setInterval(async () => {
const topUsers = await pool.query(
'SELECT id FROM users ORDER BY last_login DESC LIMIT 100'
);
for (const user of topUsers.rows) {
const userData = await pool.query(
'SELECT * FROM users WHERE id = $1',
[user.id]
);
await client.setex(
`user:${user.id}`,
3600,
JSON.stringify(userData.rows[0])
);
}
}, 300000); // 每 5 分钟刷新一次
6. 常见性能问题诊断
问题 1:连接池耗尽
错误信息:Error: Client request error: timeout exceeded
原因:所有连接都被占用,新请求无可用连接
解决方案:
1. 检查是否有长连接泄漏
const result = await pool.query(...);
// ❌ 忘记释放连接
2. 增加连接池大小
max: 30, // 从 20 增加到 30
3. 优化查询速度,减少连接占用时间
问题 2:慢查询
-- 启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2; -- 超过 2 秒的查询
-- 查看慢查询日志
SHOW VARIABLES LIKE 'slow_query_log%';
TAIL -f /var/log/mysql/slow.log;
问题 3:死锁
-- 检测死锁
SHOW ENGINE INNODB STATUS\G
-- 解决方案:统一锁定顺序
-- ❌ 事务 A: 锁定表 1,然后表 2
-- ❌ 事务 B: 锁定表 2,然后表 1
-- ✅ 总是先锁表 1,再锁表 2
7. 数据库监控
// 建立性能监控
const dbMetrics = {
queryCount: 0,
totalQueryTime: 0,
slowQueries: []
};
function trackQuery(sql, duration) {
dbMetrics.queryCount++;
dbMetrics.totalQueryTime += duration;
if (duration > 1000) { // 慢查询阈值:1 秒
dbMetrics.slowQueries.push({
sql,
duration,
timestamp: new Date()
});
}
}
// 暴露监控接口
app.get('/api/metrics/database', (req, res) => {
res.json({
...dbMetrics,
avgQueryTime: dbMetrics.totalQueryTime / dbMetrics.queryCount,
slowQueryCount: dbMetrics.slowQueries.length
});
});
数据库性能优化清单
在投入生产前检查:
- 配置合理的连接池大小(通常 10-20)
- 为频繁查询的字段创建索引
- 避免 N+1 查询问题
- 使用缓存存储热点数据
- 启用慢查询日志并定期分析
- 实现连接池监控和告警
- 设定查询超时时间
- 定期进行数据库统计信息更新
- 使用 EXPLAIN 分析关键查询
- 制定备份和恢复计划
相关工具推荐: