服务端与数据 / 进阶

数据库连接池:连接越多,不代表吞吐越高

连接池过大会让数据库上下文切换和内存压力上升。本文从部署实例数反推池大小,并设置获取超时、语句超时和泄漏保护。

从数据库总预算倒推单实例池大小

如果数据库允许 200 个连接,先为迁移、管理和后台任务预留 40 个。剩余 160 个连接由 8 个 API 实例共享,那么单实例上限最多约 20,而不是每个实例都配置 100。自动扩容时还要按最大实例数计算。

EXAMPLE / 01Connection Pool
单实例池上限 =
  (数据库最大连接数 - 运维预留 - 其他服务连接)
  / API 最大实例数

让排队和慢查询都有截止时间

connection timeout:等不到池中连接时尽快失败;statement timeout:阻止失控 SQL 长时间占用连接;idle transaction timeout:清理忘记提交的空闲事务;request timeout:应大于数据库超时,给应用留出包装错误的时间。

请求在池外无限排队会把一次数据库变慢扩大成整个服务雪崩。超时错误要单独计数,区分是连接获取慢还是 SQL 执行慢。

无论成功失败都释放连接

持续观察池总连接、空闲连接、等待队列长度和获取连接耗时。等待持续增长时,先找慢事务和泄漏,不要条件反射地扩大池。

EXAMPLE / 03Connection Pool
const client = await pool.connect();
try {
  await client.query('BEGIN');
  await runBusinessQueries(client);
  await client.query('COMMIT');
} catch (err) {
  await client.query('ROLLBACK');
  throw err;
} finally {
  client.release();
}