8

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

可以说我有一张桌子

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表大小如何,查询(您已经知道)非常快!规划师使用 INDEX,一切都很好。

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

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 查询展平,通过这样做,规划器执行序列扫描而不是索引扫描,导致查询速度降低 300 倍!

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

提前致谢!

4

2 回答 2

11

从发布之日起,我已经解决了这个问题......任何面临这个问题的人,我都是这样做的:

我的解决方案是拥有一个包含propper查询的私有 “包装器”函数和另一个调用私有函数和需要访问控制的表的公共函数。SECURITY DEFINERINNER 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(顺便说一句,这真是太棒了!),我能够省略私有模式的自省,使“危险”功能无法访问!通过适当的安全实现,最终用户将只能看到最终的 GraphQL 模式,从而将Postgres从外部世界中移除。

这很好用!直到最近Postgres 10.3 发布并修复它,不再需要这个 hack。

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

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

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

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

于 2018-05-07T15:27:16.130 回答
1

尝试以下操作:不要将查询写入USING(...)子句,而是将查询放入STABLE成本非常高的函数中。通过这样做,现在不应该经常调用该函数 - 理想情况下每个查询生命周期只调用一次,因为调用该函数的成本现在对 Postgres 来说似乎非常高。将函数标记为STABLE告诉 Postgres 该函数的结果在单个查询生命周期内不会改变。我认为这对您的查询是正确的,不是吗?在此处阅读有关这两个参数的更多信息。

像这样:

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。如果您的函数可以在单个查询生命周期内返回不同的结果,那么这将无法正常工作,您最终会得到奇怪的结果。

于 2018-05-07T11:24:07.140 回答