Postgres_FDW 没有推低 WHERE 标准

Posted

技术标签:

【中文标题】Postgres_FDW 没有推低 WHERE 标准【英文标题】:Postgres_FDW not pushing down WHERE criteria 【发布时间】:2018-05-03 22:44:34 【问题描述】:

我正在使用两个 PostgreSQL 9.6 数据库,并尝试使用 postgres_fdw 从另一个数据库中查询一个(一个是具有数据的生产备份数据库,另一个是用于进行各种分析的数据库)。

我遇到了一些奇怪的行为,尽管查询中的某些类型的 WHERE 子句没有传递给远程数据库,而是保留在本地数据库中并用于过滤从远程数据库接收到的结果。这导致远程数据库尝试通过网络发送比本地数据库需要的信息更多的信息,并且受影响的查询速度非常慢(15 秒对 15 分钟)。

我经常看到这个与时间戳相关的子句,下面的例子是我第一次遇到这个问题的方式,但我已经在其他几个变体中看到它,例如用 TIMESTAMP 文字(慢)或 TIMESTAMP WITH 替换 CURRENT_TIMESTAMP时区文字(快速)。

是否有我错过的某个设置可以帮助解决这个问题?我正在将此设置为一个团队使用混合级别的 SQL 背景,大多数人没有审查 EXPLAIN 计划之类的经验。我想出了一些解决方法(例如将相对时间子句放在子 SELECT 中),但我不断遇到新的问题实例。

一个例子:

SELECT      var_1
           ,var_2
FROM        schema_A.table_A
WHERE       execution_ts <= CURRENT_TIMESTAMP - INTERVAL '1 hour'
        AND execution_ts >= CURRENT_TIMESTAMP - INTERVAL '1 week' - INTERVAL '1 hour'
ORDER BY    var_1

解释计划

Sort  (cost=147.64..147.64 rows=1 width=1048)
  Output: table_A.var_1, table_A.var_2
  Sort Key: (table_A.var_1)::text
  ->  Foreign Scan on schema_A.table_A  (cost=100.00..147.63 rows=1 width=1048)
        Output: table_A.var_1, table_A.var_2
        Filter: ((table_A.execution_ts <= (now() - '01:00:00'::interval)) 
             AND (table_A.execution_ts >= ((now() - '7 days'::interval) - '01:00:00'::interval)))
        Remote SQL: SELECT var_1, execution_ts FROM model.table_A
                    WHERE ((model_id::text = 'ABCD'::text))
                      AND ((var_1 = ANY ('1,2,3,4,5'::bigint[])))

上面的运行大约需要 15-20 分钟,而下面的运行只需几秒钟。

SELECT      var_1
           ,var_2
FROM        schema_A.table_A
WHERE       execution_ts <= (SELECT CURRENT_TIMESTAMP - INTERVAL '1 hour')
        AND execution_ts >= (SELECT CURRENT_TIMESTAMP - INTERVAL '1 week' - INTERVAL '1 hour')
ORDER BY    var_1

解释计划

Sort  (cost=158.70..158.71 rows=1 width=16)
  Output: table_A.var_1, table_A.var_2
  Sort Key: table_A.var_1
  InitPlan 1 (returns $0)
    ->  Result  (cost=0.00..0.01 rows=1 width=8)
          Output: (now() - '01:00:00'::interval)
  InitPlan 2 (returns $1)
    ->  Result  (cost=0.00..0.02 rows=1 width=8)
          Output: ((now() - '7 days'::interval) - '01:00:00'::interval)
  ->  Foreign Scan on schema_A.table_A  (cost=100.00..158.66 rows=1 width=16)
        Output: table_A.var_1, table_A.var_2
        Remote SQL: SELECT var_1, var_2 FROM model.table_A
                    WHERE ((execution_ts <= $1::timestamp with time zone))
                      AND ((execution_ts >= $2::timestamp with time zone))
                      AND ((model_id::text = 'ABCD'::text))
                      AND ((var_1 = ANY ('1,2,3,4,5'::bigint[])))

【问题讨论】:

【参考方案1】:

任何不是IMMUTABLE的函数都不会被下推。

contrib/postgres_fdw/deparse.c中的函数is_foreign_expr

/*
 * Returns true if given expr is safe to evaluate on the foreign server.
 */
bool
is_foreign_expr(PlannerInfo *root,
                RelOptInfo *baserel,
                Expr *expr)

...
    /*   
     * An expression which includes any mutable functions can't be sent over
     * because its result is not stable.  For example, sending now() remote
     * side could cause confusion from clock offsets.  Future versions might
     * be able to make this choice with more granularity.  (We check this last
     * because it requires a lot of expensive catalog lookups.)
     */
    if (contain_mutable_functions((Node *) expr))
        return false;

    /* OK to evaluate on the remote server */
    return true;

【讨论】:

谢谢!在相关的说明中,我相信您还创建/维护了 oracle_fdw(我喜欢它)。同样的事情也适用吗? 一般来说oracle_fdw比postgres_fdw能下推的东西少,因为Oracle和PostgreSQL不同。然而,我确实推倒了current_timestamp 和朋友们。从概念上讲,这可能是一个糟糕的决定,因为系统上的时钟可能不同,但我认为这是人们所期望的。没有人关心这样的查询中的确切微秒!【参考方案2】:

我认为问题可能是now() 的执行上下文(CURRENT_TIMESTAMP 解析为)。now() 返回的值对于当前事务是固定的 - 这意味着它必须在本地执行。

将其包裹起来,子选择将其强制为常量timestamptz 值 允许远程执行评估。

使用时间戳常量需要使用当前时区规则(根据SET TIME ZONE TO ....)执行时间戳和时间戳之间的转换,并且如果数据库选择将远程timestamptz 转换为本地时间以与timestamp 进行比较文字再次,这必须在本地完成。

一般应避免使用timestamp(改用timestamptz),除非您在哪里

    希望值遵循对夏令时所做的任何更改 规则,以及 确信您永远不会希望在秋季增加的时间表示时间戳。

【讨论】:

以上是关于Postgres_FDW 没有推低 WHERE 标准的主要内容,如果未能解决你的问题,请参考以下文章

postgres_fdw 无法连接到 Amazon RDS 上的服务器

通过postgres_fdw实现跨库访问

通过postgres_fdw实现跨库访问

通过postgres_fdw实现跨库访问

Postgresql 外部表插件postgres_fdw的安装和使用

使用 postgres_fdw 创建外部表时翻译主机名时出错