分区修剪是否在 Postgres 11 中用于更新语句

Is partition pruning working in Postgres 11 for Update statements

我最近开始使用 postgresql 11,我遇到了一个有趣的行为。

我创建了 table "conversions" 分区(logdate 是关键)。

CREATE TABLE conversions(
  .....
  logdate timestamp without time zone,
  ....
) PARTITION BY RANGE (logdate);
CREATE INDEX ON conversions(logdate);
--
CREATE unique INDEX conversions_log_id_idx ON conversions(logdate, id);
CREATE INDEX conversions_log_is_created_idx ON conversions(logdate, is_created);

分区修剪适用于 SELECT 语句(如文档中所述):

SELECT *
      FROM conversions
WHERE logdate BETWEEN to_date('2017-09-01 00:00:00','YYYY-MM-DD HH24:MI:SS') AND to_date('2017-09-08 23:59:59','YYYY-MM-DD HH24:MI:SS');


"Append  (cost=0.42..11134.74 rows=13051 width=1715)"
"  Subplans Removed: 12"
"  ->  Index Scan using conversions_y2017q03_logdate_idx on conversions_y2017q03  (cost=0.42..10962.37 rows=13038 width=1715)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_default_logdate_is_created_idx on conversions_default  (cost=0.14..8.16 rows=1 width=2030)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"

效果很好,之前可以看到

但它不适用于更新语句。

例如:

UPDATE conversions
  SET is_created = 'N'
WHERE logdate BETWEEN to_date('2017-09-01 00:00:00','YYYY-MM-DD HH24:MI:SS') AND to_date('2017-09-08 23:59:59','YYYY-MM-DD HH24:MI:SS')

"Update on conversions  (cost=0.42..11069.48 rows=13051 width=1727)"
"  Update on conversions_y2016q04"
"  Update on conversions_y2017q01"
"  Update on conversions_y2017q02"
"  Update on conversions_y2017q03"
"  Update on conversions_y2017q04"
"  Update on conversions_y2018q01"
"  Update on conversions_y2018q02"
"  Update on conversions_y2018q03"
"  Update on conversions_y2018q04"
"  Update on conversions_y2019q01"
"  Update on conversions_y2019q02"
"  Update on conversions_y2019q03"
"  Update on conversions_y2019q04"
"  Update on conversions_default"
"  ->  Index Scan using conversions_y2016q04_logdate_is_created_idx on conversions_y2016q04  (cost=0.42..8.44 rows=1 width=1661)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2017q01_logdate_is_created_idx on conversions_y2017q01  (cost=0.42..8.45 rows=1 width=1804)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2017q02_logdate_is_created_idx on conversions_y2017q02  (cost=0.42..8.45 rows=1 width=1805)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2017q03_logdate_idx on conversions_y2017q03  (cost=0.42..10962.37 rows=13038 width=1727)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2017q04_logdate_id_idx on conversions_y2017q04  (cost=0.29..8.31 rows=1 width=1699)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2018q01_logdate_is_created_idx on conversions_y2018q01  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2018q02_logdate_is_created_idx on conversions_y2018q02  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2018q03_logdate_is_created_idx on conversions_y2018q03  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2018q04_logdate_is_created_idx on conversions_y2018q04  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2019q01_logdate_is_created_idx on conversions_y2019q01  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2019q02_logdate_is_created_idx on conversions_y2019q02  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2019q03_logdate_is_created_idx on conversions_y2019q03  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_y2019q04_logdate_is_created_idx on conversions_y2019q04  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"
"  ->  Index Scan using conversions_default_logdate_is_created_idx on conversions_default  (cost=0.14..8.16 rows=1 width=2036)"
"        Index Cond: ((logdate >= to_date('2017-09-01 00:00:00'::text, 'YYYY-MM-DD HH24:MI:SS'::text)) AND (logdate <= to_date('2017-09-08 23:59:59'::text, 'YYYY-MM-DD HH24:MI:SS'::text)))"

看起来 Postgresql 正在使用索引来寻找必要的分区。

这是好行为吗?你的想法?

您使用的是准备好的语句吗?如果是这样,则可以使用通用计划,这意味着计划器将无法执行 p运行ing,因为执行此操作所需的值只有在执行通用计划时才可用。 SELECT 从 PostgreSQL 11 开始支持 执行时间 p运行ing,所以即使 planner 不能执行 p运行ing 也没关系它的情况。但是,对于UPDATE,只有planner可以执行p运行ing,所以如果使用generic plan,则有none。我怀疑您可能正在使用准备好的语句,因为查看问题中共享的 SELECT 的解释输出:

"Append  (cost=0.42..11134.74 rows=13051 width=1715)"
"  Subplans Removed: 12"

很明显 运行-time p运行ing 被使用了。在此处阅读有关分区 p运行ing 的更多信息:

https://www.postgresql.org/docs/devel/ddl-partitioning.html#DDL-PARTITION-PRUNING

编辑:我还想提一下,如果计划者得到更改(非通用计划),UPDATE 可以使用 p运行ing,前提是您使用的是 LISTRANGE 分区(不适用于 PostgreSQL 11 中新增的 HASH)。

编辑 2:如评论中所述,这似乎与 OP 在 where 子句中使用 to_date 函数这一事实有关,这使得与分区键进行比较的表达式成为不可变表达式,就 PostgreSQL 而言。这意味着 planner 不能用它执行 p运行ing,尽管 executor 可以,因为它仍然是一个稳定的表达式。正如我上面所说,UPDATE 不支持执行程序 p运行ing。