我正在使用 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倍!
我认为这个问题非常明显,如何解决这个问题,以便全文搜索查询保持快速?
提前致谢!
答案 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时,请注意以下事项:
INDEXES
STABLE
并且有一个高COST
(如@mkurtz指出的那样);或者是IMMUTABLE
EXPLAIN ANALYZE
运行,并尝试尽可能优化希望你们能像我一样找到有用的信息!
答案 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
才可以正常工作。如果您的函数在单个查询生命周期内返回不同的结果,那么这将无法正常工作,您最终会得到奇怪的结果。