ctl*_*eat 7 sql postgresql sql-execution-plan postgresql-performance foreign-data-wrapper
假设我想查询一个带有几个WHERE过滤器的大表。我正在使用 Postgres 11 和外部表;外部数据包装器 (FDW) 是clickhouse_fdw. 但我也对通用解决方案感兴趣。
我可以这样做:
SELECT id,c1,c2,c3 from big_table where id=3 and c1=2
Run Code Online (Sandbox Code Playgroud)
我的 FDW 能够对远程外部数据源进行过滤,确保上述查询快速并且不会拉取太多数据。
如果我写的话上面的效果是一样的:
SELECT id,c1,c2,c3 from big_table where id IN (3,4,5) and c1=2
Run Code Online (Sandbox Code Playgroud)
即所有的过滤都被发送到下游。
但是,如果我尝试执行的过滤稍微复杂一些:
SELECT bt.id,bt.c1,bt.c2,bt.c3
from big_table bt
join lookup_table l on bt.id=l.id
where c1=2 and l.x=5
Run Code Online (Sandbox Code Playgroud)
然后查询规划器决定远程过滤c1=2,但在本地应用另一个过滤器。
在我的用例中,先计算哪些ids 有l.x=5,然后将其发送出去进行远程过滤会快得多,所以我尝试按以下方式编写:
SELECT id,c1,c2,c3
from big_table
where c1=2
and id IN (select id from lookup_table where x=5)
Run Code Online (Sandbox Code Playgroud)
big_table然而,查询规划器仍然决定在本地对satisfy的所有结果执行第二个过滤器c1=2,这非常慢。
有什么方法可以“强制”(select id from lookup_table where x=5)进行预先计算并作为远程过滤器的一部分发送?
通常,连接或任何来自子查询或 CTE 的派生表在外部服务器上不可用,必须在本地执行。即,示例中简单WHERE子句之后剩余的所有行都必须像您观察到的那样在本地检索和处理。
如果所有其他方法都失败,您可以执行子查询SELECT id FROM lookup_table WHERE x = 5并将结果连接到查询字符串中。
更方便的是,您可以使用动态 SQL 和EXECUTEPL/pgSQL 函数自动执行此操作。喜欢:
CREATE OR REPLACE FUNCTION my_func(_c1 int, _l_id int)
RETURNS TABLE(id int, c1 int, c2 int, c3 int) AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT id,c1,c2,c3 FROM big_table
WHERE c1 = $1
AND id = ANY ($2)'
USING _c1
, ARRAY(SELECT l.id FROM lookup_table l WHERE l.x = _l_id);
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
有关的:
或者尝试在 SO 上搜索。
或者您可以在 psql 中使用元命令\gexec。看:
或者这可能会起作用:(反馈说不起作用。)
SELECT id,c1,c2,c3
FROM big_table
WHERE c1 = 2
AND id = ANY (ARRAY(SELECT id FROM lookup_table WHERE x = 5));
Run Code Online (Sandbox Code Playgroud)
在本地测试,我得到这样的查询计划:
在 big_table 上使用 big_table_idx 进行索引扫描(成本= ...)
索引条件:(id = ANY ( $0 ))
过滤器:(c1 = 2)
InitPlan 1(返回$0)
-> 对lookup_table进行顺序扫描(成本= ...)
过滤器:(x = 5)
大胆强调我的。
$0计划中的参数激发了希望。生成的数组可能是 Postgres 可以传递以供远程使用的东西。我没有看到与您的任何其他尝试或我自己尝试过的其他尝试有类似的计划。你能和你的fdw一起测试吗?
相关问题涉及postgres_fdw:
那是一个不同的故事。只需使用 CTE。但我不认为这对外籍家政工人有帮助。
WITH cte AS (SELECT id FROM lookup_table WHERE x = 5)
SELECT id,c1,c2,c3
FROM big_table b
JOIN cte USING (id)
WHERE b.c1 = 2;
Run Code Online (Sandbox Code Playgroud)
PostgreSQL 12改变(改进)了行为,因此在给定一些先决条件的情况下,CTE 可以像子查询一样内联。但是,引用手册:
您可以通过指定
MATERIALIZED强制单独计算WITH查询来覆盖该决定
所以:
WITH cte AS MATERIALIZED (SELECT id FROM lookup_table WHERE x = 5)
...
Run Code Online (Sandbox Code Playgroud)
通常,如果您的数据库服务器配置正确并且列统计信息是最新的,则不需要执行这些操作。但也存在数据分布不均匀的极端情况......