Postgres_FDW 没有下推 WHERE 条件
Postgres_FDW not pushing down WHERE criteria
我正在使用两个 PostgreSQL 9.6 数据库,并尝试使用 postgres_fdw 从另一个数据库查询一个数据库(一个是生产备份数据库,其中包含数据和另一个是用于进行各种分析的数据库)。
我遇到了一些奇怪的行为,虽然查询中的某些类型的 WHERE 子句没有传递到远程数据库,而是保留在本地数据库中并用于过滤从远程接收的结果D B。这导致远程数据库尝试通过网络发送比本地数据库所需更多的信息,并且受影响的查询速度大大降低(15 秒对 15 分钟)。
我主要看到这个与时间戳相关的子句,下面的例子是我第一次遇到这个问题,但我在其他几个变体中看到它,例如用 TIMESTAMP 文字替换 CURRENT_TIMESTAMP (慢)或 TIMESTAMP WITH TIME ZONE 文字(快)。
有没有我遗漏的设置可以帮助解决这个问题?我正在为一个团队设置这个以使用混合级别的 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 [=14=])
-> Result (cost=0.00..0.01 rows=1 width=8)
Output: (now() - '01:00:00'::interval)
InitPlan 2 (returns )
-> 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 <= ::timestamp with time zone))
AND ((execution_ts >= ::timestamp with time zone))
AND ((model_id::text = 'ABCD'::text))
AND ((var_1 = ANY ('{1,2,3,4,5}'::bigint[])))
我认为问题可能出在 now()
的执行上下文(CURRENT_TIMESTAMP
解析为)。
now()
返回的值对于当前事务是固定的 - 这意味着它必须在本地执行。
将它包裹起来,子选择将它强制为一个常量 timestamptz
值
允许远程执行评估。
使用时间戳常量需要使用当前时区规则(根据 SET TIME ZONE TO ....
)在时间戳和 timestamptz 之间进行转换,如果数据库选择将远程 timestamptz
转换为本地时间再次与 timestamp
文字进行比较,这必须在本地完成。
一般应避免使用 timestamp
(请改用 timestamptz
),除非您
- 希望值遵循对夏令时所做的任何更改
规则,并且
- 确定您永远不想在秋季增加的小时内表示时间戳。
任何不是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;
}
我正在使用两个 PostgreSQL 9.6 数据库,并尝试使用 postgres_fdw 从另一个数据库查询一个数据库(一个是生产备份数据库,其中包含数据和另一个是用于进行各种分析的数据库)。
我遇到了一些奇怪的行为,虽然查询中的某些类型的 WHERE 子句没有传递到远程数据库,而是保留在本地数据库中并用于过滤从远程接收的结果D B。这导致远程数据库尝试通过网络发送比本地数据库所需更多的信息,并且受影响的查询速度大大降低(15 秒对 15 分钟)。
我主要看到这个与时间戳相关的子句,下面的例子是我第一次遇到这个问题,但我在其他几个变体中看到它,例如用 TIMESTAMP 文字替换 CURRENT_TIMESTAMP (慢)或 TIMESTAMP WITH TIME ZONE 文字(快)。
有没有我遗漏的设置可以帮助解决这个问题?我正在为一个团队设置这个以使用混合级别的 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 [=14=])
-> Result (cost=0.00..0.01 rows=1 width=8)
Output: (now() - '01:00:00'::interval)
InitPlan 2 (returns )
-> 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 <= ::timestamp with time zone))
AND ((execution_ts >= ::timestamp with time zone))
AND ((model_id::text = 'ABCD'::text))
AND ((var_1 = ANY ('{1,2,3,4,5}'::bigint[])))
我认为问题可能出在 now()
的执行上下文(CURRENT_TIMESTAMP
解析为)。
now()
返回的值对于当前事务是固定的 - 这意味着它必须在本地执行。
将它包裹起来,子选择将它强制为一个常量 timestamptz
值
允许远程执行评估。
使用时间戳常量需要使用当前时区规则(根据 SET TIME ZONE TO ....
)在时间戳和 timestamptz 之间进行转换,如果数据库选择将远程 timestamptz
转换为本地时间再次与 timestamp
文字进行比较,这必须在本地完成。
一般应避免使用 timestamp
(请改用 timestamptz
),除非您
- 希望值遵循对夏令时所做的任何更改 规则,并且
- 确定您永远不想在秋季增加的小时内表示时间戳。
任何不是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;
}