PostgreSQL 行级安全(RLS)学习笔记
从策略语法到连接池归还,把行级安全里最容易踩的两个坑完整记录下来。
展开阅读全文
行级安全(Row Level Security,简称 RLS)是数据库层面的一层访问控制:给表加上"哪些行对当前会话可见、可写"的规则之后,即使查询语句本身没有写任何过滤条件,数据库也会自动按策略筛掉不该看到的行。它最大的价值是把权限收口到数据库一侧,应用代码只需要负责设置身份,不再需要在每一条查询里手工拼过滤条件,从根上减少了漏写一处就泄露整表的风险。
策略的定义语法并不复杂:先用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY 打开开关,再用 CREATE POLICY 定义规则,通过 USING 约束可见行、WITH CHECK 约束写入行,FOR 子句区分 SELECT、INSERT、UPDATE、DELETE 等操作类型。策略里通常会用 current_setting() 读取一个会话变量作为判断依据,比如应用在建连后执行 SET app.tenant_id = 'xxx',策略里比较 current_setting('app.tenant_id') 和行上的租户列是否一致。在多 schema 的部署形态里,还要配合 search_path 的设置,让不同身份的会话各自命中属于自己的那套表。
学习过程中踩的第一个坑是"静默过滤出 0 行的假象"。调试时用一条没有任何 WHERE 条件的查询去看一张业务表,结果返回 0 行,第一反应是数据丢了,折腾了很久才发现是 RLS 策略在起作用——当前会话没有设置那个身份变量,策略把所有行都过滤掉了。它不报错、只返回空,非常具有迷惑性。从那以后我养成了习惯:裸查询返回意外的 0 行时,先怀疑行级安全,再怀疑代码。
第二个坑出在连接池上。会话变量设置之后会一直留在连接里,连接用完归还池子时如果没有重置,下一个借用这条连接的请求就会继承上一家的变量——要么看到不该看的数据,要么反过来被别人的策略过滤掉自己的数据,症状时隐时现,极难复现。修复思路有两个:在连接归还的钩子里统一 RESET 这些变量;或者改用事务级设置(set_config 的第三个参数传 true),让变量随事务结束自动失效,不依赖归还动作。我更倾向后者,因为它不依赖"记得归还时清理"这种约定,生命周期天然正确。
总结下来,RLS 本身不难,难的是它把"权限正确性"从显式的查询条件变成了隐式的环境状态,而环境状态最容易在连接复用、调试裸查这类场景里出问题。把变量生命周期管清楚,RLS 才能用得安心。