PostgreSQL行级安全(RLS)实现客户软删除时性能不佳问题咨询
嘿,我之前在老版本PostgreSQL里踩过类似的RLS软删除性能坑,结合你给出的1000万条数据的测试环境,给你梳理下问题根源和实际可行的解决办法:
首先先明确你的基础表结构(方便后续分析):
CREATE TABLE customers ( customer_id integer PRIMARY KEY, name text, hidden boolean DEFAULT FALSE ); INSERT INTO customers (customer_id, name) SELECT generate_series(0, 9999999), 'John Doe'; ANALYZE customers;
为什么RLS软删除会导致性能差?
核心问题在于PostgreSQL 9.5的RLS机制是在查询时隐式追加过滤条件,如果你给customers表加了这样的软删除策略:
CREATE POLICY soft_delete_policy ON customers FOR SELECT USING (hidden = FALSE);
那每次查询都会自动带上hidden = FALSE的过滤逻辑,但如果hidden字段没有对应的索引,数据库就不得不做全表扫描——1000万条数据的全表扫描,性能肯定拉胯。
另外,9.5版本的RLS优化器支持还不够完善,有时候即使有索引,也可能无法正确命中,这也是老版本的通病。
具体优化方案
1. 给hidden字段创建部分索引(最关键的一步)
普通索引会存储所有记录的hidden值,但我们99%的查询都是针对未删除(hidden = FALSE)的记录,所以创建只包含未删除数据的部分索引,体积更小、查询更快:
CREATE INDEX idx_customers_active ON customers (customer_id) WHERE hidden = FALSE;
这里选择customer_id作为索引列,是因为它是主键,日常查询大概率会用它来过滤或关联,这样优化器可以同时利用主键索引和部分索引的过滤逻辑,直接定位到目标数据。
2. 优化RLS策略的写法
- 避免在策略中使用复杂表达式(比如函数、子查询),尽量用简单的字段比较,让优化器更容易识别并结合索引。
- 拆分通用策略为单独的
FOR SELECT、FOR UPDATE等策略,9.5版本对细分策略的执行效率更高,比如:-- 只针对查询的软删除策略 CREATE POLICY select_active_customers ON customers FOR SELECT USING (hidden = FALSE); -- 如果需要更新,再单独创建更新策略 CREATE POLICY update_active_customers ON customers FOR UPDATE USING (hidden = FALSE);
3. 尽量升级PostgreSQL版本(如果业务允许)
PostgreSQL 9.5已经是超老版本了(停止维护都好几年了),后续版本(10及以上)对RLS做了大量性能优化:
- 优化器能更智能地利用索引加速RLS过滤
- 减少了RLS策略带来的查询计划额外开销
- 新增了
pg_stat_user_policies等监控视图,方便排查策略执行的性能瓶颈
如果业务能兼容升级,这会是解决性能问题的根本办法。
4. 调整查询语句,引导优化器命中索引
避免写无过滤条件的SELECT * FROM customers,尽量带上customer_id这类主键/索引字段,比如:
-- 高效的查询:结合主键和RLS过滤 SELECT * FROM customers WHERE customer_id = 12345;
这样优化器会先通过主键索引定位到记录,再检查hidden字段,几乎是瞬间返回结果。
5. 用EXPLAIN ANALYZE排查瓶颈
执行查询计划分析,确认索引是否生效:
EXPLAIN ANALYZE SELECT * FROM customers WHERE hidden = FALSE LIMIT 100;
如果输出里显示Seq Scan(全表扫描),那说明索引没被用到,可能是统计信息过时(可以再跑一次ANALYZE customers;),或者索引创建不符合查询逻辑。
内容的提问来源于stack exchange,提问作者Backend Viking

