Postgres_FDW不会降低 WHERE 标准



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

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

我主要在时间戳相关子句中看到了这一点,以下示例是我第一次遇到这个问题的方式,但我在其他几种变体中看到过它,例如将CURRENT_TIMESTAMP替换为时间戳文字(慢(或时间戳与时区文字(快速(。

我缺少的某个地方是否有一个设置可以帮助解决这个问题? 我正在为一个团队设置这个,以便在混合级别的SQL背景中使用,大多数人都没有审查解释计划的经验。 我想出了一些解决方法(例如将相对时间子句放在 sub-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[])))

任何未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;
}

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

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

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

一般来说,应避免使用timestamp(改用timestamptz(,除非您

  1. 希望这些值遵循对夏令时所做的任何更改 规则,以及
  2. 可以肯定的是,您永远不想在秋季添加的小时内表示时间戳。

最新更新