我正在开发一个应用程序来显示用户共享的内容,并且我正在使用 PostgreSQL 数组来处理安全模型。
我们支持公共(public)和私有(private)内容,我有两个查询需要优化。从 PostgreSQL 文档中我需要在索引数组列时使用 GIN 索引,但我无法让 PostgreSQL 选择它们。
这是我的数据和索引定义:
-- Table: public.usershares
-- DROP TABLE public.usershares;
CREATE TABLE public.usershares
(
id integer NOT NULL,
title text,
sharedcontent text,
shared_on timestamp without time zone,
roles text[],
claims text[],
users integer[],
private boolean,
CONSTRAINT pk_id PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
ALTER TABLE public.usershares
OWNER TO postgres;
-- Index: public.idx_arrays_private
-- DROP INDEX public.idx_arrays_private;
CREATE INDEX idx_arrays_private
ON public.usershares
USING gin
(roles COLLATE pg_catalog."default", claims COLLATE pg_catalog."default", users)
WHERE private = true AND claims <> '{}'::text[];
-- Index: public.idx_arrays_public
-- DROP INDEX public.idx_arrays_public;
CREATE INDEX idx_arrays_public
ON public.usershares
USING gin
(roles COLLATE pg_catalog."default", users)
WHERE private = false AND claims = '{}'::text[];
-- Index: public.idx_sharedon
-- DROP INDEX public.idx_sharedon;
CREATE INDEX idx_sharedon
ON public.usershares
USING btree
(shared_on DESC);
-- Index: public.idx_sharedon_notprivate
-- DROP INDEX public.idx_sharedon_notprivate;
CREATE INDEX idx_sharedon_notprivate
ON public.usershares
USING btree
(shared_on DESC)
WHERE private = false;
-- Index: public.idx_sharedon_private
-- DROP INDEX public.idx_sharedon_private;
CREATE INDEX idx_sharedon_private
ON public.usershares
USING btree
(shared_on DESC)
WHERE private = true;
查询#1:虽然我对此查询“idx_arrays_private”有一个部分 GIN 索引,但 PostgreSQL 使用“idx_sharedon_private”(这也是一个部分索引,但不包括数组列(角色、声明和用户)
select *
from usershares
where
( usershares.private = true
and usershares.claims != '{}'
and ( ( array['adminX'] && usershares.roles /* to see private content user has to belong to one of the roles */
and array['managerX'] <@ usershares.claims ) /* and have all the required claims */
or array[]::integer[] && usershares.users /* or just be member of the list of authorized users */ ) )
order by shared_on desc
limit 100
offset 100;
查询#2:虽然我也有这个查询“idx_arrays_public”的部分 GIN 索引,但 PostgreSQL 使用“idx_sharedon_notprivate”(这也是一个部分索引,但不包括数组列(角色、声明和用户) )
select *
from usershares
where
( usershares.private = false
and usershares.claims = '{}'
and ( array['admin'] && usershares.roles /* to see public content user has to belong to one of the roles */
or array[1,2,3,4,5] && usershares.users /* or be a member of the list of authorized users */
) )
order by shared_on desc
limit 100
offset 100;
生成测试数据的脚本:
TRUNCATE TABLE usershares;
INSERT INTO usershares
(id,
title,
sharedcontent,
shared_on,
roles,
claims,
users,
private)
SELECT x.id,
'title #'
|| x.id,
'content #'
|| x.id,
Now(),
array['admin','registered'],
'{}',
'{}',
false
FROM Generate_series(1, 500000) AS x(id);
INSERT INTO usershares
(id,
title,
sharedcontent,
shared_on,
roles,
claims,
users,
private)
SELECT x.id,
'title #'
|| x.id,
'content #'
|| x.id,
Now(),
array['admin','registered'],
array['manager', 'director'],
array[1,2,3,4,5,6,7,8,9,10],
true
FROM Generate_series(500001, 750000) AS x(id);
INSERT INTO usershares
(id,
title,
sharedcontent,
shared_on,
roles,
claims,
users,
private)
SELECT x.id,
'title #'
|| x.id,
'content #'
|| x.id,
Now(),
array['adminX','registeredX'],
array['managerX', 'directorX'],
'{}',
true
FROM Generate_series(750001, 1000000) AS x(id);
最佳答案
这似乎是两个因素的结合。
首先,规划者知道,考虑到测试数据的分布,索引的选择性不是很强;例如,claims='{}' 和 array['admin'] && Roles
仅将范围缩小到表的 50%,此时索引遍历可能并不比堆更好扫描。它还可以根据 private=false
条件消除同样多的行,只需从部分 shared_on
索引中提取每条记录(这似乎是它的首选方法)第二个查询)。
其次,LIMIT
子句意味着在过滤之前进行排序通常会更有效。在查询 #1 中使用 GIN 索引意味着查找所有 250,000 条匹配记录,对它们进行排序,然后丢弃最后 249,800 条。另一种方法是从 shared_on
索引中逐条提取记录,直到找到 200 个匹配项。
因此,它只是根据具体情况选择它认为最有效的方法。很难对结果提出异议。对我来说,使用 GIN 索引的第一个查询需要 140 毫秒,使用 B 树需要 0.3 毫秒。
考虑到匹配记录的比例较低,数组索引成为更有效的过滤器,并且预先进行排序变得不太可行(通过有效随机采样定位 200 个匹配可能需要更长的时间......) 。最终插入中有 10,000 条记录而不是 250,000 条记录,查询 #1 始终选择 GIN 索引。
关于数组上的 PostgreSQL GIN 索引,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36060091/