【问题标题】:PostgreSQL GIN indexes on arraysPostgreSQL GIN 数组上的索引
【发布时间】:2016-03-17 23:41:06
【问题描述】:

我正在开发一个应用程序来显示用户共享的内容,并且我正在使用 PostgreSQL 数组来处理安全模型。

我们支持公共和私人内容,我有两个查询需要优化。从 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;

QUERY #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;

QUERY #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); 

【问题讨论】:

    标签: postgresql indexing


    【解决方案1】:

    这似乎是两个因素的结合。

    首先,考虑到您的测试数据的分布情况,规划者知道指标的选择性不是很高;例如,claims='{}' and array['admin'] &amp;&amp; roles 仅将其缩小到表的 50%,此时索引遍历可能并不比堆扫描好。它还可以根据private=false 条件消除同样多的行,只需从部分shared_on 索引中提取每条记录(这似乎是第二个查询的首选方法)。

    其次,LIMIT 子句意味着在过滤之前进行排序通常会更有效。在查询 #1 中使用 GIN 索引意味着查找所有 250,000 条匹配记录,对它们进行排序,然后丢弃最后的 249,800 条记录。另一种方法是从shared_on 索引中逐个提取记录,直到找到200 个匹配项。

    因此,它只是根据情况选择它认为最有效的方法。很难与结果争论;对我来说,第一个查询使用 GIN 索引需要 140ms,使用 B-tree 需要 0.3ms。

    由于匹配记录的比例较低,数组索引成为一个更有效的过滤器,并且预先进行排序变得不太可行(通过有效的随机抽样定位 200 个匹配项可能需要更长的时间......) .在您的最终插入中包含 10,000 条记录而不是 250,000 条记录,查询 #1 始终选择 GIN 索引。

    【讨论】:

      猜你喜欢
      • 2013-11-26
      • 2018-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多