与内联版本相比,Postgres 行级安全策略优化不佳

Cal*_*mer 5 postgresql performance execution-plan row-level-security explain query-performance

我有一个看起来像这样的查询:

  SELECT post.id, post.author_id, post.published_at, post.content
    FROM post
   WHERE post.group_id = 1
ORDER BY post.published_at DESC, post.id
   LIMIT 5;
Run Code Online (Sandbox Code Playgroud)

(group_id, published_at DESC, id)当没有使用行级别安全性 (RLS) 策略时,此查询具有一个索引,该索引为其提供此查询计划。

 Limit  (cost=0.14..1.12 rows=5 width=143)
   ->  Index Scan using post_published_at on post  (cost=0.14..15.86 rows=80 width=143)
         Index Cond: (group_id = 1)
Run Code Online (Sandbox Code Playgroud)

然后我添加这个策略:

CREATE POLICY select_member_of ON post FOR SELECT USING
  (EXISTS (SELECT 1
             FROM group_member
            WHERE group_member.account_id = current_setting('current_account_id', false)::INT AND
                  group_member.group_id = post.group_id));
Run Code Online (Sandbox Code Playgroud)

有在化合物主键group_member.account_idgroup_member.group_idgroup_member表中。

我希望Postgres的计划此查询为仅索引扫描的group_member,因为这两个group_member.account_idgroup_member.group_id将被设定为恒定值。group_member.group_id由于WHERE post.group_id = 1上述SELECT查询中的条件,应该是常量。

事实上,当我将 RLS 策略内联到查询中时,这看起来正在发生,如下所示:

  SELECT id, author_id, published_at, content
    FROM post
   WHERE group_id = 1 AND
         (EXISTS (SELECT 1
                    FROM group_member
                   WHERE group_member.account_id = current_setting('current_account_id', false)::INT AND
                         group_member.group_id = post.group_id))
ORDER BY published_at DESC, id
   LIMIT 5;
Run Code Online (Sandbox Code Playgroud)

我得到查询计划:

 Limit  (cost=0.30..1.85 rows=5 width=143)
   ->  Nested Loop Semi Join  (cost=0.30..25.04 rows=80 width=143)
         ->  Index Scan using post_published_at on post  (cost=0.14..15.86 rows=80 width=147)
               Index Cond: (group_id = 1)
         ->  Materialize  (cost=0.16..8.19 rows=1 width=4)
               ->  Index Only Scan using group_member_pkey on group_member  (cost=0.16..8.18 rows=1 width=4)
                     Index Cond: ((account_id = (current_setting('current_account_id'::text, false))::integer) AND (group_id = 1))
Run Code Online (Sandbox Code Playgroud)

这就是我一直在寻找的。但是,当我使用真正的 RLS 策略运行查询时,查询计划变为:

 Limit  (cost=23.08..23.10 rows=5 width=143)
   ->  Sort  (cost=23.08..23.28 rows=80 width=143)
         Sort Key: post.published_at DESC, post.id
         ->  Subquery Scan on post  (cost=8.92..21.75 rows=80 width=143)
               ->  Nested Loop Semi Join  (cost=8.92..20.95 rows=80 width=147)
                     ->  Bitmap Heap Scan on post post_1  (cost=8.76..11.76 rows=80 width=147)
                           Recheck Cond: (group_id = 1)
                           ->  Bitmap Index Scan on post_published_at  (cost=0.00..8.74 rows=80 width=0)
                                 Index Cond: (group_id = 1)
                     ->  Materialize  (cost=0.16..8.20 rows=1 width=4)
                           ->  Subquery Scan on group_member  (cost=0.16..8.19 rows=1 width=4)
                                 ->  Index Only Scan using group_member_pkey on group_member group_member_1  (cost=0.16..8.18 rows=1 width=8)
                                       Index Cond: ((account_id = (current_setting('current_account_id'::text, false))::integer) AND (group_id = 1))
Run Code Online (Sandbox Code Playgroud)

哪个更糟。

这是预期的行为吗?有没有办法为我内联 RLS 策略的版本获得相同的查询计划?

Col*_*art 1

如果没有示例数据,\xe2\x80\x99 很难重现您的确切场景。

\n\n

在我看来,你应该使政策中的表达尽可能简单。在你的情况下,这将是:

\n\n
CREATE POLICY select_member_of ON post FOR SELECT USING (group_id IN (SELECT group_id\n            FROM group_member\n            WHERE account_id = current_setting('current_account_id', false)::INT));\n
Run Code Online (Sandbox Code Playgroud)\n