Postgresql 如何为三个表的简单联接优化执行计划

Postgresql 如何为三个表的简单联接优化执行计划,postgresql,explain,Postgresql,Explain,我有一个相对简单的表结构和查询,但得到的执行计划似乎并不理想。尤其是执行时间让我感到疑惑。 这是我的表格结构: CREATE TABLE t_c ( c_id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL, cn character varying NOT NULL, cs character varying, c

我有一个相对简单的表结构和查询,但得到的执行计划似乎并不理想。尤其是执行时间让我感到疑惑。 这是我的表格结构:

CREATE TABLE t_c (
        c_id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL,
        cn character varying NOT NULL,
        cs character varying,
        cp character varying
);

CREATE TABLE t_t (
        t_id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL,
        tp character varying,
        tn character varying,
        ts bigint NOT NULL,
        tt character varying,
        tii character varying(256) NOT NULL
);
 
CREATE TABLE t_t_c (
        t_id uuid NOT NULL,
        c_id uuid NOT NULL,
        mc_id uuid NOT NULL
);

ALTER TABLE ONLY t_c ADD CONSTRAINT t_c_pkey PRIMARY KEY (c_id);
ALTER TABLE ONLY t_t ADD CONSTRAINT t_t_pkey PRIMARY KEY (t_id);

CREATE INDEX t_c_cn_idx ON t_c USING btree (cn);
CREATE INDEX t_t_tii_idx ON t_t USING btree (tii);

ALTER TABLE ONLY t_c ADD CONSTRAINT t_c_unique UNIQUE(cn, cs, cp);

CREATE UNIQUE INDEX idx_cid_tid ON t_t_c USING btree (c_id, t_id);
CREATE UNIQUE INDEX idx_tid_cid ON t_t_c USING btree (t_id, c_id);
ALTER TABLE ONLY t_t_c ADD CONSTRAINT t_t_c_cid_fkey FOREIGN KEY (c_id) REFERENCES t_c(c_id);
ALTER TABLE ONLY t_t_c ADD CONSTRAINT t_t_c_mcid_fkey FOREIGN KEY (mc_id) REFERENCES t_c(c_id);
ALTER TABLE ONLY t_t_c ADD CONSTRAINT t_t_c_tid_fkey FOREIGN KEY (t_id) REFERENCES t_t(t_id);
tuc和tut都有大约200万行,tu-tu-c有大约20亿行

这是我要运行的查询:

explain analyze select t_t.tii from t_t_c ttc join t_t tt on tt.t_id=ttc.t_id
JOIN t_c c on c.c_id=ttc.c_id
where c.cn = 'xxx'
group by t_t.tii
这导致:

Group  (cost=5118006.20..5119624.81 rows=718 width=8) (actual time=231430.737..233032.234 rows=712 loops=1)
  Group Key: t.tii
  ->  Gather Merge  (cost=5118006.20..5119621.22 rows=1436 width=8) (actual time=231430.730..233497.475 rows=937 loops=1)
        Workers Planned: 2
        Workers Launched: 2
        ->  Group  (cost=5117006.18..5118455.45 rows=718 width=8) (actual time=231244.223..232715.561 rows=312 loops=3)
              Group Key: t.tii
              ->  Sort  (cost=5117006.18..5117730.81 rows=289854 width=8) (actual time=231244.213..231965.197 rows=295104 loops=3)
                    Sort Key: t.tii
                    Sort Method: quicksort  Memory: 25821kB
                    Worker 0:  Sort Method: quicksort  Memory: 27140kB
                    Worker 1:  Sort Method: quicksort  Memory: 25404kB
                    ->  Parallel Hash Join  (cost=212629.56..5090709.22 rows=289854 width=8) (actual time=3618.432..229889.447 rows=295104 loops=3)
                          Hash Cond: (ttc.t_id = tt.t_id)
                          ->  Nested Loop  (cost=0.70..4877319.49 rows=289854 width=16) (actual time=1.869..224573.547 rows=295104 loops=3)
                                ->  Parallel Seq Scan on t_c c  (cost=0.00..54571.92 rows=408 width=16) (actual time=0.443..220.151 rows=310 loops=3)
                                      Filter: ((cn)::text = 'xxx'::text)
                                      Rows Removed by Filter: 652230
                                ->  Index Only Scan using idx_cid_tid on t_t_c ttc  (cost=0.70..11740.18 rows=8028 width=32) (actual time=0.884..719.911 rows=952 loops=930)
                                      Index Cond: (c_id = c.c_id)
                                      Heap Fetches: 885317
                          ->  Parallel Hash  (cost=201875.05..201875.05 rows=860305 width=24) (actual time=3599.908..3599.911 rows=692137 loops=3)
                                Buckets: 2097152  Batches: 1  Memory Usage: 130208kB
                                ->  Parallel Seq Scan on t_t t  (cost=0.00..201875.05 rows=860305 width=24) (actual time=0.057..1950.674 rows=692137 loops=3)

                            
总体执行时间约为4分钟。尤其是散列连接嵌套循环需要很长时间。 是否有需要优化的内容,或者添加另一个索引? 我也不确定uuid是否是t_id、c_id(主键/外键)的最佳数据类型。也许整数数据类型可以提高性能

Postgres版本是11.6

多谢各位

基督教徒

编辑: 使用EXISTS()的修改后的查询会产生不同的执行计划,但执行时间几乎相同,可能会比原来多10%:

Gather  (cost=5043832.59..5251301.23 rows=30951 width=8) (actual time=210773.943..217715.805 rows=853778 loops=1)
  Workers Planned: 2
  Workers Launched: 2
  ->  Parallel Hash Semi Join  (cost=5042832.59..5247206.13 rows=12896 width=8) (actual time=210762.956..217221.553 rows=284593 loops=3)
        Hash Cond: (tt.t_id = ttc.t_id)
        ->  Parallel Seq Scan on t_t tt  (cost=0.00..201875.05 rows=860305 width=24) (actual time=0.393..4337.062 rows=698756 loops=3)
        ->  Parallel Hash  (cost=5039175.26..5039175.26 rows=292587 width=16) (actual time=210754.705..210754.707 rows=297660 loops=3)
              Buckets: 1048576  Batches: 1  Memory Usage: 50144kB
              ->  Nested Loop  (cost=0.70..5039175.26 rows=292587 width=16) (actual time=1.127..209827.976 rows=297660 loops=3)
                    ->  Parallel Seq Scan on t_c c  (cost=0.00..55789.33 rows=417 width=16) (actual time=0.961..394.992 rows=314 loops=3)
                          Filter: ((cn)::text = 'xxx'::text)
                          Rows Removed by Filter: 667056
                    ->  Index Only Scan using idx_cid_tid on t_t_c ttc  (cost=0.70..11869.46 rows=8111 width=32) (actual time=2.031..662.330 rows=947 loops=943)
                          Index Cond: (c_id = c.c_id)
                          Heap Fetches: 892980
Planning Time: 0.475 ms
Execution Time: 219290.828 ms

如果未从主查询中选择任何列,请避免在主查询中联接表。相反,将它们下推到一个
EXISTS(相关子查询)

简单重写,避免(愚蠢的)
分组方式



如果未从主查询中选择任何列,请避免在主查询中联接表。相反,将它们下推到一个
EXISTS(相关子查询)

简单重写,避免(愚蠢的)
分组方式



也许重新排列桌子会有帮助,尽管我对此表示怀疑。这将使查询更具可读性

select t_t.tii from 
t_c c
JOIN t_t_c ttc USING (c_id)
JOIN t_t tt USING (t_id)
where c.cn = 'xxx'
group by t_t.tii
从您所做的测试来看,cn是一个非常有选择性的列,而“SELECT*From t t_c WHERE cn=…”很快。postgres以错误的顺序加入,这真的很奇怪。因此,您可以强制postgres从这个表开始,稍后再加入其他表:

WITH cids AS MATERIALIZED( SELECT c_id FROM c WHERE cn=... )
SELECT DISTINCT t_t.tii from 
cids
JOIN t_t_c ttc USING (c_id)
JOIN t_t tt USING (t_id)
注意:我询问了cn最常见值的最大行数,以避免这种物化CTE变得巨大的可能性。但3万行就行了

你也可以这样做:

SELECT DISTINCT t_t.tii from t_t
WHERE t_id IN (SELECT t_id FROM t_t_c WHERE c_id IN (
    SELECT c_id FROM t_c WHERE cn=...
))
如果多个c_id在t_t_c中具有相同的c_id,这可能会更快,因为in()会删除重复项。如果情况并非如此,那么删除重复项将是浪费时间,因此可能会更慢。我对结果感兴趣


如果上面的查询坚持不使用链接表t_t_c上的多列索引(它们肯定应该使用),那么这应该给出一条线索,说明为什么原始查询的计划如此糟糕,问题是,为什么不使用索引?可能是排序问题,或者是类型问题,什么的。试着在tuc和tutuc之间加入,看看会发生什么。正如我上面所说的,它以错误的顺序连接表是非常奇怪的,因此可能在某个地方存在一个问题,目前还不明显,但表现为一个索引,不能在应该的时候使用。

也许重新排序表会有所帮助,尽管我对此表示怀疑。这将使查询更具可读性

select t_t.tii from 
t_c c
JOIN t_t_c ttc USING (c_id)
JOIN t_t tt USING (t_id)
where c.cn = 'xxx'
group by t_t.tii
从您所做的测试来看,cn是一个非常有选择性的列,而“SELECT*From t t_c WHERE cn=…”很快。postgres以错误的顺序加入,这真的很奇怪。因此,您可以强制postgres从这个表开始,稍后再加入其他表:

WITH cids AS MATERIALIZED( SELECT c_id FROM c WHERE cn=... )
SELECT DISTINCT t_t.tii from 
cids
JOIN t_t_c ttc USING (c_id)
JOIN t_t tt USING (t_id)
注意:我询问了cn最常见值的最大行数,以避免这种物化CTE变得巨大的可能性。但3万行就行了

你也可以这样做:

SELECT DISTINCT t_t.tii from t_t
WHERE t_id IN (SELECT t_id FROM t_t_c WHERE c_id IN (
    SELECT c_id FROM t_c WHERE cn=...
))
如果多个c_id在t_t_c中具有相同的c_id,这可能会更快,因为in()会删除重复项。如果情况并非如此,那么删除重复项将是浪费时间,因此可能会更慢。我对结果感兴趣

如果上面的查询坚持不使用链接表t_t_c上的多列索引(它们肯定应该使用),那么这应该给出一条线索,说明为什么原始查询的计划如此糟糕,问题是,为什么不使用索引?可能是排序问题,或者是类型问题,什么的。试着在tuc和tutuc之间加入,看看会发生什么。正如我在上面所说的,它以错误的顺序连接表是非常奇怪的,因此可能在某个地方存在一个目前不明显的问题,但表现为一个索引,不能在应该使用的时候使用

堆取数:885317

这可能是你大部分时间都在做的事情。真空t____c,以便仅索引扫描有效

如果这不起作用,那么打开track\u io\u timing并显示查询的
解释(分析,缓冲区)

堆取数:885317

这可能是你大部分时间都在做的事情。真空t____c,以便仅索引扫描有效


如果这不起作用,那么打开track\u io\u timing并显示查询的
解释(分析,缓冲)

t.tii
::查询中没有表或别名
t
<代码>外键(mc_id)::没有将
mc_id
作为第一列的索引。另外:您仅从一个表中选择(disticnt)
select t t.ti…
::您可以将其他两个表推到
EXISTS(…)
术语中。抱歉,这是一个输入错误,这是t_t。直到在t_c中引用c_id的外键mc_id更能确保一致性。你认为索引会有帮助吗?m_c不是查询的一部分,所以我认为它在这里没有任何影响。我不在乎它是否有帮助。在我看来,对于1GB桥接表,FK的支持索引是必需的。(试着想象一下,如果没有它,删除其他表的代价会有多大)解释ANALYZE SELECT*FROM t_c,其中cn='xxx'返回:
在t_c上的位图堆扫描(cost=18.68..1443.06 rows=1000 width=145)(实际时间=0.341..3.749 rows=943循环=1)重新检查条件:((cn)::text='xxx':text)堆块:精确=938->t_c_cn_idx上的位图索引扫描(成本=0.00..18.43行=1000宽度=0)(实际时间=0.201..0.203行=943循环=1)索引条件:((cn)::text='xxx'::text