启用 RLS(行级安全性)时 PostgreSQL 查询不使用 INDEX
PostgreSQL query not using INDEX when RLS (Row Level Security) is enabled
我正在使用 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 table 大小,查询都非常快(您已经知道)!计划者使用 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 Scan 而不是 Index Scan 导致查询速度慢 300 倍!
我认为这个问题很明显,我该如何解决这个问题才能使全文搜索查询保持快速?
提前致谢!
尝试以下操作:
与其将查询写入 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
的情况下,这才能正常工作。如果您的函数在单个查询生命周期内可能 return 不同的结果,那么这将无法正常工作,您最终会得到奇怪的结果。
我从发帖的时候就解决了这个问题...任何遇到这个问题的人,我都是这样做的:
我的解决方案是有一个 private SECURITY DEFINER
"wrapper" 函数包含 propper 查询和另一个 public 调用 private 函数和 INNER JOINS
需要访问控制的 table 函数。
所以在上面的具体情况下,它会是这样的:
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(超级棒 顺便说一句!),我能够省略对 private 架构,使 "dangerous" 函数无法访问!通过适当的安全实施,最终用户将只能看到最终的 GraphQL 架构,完全从外界移除 Postgres。
效果很好! 直到最近 Postgres 10.3 发布并修复了它,不再需要这个 hack。
另一方面,我的 RLS 策略非常复杂、嵌套且非常深入。他们是 运行 的 tables 也相当大(总共大约有 50,000 多个 运行 RLS 反对条目)。即使使用超级复杂和嵌套的策略,我也设法将性能保持在合理的范围内。
使用 RLS 时,请记住以下几点:
- 创建合适的
INDEXES
- 到处都喜欢内联查询! (即使这意味着将同一个查询重写 N 次)
- 一定要避免策略中的函数!如果你绝对必须把它们放在里面,确保它们是
STABLE
并且有一个高 COST
(就像@mkurtz 指出的那样);或者是 IMMUTABLE
- 从策略中提取查询,运行直接使用
EXPLAIN ANALYZE
并尝试尽可能优化它
希望你们和我一样觉得这些信息很有帮助!
我正在使用 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 table 大小,查询都非常快(您已经知道)!计划者使用 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 Scan 而不是 Index Scan 导致查询速度慢 300 倍!
我认为这个问题很明显,我该如何解决这个问题才能使全文搜索查询保持快速?
提前致谢!
尝试以下操作:
与其将查询写入 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
的情况下,这才能正常工作。如果您的函数在单个查询生命周期内可能 return 不同的结果,那么这将无法正常工作,您最终会得到奇怪的结果。
我从发帖的时候就解决了这个问题...任何遇到这个问题的人,我都是这样做的:
我的解决方案是有一个 private SECURITY DEFINER
"wrapper" 函数包含 propper 查询和另一个 public 调用 private 函数和 INNER JOINS
需要访问控制的 table 函数。
所以在上面的具体情况下,它会是这样的:
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(超级棒 顺便说一句!),我能够省略对 private 架构,使 "dangerous" 函数无法访问!通过适当的安全实施,最终用户将只能看到最终的 GraphQL 架构,完全从外界移除 Postgres。
效果很好! 直到最近 Postgres 10.3 发布并修复了它,不再需要这个 hack。
另一方面,我的 RLS 策略非常复杂、嵌套且非常深入。他们是 运行 的 tables 也相当大(总共大约有 50,000 多个 运行 RLS 反对条目)。即使使用超级复杂和嵌套的策略,我也设法将性能保持在合理的范围内。
使用 RLS 时,请记住以下几点:
- 创建合适的
INDEXES
- 到处都喜欢内联查询! (即使这意味着将同一个查询重写 N 次)
- 一定要避免策略中的函数!如果你绝对必须把它们放在里面,确保它们是
STABLE
并且有一个高COST
(就像@mkurtz 指出的那样);或者是IMMUTABLE
- 从策略中提取查询,运行直接使用
EXPLAIN ANALYZE
并尝试尽可能优化它
希望你们和我一样觉得这些信息很有帮助!