我目前正在研究 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');
答案 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";