← 中文资源

简体中文版本

PostgreSQL 连接池与参数化查询

通过 pg 连接池访问 PostgreSQL:语句一律参数化防注入,超时与空闲回收显式配置,事务借还客户端有始有终。

阅读英文原文

示例说明

直连每次握手成本高,连接池复用 TCP 与后端进程。pg 的 query 第二参数数组让值与 SQL 文本分离,数据库驱动负责安全编码,从根本上杜绝字符串拼接注入;statement_timeout 在服务端兜底慢查询;idleTimeoutMillis 回收闲置客户端,防止池被慢慢占满。

技术要点

  • 占位符传值杜绝 SQL 注入路径
  • statement_timeout 服务端掐断慢语句
  • connect 与 release 必须严格配对

代码解读

listRecentOrders 把客户 ID 与上限作为参数传入,LIMIT 也参数化便于复用。withTransaction 从池里借出独占客户端执行 BEGIN 到 COMMIT 的单元,异常路径回滚,finally 释放连接。application_name 让慢查询日志能定位到具体服务。

使用建议

池大小按数据库总连接预算与实例数折算,不要单服务吃满。事务内不要做外部 HTTP 调用,连接占用时间会被拉长。超时值要与上游请求预算形成漏斗,逐层收紧。

代码示例

import pg from 'pg';

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 5_000,
  statement_timeout: 10_000,
  query_timeout: 12_000,
  application_name: 'orders-service',
});

pool.on('error', (err) => console.error('idle client error:', err.message));

async function listRecentOrders(customerId, limit = 25) {
  const { rows, rowCount } = await pool.query(
    'SELECT id, total, created_at FROM orders WHERE customer_id = $1 ORDER BY created_at DESC LIMIT $2',
    [customerId, limit],
  );
  return { rows, rowCount };
}

async function withTransaction(fn) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    const result = await fn(client);
    await client.query('COMMIT');
    return result;
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

const recent = await listRecentOrders('c-812');
console.log('found', recent.rowCount, 'orders');
await pool.end();