【问题标题】:postgresql row level security RESTRICTIVE policies write single select querypostgresql 行级安全 RESTRICTIVE 策略编写单选查询
【发布时间】:2022-01-03 05:48:17
【问题描述】:

下面是 3 个表格脚本。

CREATE TABLE rls_permission(upn text,is_all boolean ,reference int[]);
CREATE TABLE objects(key serial primary key,object_type_key int,status text );
CREATE TABLE object_attributes(key serial primary key,objects_key int,status text ,values text,reference int[], type_key int);
---Indexes
CREATE INDEX objects_object_type_key_status ON objects USING btree    (object_type_key, status )    

CREATE UNIQUE INDEX object_attributes_objects_key_type_key_uniq ON object_attributes (objects_key, type_key);

CREATE INDEX object_attributes_reference on object_attributes USING gin(value_reference gin__int_ops)

Web 应用程序将首先检索某些对象,然后检索其各自的对象属性值。一个对象在object_attributes表中可能有很多属性值。

rls_permission中配置的用户权限设置,如果is_all列的值为true,则用户可以看到所有行/对象,否则如reference列中提到的引用。 (引用值由另一个接口填充到rls_permission,该接口具有完全访问权限并从object_attributes获取值)

我在objects 表上创建了行级策略。


CREATE POLICY no_rls_objects ON objects AS PERMISSIVE FOR ALL TO PUBLIC USING (TRUE);

CREATE POLICY rls_on_objects ON objects AS RESTRICTIVE TO web_app_user
   USING( (current_setting('db.rls_user')='web_system')
          OR (SELECT per.is_all FROM rls_permission per
                WHERE (lower(per.upn) = 
                      lower(current_setting('db.rls_user'::text)))
              ) 
          OR (EXISTS ( SELECT 1 FROM object_attributes att
                               JOIN rls_permission per ON ((per.reference && att.reference)))
                                WHERE ((lower(per.upn) = lower(current_setting('db.rls_user'::text))) 
                                 AND (att.objects_key = objects.key) 
                       )
               )
          OR (object_type_key NOT IN (1,24))
          ) 

rls_on_objects RESTRICTIVE Policy 有两个用 OR 分隔的 SELECT 查询。

我无法创建两个 RESTRICTIVE 策略,每个策略中有一个查询,因为两个 RESTRICTIVE 策略将使用 AND 组合。但我需要使用 OR 组合两个查询。

有没有办法重写查询并进行单查询?

因为两个查询都有(lower(per.upn) = lower(current_setting('db.rls_user'::text))),所以它都有计算,当 is_all 为 false 或 null 时,它检查/执行第二个查询。进行单次查询将提高 RLS 性能,因为它不需要计算两次。

Update1:​​下面是我的策略和查询 select * from objects where status='active' and object_type_key=1的执行查询计划

Aggregate  (cost=122653500.78..122653500.79 rows=1 width=8) (actual time=3087.257..3087.357 rows=1 loops=1)
  InitPlan 1 (returns $0)
    ->  Seq Scan on rls_permission  (cost=0.00..27.40 rows=4 width=1) (actual time=0.014..0.015 rows=1 loops=1)
          Filter: (lower(upn) = lower(current_setting('db.rls_user'::text)))
          Rows Removed by Filter: 3
  ->  Index Scan using objects_object_type_key_status on objects  (cost=0.43..122650563.66 rows=1163888 width=0) (actual time=1454.965..3086.453 rows=7697 loops=1)
        Index Cond: (object_type_key = 1)
        Filter: (((current_setting('db.rls_user'::text) = 'web_system'::text) OR $0 OR (alternatives: SubPlan 2 or hashed SubPlan 3) OR (object_type_key <> ALL ('{1,24}'::integer[]))) AND (status = 'active'::enm_status))
        Rows Removed by Filter: 3024827
        SubPlan 2
          ->  Nested Loop  (cost=0.44..40.35 rows=1 width=0) (never executed)
                Join Filter: (per.reference && att.reference)
                ->  Index Scan using object_attributes_object_key_type_key_uniq on object_attributes att  (cost=0.44..12.90 rows=1 width=25) (never executed)
                      Index Cond: ((object_key = objects.key) AND (type_key = ANY ('{6,192}'::integer[])))
                ->  Seq Scan on rls_permission per  (cost=0.00..27.40 rows=4 width=32) (never executed)
                      Filter: (lower(upn) = lower(current_setting('db.rls_user'::text)))
        SubPlan 3
          ->  Nested Loop  (cost=1000.00..741248.75 rows=45627 width=4) (actual time=0.333..1449.186 rows=9079 loops=1)
                Join Filter: (per_1.reference && att_1.reference)
                Rows Removed by Join Filter: 1170632
                ->  Gather  (cost=1000.00..672780.72 rows=1140677 width=29) (actual time=0.256..1072.187 rows=1179711 loops=1)
                      Workers Planned: 2
                      Workers Launched: 2
                      ->  Parallel Seq Scan on object_attributes att_1  (cost=0.00..557713.02 rows=475282 width=29) (actual time=0.032..1214.816 rows=393237 loops=3)
                            Filter: (type_key = ANY ('{6,192}'::integer[]))
                            Rows Removed by Filter: 7566828
                ->  Materialize  (cost=0.00..27.42 rows=4 width=32) (actual time=0.000..0.000 rows=1 loops=1179711)
                      ->  Seq Scan on rls_permission per_1  (cost=0.00..27.40 rows=4 width=32) (actual time=0.009..0.010 rows=1 loops=1)
                            Filter: (lower(upn) = lower(current_setting('db.rls_user'::text)))
                            Rows Removed by Filter: 3
Planning Time: 0.423 ms
Execution Time: 3087.431 ms

Updated2--- Edouard H. 建议查询的查询计划

query1
Aggregate  (cost=239792868.03..239792868.04 rows=1 width=8) (actual time=33737.946..33737.947 rows=1 loops=1)
  ->  Index Scan using object_type_key_status on objects  (cost=0.43..239790834.63 rows=813358 width=0) (actual time=4.949..33735.611 rows=7697 loops=1)
        Index Cond: (object_type_key = 1)
        Filter: (((current_setting('db.rls_user'::text) = 'web_system'::text) OR (SubPlan 2) OR (object_type_key <> ALL ('{1,24}'::integer[]))) AND (status = 'active'::enm_status))
        Rows Removed by Filter: 3024827
        SubPlan 2
          ->  Seq Scan on rls_permission per  (cost=0.00..79.03 rows=4 width=1) (actual time=0.009..0.009 rows=1 loops=3032524)
                Filter: (lower(upn) = lower(current_setting('db.rls_user'::text)))
                Rows Removed by Filter: 3
                SubPlan 1
                  ->  Index Scan using object_attributes_objects_key_type_key_uniq on object_attributes att  (cost=0.44..12.91 rows=1 width=0) (actual time=0.005..0.005 rows=0 loops=3032524)
                        Index Cond: ((objects_key = objects.key) AND (type_key = ANY ('{6,192}'::integer[])))
                        Filter: (reference && per.reference)
                        Rows Removed by Filter: 0
Planning Time: 0.781 ms
Execution Time: 33738.102 ms

query2
Aggregate  (cost=122743462.16..122743462.17 rows=1 width=8) (actual time=24355.184..24355.186 rows=1 loops=1)
  ->  Index Scan using objects_object_type_key_status on objects  (cost=0.43..122741428.77 rows=813358 width=0) (actual time=1.222..24353.622 rows=7697 loops=1)
        Index Cond: (object_type_key = 1)
        Filter: (((current_setting('db.rls_user'::text) = 'web_system'::text) OR (SubPlan 1) OR (object_type_key <> ALL ('{1,24}'::integer[]))) AND (status = 'active'::enm_status))
        Rows Removed by Filter: 3024827
        SubPlan 1
          ->  GroupAggregate  (cost=40.36..40.38 rows=1 width=2) (actual time=0.007..0.007 rows=0 loops=3032524)
                Group Key: per.is_all
                ->  Sort  (cost=40.36..40.37 rows=1 width=26) (actual time=0.007..0.007 rows=0 loops=3032524)
                      Sort Key: per.is_all
                      Sort Method: quicksort  Memory: 25kB
                      ->  Nested Loop  (cost=0.44..40.35 rows=1 width=26) (actual time=0.006..0.006 rows=0 loops=3032524)
                            Join Filter: (att.reference && per.reference)
                            Rows Removed by Join Filter: 0
                            ->  Index Scan using object_attributes_objects_key_type_key_uniq on object_attributes att  (cost=0.44..12.90 rows=1 width=25) (actual time=0.004..0.004 rows=0 loops=3032524)
                                  Index Cond: ((objects_key = objects.key) AND (type_key = ANY ('{6,192}'::integer[])))
                            ->  Seq Scan on rls_permission per  (cost=0.00..27.40 rows=4 width=33) (actual time=0.004..0.004 rows=1 loops=1098057)
                                  Filter: (lower(upn) = lower(current_setting('db.rls_user'::text)))
                                  Rows Removed by Filter: 3
Planning Time: 0.340 ms
Execution Time: 24355.242 ms

谢谢

【问题讨论】:

    标签: sql postgresql query-optimization row-level-security


    【解决方案1】:

    我认为你担心的是错误的问题。

    重复的WHERE 子句不会是主要的性能问题。 OR 和必须为每个表行执行的连接子查询将是一个更大的问题。并不是说我知道如何改进它,除非使用更简单的权限系统。

    【讨论】:

    • 许可策略对所有用户都是正确的。但是它的 RESTRICTIVE 政策限制了一位用户。
    • 啊,你说得对。
    【解决方案2】:

    试试这样的:

    SELECT per.is_all
        OR EXISTS ( SELECT 1
                      FROM object_attributes att
                     WHERE att.reference && per.reference
                       AND att.objects_key = objects.key
                  )
      FROM rls_permission per
     WHERE lower(per.upn) = lower(current_setting('db.rls_user'::text))
    

    或者这可能会更快:

    SELECT per.is_all
        OR bool_or(att.reference IS NOT NULL)
      FROM rls_permission per
      LEFT JOIN object_attributes att
        ON att.reference && per.reference
     WHERE lower(per.upn) = lower(current_setting('db.rls_user'::text))
       AND att.objects_key = objects.key
     GROUP BY per.is_all
    

    【讨论】:

    • 感谢单个查询(在 50 秒内完成)。但这似乎比我使用两个 select 语句的查询(在 10 秒内完成)要慢得多。
    • @Hari - 我添加了一个可能更快的变体解决方案,但我没有对其进行测试。您可以执行您的查询吗?我建议您使用 EXPLAIN ANALYZE 并用结果更新您的问题,以便比较它们的执行计划?
    • 我为我的查询策略添加了查询计划。我还更新了我的完整 RLS 政策。还更新了您建议的查询的查询计划。
    猜你喜欢
    • 2022-07-07
    • 2018-04-30
    • 2020-11-19
    • 1970-01-01
    • 2019-09-24
    • 1970-01-01
    • 2021-05-19
    • 1970-01-01
    • 2023-01-23
    相关资源
    最近更新 更多