← 返回题目列表

PostgreSQL Row Level Security 是什么?适合做多租户隔离吗?

困难 第 31 / 31 题 更新于 2026/07/30
PostgreSQLRLS多租户

简化版

RLS 是 PostgreSQL 的行级安全策略,可以在数据库层按用户、租户或条件限制可见和可写行。它适合增强多租户隔离,但不能替代应用层权限设计;策略、连接池、超级用户绕过、上下文设置都要谨慎处理。

详细版

开启 RLS 后,可以为表定义策略,例如当前租户只能访问 tenant_id = current_setting('app.tenant_id') 的数据。

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::bigint);

这样即使 SQL 忘了写 tenant_id 条件,数据库层仍能过滤。但 RLS 带来调试、性能和上下文管理成本,尤其在连接池复用场景下必须正确设置和清理租户变量。

完整版教学

一、RLS 解决什么问题

多租户系统最怕 SQL 漏写租户条件。应用层每个查询都加 WHERE tenant_id = ?,只要某个地方漏掉,就可能越权读到别的租户数据。

RLS 把行过滤规则下沉到数据库层。查询表时,PostgreSQL 自动把策略条件附加进去。

它不是为了让应用层不用做权限,而是增加一道数据库级防线。

二、基本策略怎么写

可以用会话变量传递当前租户:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation
ON orders
USING (tenant_id = current_setting('app.tenant_id')::bigint)
WITH CHECK (tenant_id = current_setting('app.tenant_id')::bigint);

USING 控制可见行,WITH CHECK 控制插入和更新后的行是否允许存在。

三、连接池场景的坑

应用连接池会复用数据库连接。如果请求 A 设置了 app.tenant_id=1,请求结束后没有清理,连接被请求 B 复用,就可能带着错误租户上下文。

使用 transaction pooling 时更要小心,会话级变量可能不稳定。可以在每个事务开始显式设置,并在事务结束释放。

SET LOCAL app.tenant_id = '1001';

SET LOCAL 只在当前事务生效,比长期会话变量更安全。

四、RLS 的性能影响

RLS 本质上给查询增加过滤条件。如果 tenant_id 没有合适索引,性能会很差。

例如 1 亿行订单,每个租户 10 万行。如果没有 (tenant_id, created_at) 这样的索引,RLS 过滤仍可能扫描大量数据。

设计点原因
租户字段建索引支撑自动过滤
策略表达式保持简单避免复杂函数拖慢查询
用 EXPLAIN 验证确认计划可接受

五、哪些角色可能绕过 RLS

表 owner、超级用户、带 BYPASSRLS 属性的角色可能绕过 RLS。生产中要控制应用账号权限,不要让应用用超级用户连接。

还可以用 ALTER TABLE ... FORCE ROW LEVEL SECURITY 让表 owner 也受策略约束。是否使用要结合运维和迁移任务评估。

RLS 是安全机制,账号治理不严会让机制形同虚设。

六、常见误区与追问

  • 误区:用了 RLS 应用层就不用鉴权。 RLS 只管行访问,业务操作权限仍要应用层控制。
  • 误区:RLS 没有性能成本。 策略条件会进入查询计划,索引和表达式设计很关键。
  • 误区:连接池不会影响 RLS。 租户上下文设置错误会造成严重越权风险。
  • 追问:USINGWITH CHECK 区别? USING 控制可见行,WITH CHECK 控制写入后的行是否合法。
  • 追问:RLS 适合所有多租户吗? 适合作为防线之一,但强隔离还可能需要独立库、独立 schema 或独立集群。

七、加强记忆

记忆钩子:RLS 像数据库门口的保安,只让你看到自己租户的货架;但员工证、门禁系统和监控仍然要配齐。

回答这题要同时讲优点和边界:数据库层防漏条件、连接池上下文、索引性能、绕过权限。这样不会把 RLS 神化成万能隔离。