【问题标题】:Function index with multi-tenant infrastructure not working多租户基础设施的功能索引不起作用
【发布时间】:2021-07-28 15:44:49
【问题描述】:

我目前正在开发一个 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))

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

编辑 - 添加解释(分析,缓冲区)

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()

编辑 4 - 添加租户数据类型

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

【问题讨论】:

  • 你跑ANALYZE "table";了吗?
  • @LaurenzAlbe 是的,同样的输出
  • 请同时添加CREATE TABLE声明。
  • 我添加了声明
  • 难以置信。 psql 中的表的 \d 输出是什么样的?

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


【解决方案1】:

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

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

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

ANALYZE "table";

【讨论】:

    【解决方案2】:

    您应该添加一个 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
    

    【讨论】:

    • 我刚试过,不幸的是它不起作用。它仍在进行顺序扫描。
    • 你删除了upper_idx吗?尝试运行分析“表”。 “表”包含多少行?
    • 是的,我尝试删除它并运行 ANALYZE "table"。它有 599537 行。
    • 修改创建脚本使用ENUM类型的租户,但db<>fiddle仍然使用'upper_idx'
    猜你喜欢
    • 2021-05-14
    • 2022-11-14
    • 2010-10-31
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2023-03-20
    • 2021-01-10
    • 1970-01-01
    相关资源
    最近更新 更多