Back to Blog
2026-02-14ToolBox Team

数据库连接池与性能优化的科学

🔧 返回工具箱 | Back to Tools

浏览所有工具 | View All Tools
databaseperformanceconnection-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 分析关键查询
  • 制定备份和恢复计划

相关工具推荐