我目前正在尝试改进 postgres 查询。以下是在 postgres-12.6 上重现我的问题本质的最小设置:
CREATE TABLE data_node (id BIGINT PRIMARY KEY);
CREATE TABLE data_entry (id BIGINT PRIMARY KEY, node_fk BIGINT NOT NULL, FOREIGN KEY (node_fk) REFERENCES data_node (id));
CREATE INDEX node_ix ON data_entry USING BTREE (node_fk);
INSERT INTO data_node (id) SELECT generate_series(1,10);
INSERT INTO data_entry (id, node_fk) SELECT s, 2 FROM generate_series(1,10000000) s;
我正在尝试有效地过滤data_node
大表引用的小表的所有条目data_entry
。预计仅引用节点的子集,并且对于某些安装实例,可能仅引用单个节点。以下查询似乎很自然地完成了这项工作:
SELECT * FROM data_node
WHERE EXISTS (
SELECT 1 FROM data_entry WHERE node_fk = data_node.id
);
在VACUUM ANALYZE
两个表上 a 之后,这会产生以下查询计划:
Merge Join (cost=179055.16..179055.99 rows=1 width=8) (actual time=1895.155..1895.158 rows=1 loops=1)
Merge Cond: (data_node.id = data_entry.node_fk)
-> Index Only Scan using data_node_pkey on data_node (cost=0.14..8.29 rows=10 width=8) (actual time=0.004..0.008 rows=3 loops=1)
Heap Fetches: 0
-> Sort (cost=179055.02..179055.03 rows=1 width=8) (actual time=1895.143..1895.144 rows=1 loops=1)
Sort Key: data_entry.node_fk
Sort Method: quicksort Memory: 25kB
-> HashAggregate (cost=179055.00..179055.01 rows=1 width=8) (actual time=1895.135..1895.136 rows=1 loops=1)
Group Key: data_entry.node_fk
-> Seq Scan on data_entry (cost=0.00..154055.00 rows=10000000 width=8) (actual time=0.009..831.883 rows=10000000 loops=1)
在所示的最小设置中,查询需要几秒钟(在实际的最小应用场景中,需要几分钟),尽管使用较大表上的索引,它应该在不到一毫秒的时间内完成。通过设置SET enable_seqscan = false;
,我能够让 postgres 使用“好”的查询计划:
Nested Loop Semi Join (cost=0.57..12.86 rows=1 width=8) (actual time=0.029..0.044 rows=1 loops=1)
-> Index Only Scan using data_node_pkey on data_node (cost=0.14..8.29 rows=10 width=8) (actual time=0.003..0.004 rows=10 loops=1)
Heap Fetches: 0
-> Index Only Scan using node_ix on data_entry (cost=0.43..185308.04 rows=10000000 width=8) (actual time=0.004..0.004 rows=0 loops=10)
Index Cond: (node_fk = data_node.id)
Heap Fetches: 0
Planning Time: 0.269 ms
Execution Time: 0.068 ms
虽然这在我的测试设置中提供了我想要的东西,但全局更改enable_seqscan
当然不是生产环境的可行解决方案。
是否有任何“干净”的方法来确保 postgres 使用“好”查询计划或性能相当的计划来过滤引用的小表条目?