6

首先,这个 SO 问题描述了一个类似的问题:PostgreSQL query not using INDEX when RLS (Row Level Security) is enabled ,但我无法成功利用它的建议,也想看看是否有改进的方法Postgraphile 上下文中的事物。

复制步骤:

作为超级用户,创建一个简单的表并用一些随机数据填充它:

CREATE TABLE public.videos AS SELECT id, md5(random()::text) AS title from generate_Series(1,1000000) id;

执行 ILIKE 查询(在本文中进一步称为“ILIKE 查询”,用于多次测试性能):

EXPLAIN ANALYSE SELECT COUNT(*) FROM public.videos WHERE title ILIKE '%test%';

正如预期的那样,它执行 Seq Scan,执行时间约为 194.823 ms

安装 gp_trgm 扩展并添加一个 gin 索引:

CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
CREATE INDEX trgm_idx_videos_title ON public.videos USING gin (title gin_trgm_ops);

由于表已经填充了数据,因此创建索引需要一些时间(约 10 秒)。现在,运行相同的 ILIKE 查询将使用“trgm_idx_videos_title 上的位图索引扫描”,执行时间为 0.036 毫秒。

到目前为止一切似乎都很好,但前提是您始终可以使用超级用户获取数据并且不为所述表实施额外的安全性。

让我们设置一个额外的用户并授予它连接到我们的数据库的访问权限(名为 gin_rls_test)

CREATE ROLE db_login WITH LOGIN PASSWORD 'db_login_pwd' NOINHERIT;
GRANT CONNECT ON DATABASE gin_rls_test TO db_login;

我们还需要为该用户之前创建的表授予 SELECT 权限。

GRANT SELECT ON public.videos TO db_login;

为确保我们的用户能够以与目前相同的方式查询数据,请使用 db_login 连接到数据库服务器。(在 pgAdmin 中,您只需创建一个新服务器,指定一些不同的名称,相同的主机名,但使用 db_login/db_login_pwd 作为用户名和密码)

如果您使用新添加的 db_login 连接导航到我们的表,打开查询工具并执行相同的 ILIKE 查询 - 结果应该相同,将应用索引。

打破这一点的是 RLS(行级安全性)。让我们切换回超级用户的查询编辑器并为我们的表设置它:

ALTER TABLE public.videos ENABLE ROW LEVEL SECURITY;

CREATE OR REPLACE FUNCTION public.user_has_permission() returns boolean LANGUAGE plpgsql as $$
BEGIN
   return true;
END;
$$;

CREATE POLICY videos_authorization ON public.videos FOR SELECT USING (public.user_has_permission());

为简单起见,public.user_has_permission() 函数只返回 true。(在我的情况下,它是一个 plpgsql 函数,它检查存储在 pg_catalog.current_setting 中的设置的权限,并且不执行任何额外的显式请求。)

现在,如果您从超级用户查询编辑器运行 ILIKE 查询,它仍然会像以前一样超级快,因为超级用户避免了 rls。如果从 db_login 查询编辑器运行它,将不再命中索引,将使用 Seq Scan,执行时间约为 1013.485 毫秒。

通读此线程(https://www.postgresql.org/message-id/CAGrP7a3PwDYJhPe53yE6pBPPNxk2Ve4n%2BdPQMS1HcBU6swXYfA%40mail.gmail.com)后,似乎问题发生了,因为 ILIKE 的底层功能不是防漏的。EXPLAIN ANALYZE 告诉我们我们使用操作符 ~~* 进行文本比较,运行这个查询会让你知道这个操作符的底层函数的名称:

SELECT * FROM pg_operator WHERE oprname = '~~*';

实际上有 3 个结果,在我们的例子中,底层函数名称是“texticlike”。您可以做的是切换到超级用户查询编辑器并使该功能防泄漏:

ALTER FUNCTION texticlike LEAKPROOF;

现在,如果您再次从 db_login 查询编辑器运行 ILIKE 查询,索引将被命中并且执行时间将回到 0.040 毫秒。

问题:

也许使某些运算符显式防漏是可以接受的,但真正的问题(除了意外泄漏某些东西,例如如果抛出异常)是只有超级用户才能使函数防漏。如果您有 Azure 或 AWE 托管的数据库,您将没有超级用户访问权限,并且在尝试使函数防漏时会出现以下错误:

ERROR:  only superuser can define a leakproof function
SQL state: 42501

所以对我来说,关于如何使 GIN 索引适用于启用 RLS 的表上的 ILIKE 查询,这仍然是一个悬而未决的问题。或者,在仍然将该标题属性作为文本类型的同时实现相同性能结果的替代方法是什么?

我正在使用 Postgraphile,我对提高“includesInsensitive”和“startsWithInsensitive”过滤器的性能很感兴趣,ILIKE 运算符防漏也会影响这些过滤器。

附加信息:

如果您想将功能切换回不防漏:

ALTER FUNCTION texticlike NOT LEAKPROOF;

我尝试使用此查询来查找所有可用的防漏运算符,但在其中没有找到任何可行的替代方案(最接近的是运算符 ^@ 的“starts_with”函数,它区分大小写):

select pg_proc.proname, pg_operator.oprname, pg_operator.oprcode, pg_proc.proleakproof from pg_proc
join pg_operator ON pg_proc.proname::text = pg_operator.oprcode::text
where pg_proc.proleakproof;

使用 gp_trgm gin index 的最初想法来自这篇文章:https ://niallburkley.com/blog/index-columns-for-like-in-postgres/

PostgreSQL 版本(使用SELECT version();)-“x86_64-pc-linux-musl 上的 PostgreSQL 12.2,由 gcc (Alpine 9.2.0) 9.2.0, 64-bit 编译”

4

1 回答 1

1

您可以尝试使用没有安全屏障的视图并将 RLS 谓词直接复制到视图中:

create view view_video as
select * from videos
 where user_has_permission(); -- a predicate from RLS

并在不影响速度的情况下针对视图查询数据。由于视图查询被转换为对常规表的查询,因此将使用所有索引。

于 2020-07-26T08:46:38.487 回答