You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node连接池与PostgreSQL search_path:多租户连接绑定问题

多租户场景下PostgreSQL连接池的schema切换与连接绑定问题

不确保同一请求使用同一连接会引发的问题

是的,会引发严重的多租户数据隔离问题。因为PostgreSQL的search_path是连接级别的配置,连接池中的连接是被多个请求复用的:

  • 假设请求A获取连接后设置search_path = tenant_a,操作完成后将连接放回池;
  • 后续请求B复用这个连接,如果没有重新设置search_path,就会默认访问tenant_a的schema,导致请求B读取/修改不属于自己租户的数据,完全破坏多租户的隔离性。

确保同一客户端请求使用同一连接的方案

1. 请求生命周期绑定连接

在请求开始时从连接池获取专属连接,整个请求的所有数据库操作都使用该连接,请求结束后释放回池。这种方式能保证同一请求内的所有操作都基于同一个已设置好search_path的连接。

以Express框架为例,用中间件实现:

const { escapeIdentifier } = require('pg');
const pool = require('./your-pool-config');

// 中间件:为请求绑定专属连接并设置schema
app.use(async (req, res, next) => {
  try {
    // 从用户ID获取对应的租户schema
    const tenantSchema = getTenantSchemaByUserId(req.user.id);
    // 获取连接
    req.dbConn = await pool.connect();
    // 安全设置search_path(避免SQL注入)
    await req.dbConn.query(`SET search_path TO ${escapeIdentifier(tenantSchema)}`);
    next();
  } catch (err) {
    // 出错时释放连接
    if (req.dbConn) req.dbConn.release();
    next(err);
  }
});

// 路由中直接使用绑定的连接
app.get('/api/user/data', async (req, res, next) => {
  try {
    const result = await req.dbConn.query('SELECT * FROM user_profile');
    res.json(result.rows);
  } catch (err) {
    next(err);
  }
});

// 中间件:请求结束后释放连接
app.use((req, res, next) => {
  if (req.dbConn) req.dbConn.release();
  next();
});

2. 每次获取连接时强制设置search_path

如果不想绑定请求与连接,可以在每次从池里获取连接后,立即设置对应租户的search_path,再执行数据库操作。这种方式无需绑定请求生命周期,但每次操作都要额外执行一次SET语句。

封装工具函数示例:

const { escapeIdentifier } = require('pg');
const pool = require('./your-pool-config');

async function getTenantConnection(tenantSchema) {
  const conn = await pool.connect();
  try {
    // 安全设置当前连接的search_path
    await conn.query(`SET search_path TO ${escapeIdentifier(tenantSchema)}`);
    return conn;
  } catch (err) {
    conn.release();
    throw err;
  }
}

// 使用示例
async function fetchTenantData(userId) {
  const tenantSchema = getTenantSchemaByUserId(userId);
  const conn = await getTenantConnection(tenantSchema);
  try {
    const result = await conn.query('SELECT * FROM order_records');
    return result.rows;
  } finally {
    // 无论成功失败都释放连接
    conn.release();
  }
}

关键注意事项

  • 防止SQL注入:绝对不能直接拼接租户schema名称到SET语句中,必须使用pg库提供的escapeIdentifier函数转义特殊字符,避免注入风险。
  • 连接释放:无论使用哪种方案,都要确保连接最终被释放回池,避免连接泄漏导致池耗尽。

内容的提问来源于stack exchange,提问作者Jayadevan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 12:26:06