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