多租户基础架构不工作的功能索引

时间:2021-05-06 10:21:31

标签: postgresql query-optimization multi-tenant database-indexes

我目前正在研究 Postgresql 数据库,我们在其中实施了多租户基础架构。

这是它的工作原理: 我们有几个表(table1、tables2、...),我们在其中添加了 tenant 列。此列用于根据不同的数据库用户过滤行。我们有几个用户(tenant1、tenant2、...)和一个超级用户(没有应用到它的租户)。

我想优化以下简单的查询:

SELECT id
FROM table
WHERE UPPER("table"."column"::text) = UPPER('blablabla')

因此,我创建了一个函数索引:

CREATE INDEX "upper_idx" ON "table" (upper("column") );

如果我以 superuser 身份连接到数据库并执行我的 SELECT 查询,它会运行流畅且快速。

Bitmap Heap Scan on table  (cost=71.66..9225.47 rows=2998 width=4)
  Recheck Cond: (upper((column)::text) = 'blablabla'::text)
  ->  Bitmap Index Scan on upper_idx  (cost=0.00..70.91 rows=2998 width=0)
        Index Cond: (upper((column)::text) = 'blablabla'::text)

但是,当我以 tenant1 身份连接时,索引没有被选取,数据库运行顺序扫描:

Gather  (cost=1000.00..44767.19 rows=15 width=4)
  Workers Planned: 2
  ->  Parallel Seq Scan on table  (cost=0.00..43765.69 rows=6 width=4)
        Filter: (((tenant)::text = (CURRENT_USER)::text) AND (upper((column)::text) = 'blablabla'::text))

你知道如何让它在这种情况下工作吗?

编辑 - 添加了 EXPLAIN(ANALYZE, BUFFER)

Gather  (cost=1000.00..44767.19 rows=15 width=4) (actual time=502.601..503.466 rows=0 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  Buffers: shared hit=781 read=36160
  ->  Parallel Seq Scan on table  (cost=0.00..43765.69 rows=6 width=4) (actual time=498.978..498.978 rows=0 loops=3)
        Filter: (((tenant)::text = (CURRENT_USER)::text) AND (upper((column)::text) = 'blablabla'::text))
        Rows Removed by Filter: 199846
        Buffers: shared hit=781 read=36160
Planning Time: 1.650 ms
Execution Time: 503.510 ms

EDIT 2 - 添加(截断的)CREATE TABLE 语句

-- public.table definition

-- Drop table

-- DROP TABLE public.table;

CREATE TABLE public.table (
    id serial NOT NULL,
    created timestamptz NOT NULL,
    modified timestamptz NOT NULL,
    ...
    column varchar(100) NULL,
    "tenant" tenant NOT NULL,
    ...

);

...
CREATE INDEX upper_idx ON public.table USING btree (upper((column)::text), tenant);
CREATE INDEX table_column_91bdd18f ON public.table USING btree (column);
CREATE INDEX table_column_91bdd18f_like ON public.table USING btree (column varchar_pattern_ops);
...

-- Table Triggers

create trigger archive_deleted_rows after
delete
    on
    public.table for each row execute procedure archive.archive('{id}');
create trigger set_created_modified before
insert
    or
update
    on
    public.table for each row execute procedure set_created_modified();
create trigger set_tenant before
insert
    or
update
    on
    public.table for each row execute procedure set_tenant();


-- public.table foreign keys

...

编辑 3 - \d table 的转储

                                                      Table "public.table"
                  Column                  |           Type           | Collation | Nullable |                  Default                   
------------------------------------------+--------------------------+-----------+----------+--------------------------------------------
 id                                       | integer                  |           | not null | nextval('table_id_seq'::regclass)
 ........
 column                                   | character varying(100)   |           |          | 
 tenant                                   | tenant                   |           | not null | 
 ........
Indexes:
    "table_pkey" PRIMARY KEY, btree (id)
    ..........
    "table__column_upper_idx" btree (upper(column::text), tenant)
    "table_column_91bdd18f" btree (column)
    "table_column_91bdd18f_like" btree (column varchar_pattern_ops)
    .........
Check constraints:
    .........
Foreign-key constraints:
    .........
Referenced by:
    .........
Policies:
    POLICY "tenant_policy"
      TO tenant1,tenant2
      USING (((tenant)::text = (CURRENT_USER)::text))
Triggers:
    ........
    set_tenant BEFORE INSERT OR UPDATE ON table FOR EACH ROW EXECUTE PROCEDURE set_tenant()

EDIT 4 - 添加租户数据类型

CREATE TYPE tenant AS ENUM (
    'tenant1',
    'tenant2');

2 个答案:

答案 0 :(得分:0)

您应该添加一个 multi-column 索引,例如 whis:

CREATE INDEX "upper_column_for_tenant_idx" ON "table" (upper("column") , tenant);

为了只有一个索引,您应该先放置 upper("column") 然后是租户。

PostgreSQL docs 状态:

<块引用>

多列 B 树索引可与涉及索引列的任何子集的查询条件一起使用,但当对前导(最左侧)列存在约束时,该索引的效率最高。

我已尝试在 db<>fiddle 中重新创建您的设置。可以看到查询计划

EXPLAIN ANALYZE
SELECT * FROM public.table WHERE upper(("column")::text) = '50' and tenant='5'

是:

QUERY PLAN
Bitmap Heap Scan on "table"  (cost=14.20..2333.45 rows=954 width=24) (actual time=0.093..0.831 rows=1000 loops=1)
  Recheck Cond: ((upper(("column")::text) = '50'::text) AND ((tenant)::text = '5'::text))
  Heap Blocks: exact=74
  ->  Bitmap Index Scan on upper_idx  (cost=0.00..13.97 rows=954 width=0) (actual time=0.065..0.065 rows=1000 loops=1)
        Index Cond: ((upper(("column")::text) = '50'::text) AND ((tenant)::text = '5'::text))
Planning Time: 0.361 ms
Execution Time: 0.997 ms

答案 1 :(得分:0)

您应该在两列上创建索引。由于 tenant 是枚举数据类型,并且您将其与 name 类型的函数结果进行比较,因此双方都被强制转换为“公分母”text。所以使用这个索引:

CREATE INDEX ON "table" (upper("column"), tenant::text);

然后计算表的新统计数据:

ANALYZE "table";
相关问题