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

时间:2018-01-12 16:44:05

标签: sql database postgresql row-level-security

我正在使用 PostgreSQL 10.1 ,正确到达......

假设我有 TABLE

CREATE TABLE public.document (
    id uuid PRIMARY KEY,

    title   text,
    content text NOT NULL
);

加上 GIN INDEX

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

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

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

无论 public.document 表大小如何,查询都是(你已经知道)hella fast!规划师使用INDEX,一切都很好。

现在我通过 RLS(行级安全性)介绍一些基本的访问控制,首先我启用它:

ALTER TABLE public.document ENABLE ROW LEVEL SECURITY;

然后我添加了政策:

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

为了简单起见, is_current_user 是另一个确切检查的查询。

现在使用 document_policy查询展平全文搜索查询,并通过这样做,规划人员执行 Seq扫描而不是索引扫描导致查询速度减慢300倍!

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

提前致谢!

2 个答案:

答案 0 :(得分:3)

我从发布时就已经解决了这个问题......任何人都面临这个问题,这就是我这样做的方式:

我的解决方案是拥有一个私有 SECURITY DEFINER“包装”功能,其中包含propper查询和另一个 public 函数,该函数调用 private 1和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;

由于我使用Postgraphile超级棒极了 BTW!),我能够省略对私有架构的内省,使其“危险” “功能无法进入!通过适当的安全实施,最终用户只能看到最终的GraphQL架构,从外部世界中删除 Postgres

这适用于beautifly! 直到最近 Postgres 10.3 发布并修复它,不再需要这个黑客。

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

使用RLS时,请注意以下事项:

  1. 创建正确的INDEXES
  2. 更喜欢各地的内联查询! (即使这意味着重写相同的查询N次)
  3. 一定要避免政策中的功能!如果你绝对必须将它们放在里面,请确保它们是STABLE并且有一个高COST(如@mkurtz指出的那样);或者是IMMUTABLE
  4. 从政策中提取查询,直接使用EXPLAIN ANALYZE运行,并尝试尽可能优化
  5. 希望你们能像我一样找到有用的信息!

答案 1 :(得分:1)

试试以下内容: 而不是将查询写入USING(...)子句,而是将查询放入具有非常高成本的STABLE函数中。 通过这样做,现在不应经常调用函数 - 理想情况下每个查询生命周期只调用一次,因为调用函数的成本现在看起来对Postgres非常高。将函数标记为STABLE告诉Postgres函数的结果在单个查询生存期内不会发生变化。我认为这对您的查询是正确的,不是吗? 详细了解这两个参数here

像这样:

CREATE OR REPLACE FUNCTION check_permission () RETURNS BOOLEAN AS $$
    SELECT EXISTS (
        SELECT 1 FROM public.user WHERE (is_current_user) AND ('r' = ANY(privileges))
    )
$$ LANGUAGE SQL STABLE COST 100000;

现在的政策是:

CREATE POLICY document_policy ON public.document FOR SELECT
    USING (check_permission());

希望这会给你带来更好的表现。但请注意,只有将函数标记为STABLE才可以正常工作。如果您的函数在单个查询生命周期内返回不同的结果,那么这将无法正常工作,您最终会得到奇怪的结果。