启用 RLS(行级安全)时,PostgreSQL 查询不使用 INDEX

eni*_*njo 8 sql database postgresql row-level-security

我正在使用PostgreSQL 10.1,直奔主题...

假设我有一个TABLE

CREATE TABLE public.document (
    id uuid PRIMARY KEY,

    title   text,
    content text NOT NULL
);
Run Code Online (Sandbox Code Playgroud)

连同上面的GIN INDEX

CREATE INDEX document_idx ON public.document USING GIN(
    to_tsvector(
        'english',
        content || ' ' || COALESCE(title, '')
    )
);
Run Code Online (Sandbox Code Playgroud)

还有一个基本的全文搜索查询:

SELECT * FROM public.document WHERE (
    to_tsvector(
        'english',
        content || ' ' || COALESCE(title, '')
    ) @@ plainto_tsquery('english', fulltext_search_documents.search_text)
)
Run Code Online (Sandbox Code Playgroud)

无论public.document表大小如何,查询都非常快(您已经知道了)!规划器使用 INDEX,一切都很好。

现在我通过RLS (Row Level Security)介绍一些基本的访问控制,首先我启用它:

ALTER TABLE public.document ENABLE ROW LEVEL SECURITY;
Run Code Online (Sandbox Code Playgroud)

然后我添加策略:

CREATE POLICY document_policy ON public.document FOR SELECT
    USING (EXISTS (
        SELECT 1 FROM public.user WHERE (is_current_user) AND ('r' = ANY(privileges))
    ));
Run Code Online (Sandbox Code Playgroud)

为了简单起见,is_current_user是另一个查询,它会准确地检查它。

现在全文搜索查询document_policy 查询扁平化,这样规划器执行Seq Scan而不是Index Scan导致查询速度降低 300 倍!

我认为这个问题很明显,我该如何解决这个问题,以便全文搜索查询保持快速?

提前致谢!

eni*_*njo 11

我从发布时就解决了这个问题......任何遇到这个问题的人,我都是这样做的:

我的解决方案是拥有一个包含 propper 查询的私有 SECURITY DEFINER“包装器”函数和另一个调用私有函数和需要访问控制的表的公共函数。INNER JOINS

所以在上面的特定情况下,它会是这样的:

CREATE FUNCTION private.filter_document() RETURNS SETOF public.document AS
$$
    SELECT * FROM public.document WHERE (
        to_tsvector(
            'english',
            content || ' ' || COALESCE(title, '')
        ) @@ plainto_tsquery('english', fulltext_search_documents.search_text)
    )
$$
LANGUAGE SQL STABLE SECURITY DEFINER;
----
CREATE FUNCTION public.filter_document() RETURNS SETOF public.document AS
$$
    SELECT filtered_d.* FROM private.filter_documents() AS filtered_d
        INNER JOIN public.document AS d ON (d.id = filtered_d.id)
$$
LANGUAGE SQL STABLE;
Run Code Online (Sandbox Code Playgroud)

由于我使用的是Postgraphile(顺便说一句,这真是太棒了!),我能够省略对私有模式的自省,使“危险”功能无法访问!通过适当的安全实现,最终用户只会看到最终的 GraphQL 模式,完全从外部世界中删除Postgres

这很有效! 直到最近Postgres 10.3 发布并修复它时,才不再需要这个 hack。

另一方面,我的 RLS 策略非常复杂,嵌套并且非常深入。它们再次运行的表也非常大(总共大约有 50,000 多个条目用于运行 RLS)。即使使用超级复杂和嵌套的策略,我也设法将性能保持在合理的范围内。

使用 RLS 时,请记住以下几点:

  1. 创建适当的 INDEXES
  2. 更喜欢无处不在的内联查询!(即使这意味着重写相同的查询 N 次)
  3. 一定要避免策略中的函数!如果你绝对必须把它们放在里面,请确保它们是STABLE高的COST(就像@mkurtz 指出的那样);或者是IMMUTABLE
  4. 从策略中提取查询,直接运行EXPLAIN ANALYZE并尝试尽可能优化它

希望你们发现这些信息和我一样有用!