示例说明
直连每次握手成本高,连接池复用 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();