Do not append-optimize plans with result relations (DELETE/UPDATE)

Plans that have a result relation set (DELETE or UPDATE) should
not optimize subplans that contain append nodes. This is because
the PostgreSQL must turn such plans on an inheritance table into
a set of similar plans on each subtable (i.e., it needs to apply
UPDATE and DELETE to each subtable and not just the parent).

Optimizing such plans with, e.g., ConstraintAwareAppend makes
the planner believe it only needs to do the DELETE or UPDATE
on the root table, which means rows in subtables won't be affected.
This commit is contained in:
Erik Nordström 2017-11-22 15:47:24 +01:00 committed by Erik Nordström
parent e1a0e819cf
commit c7cc9114b4
5 changed files with 351 additions and 4 deletions

View File

@ -295,7 +295,7 @@ timescaledb_set_rel_pathlist(PlannerInfo *root,
ht != NULL &&
is_append_parent(rel, rte) &&
/* Do not optimize result relations (INSERT, UPDATE, DELETE) */
rti != (Index) root->parse->resultRelation)
0 == root->parse->resultRelation)
{
ListCell *lc;

View File

@ -59,3 +59,165 @@ SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;
1257897600000000000 | dev1 | 4.5 | 5 | | f
(6 rows)
-- Make sure DELETE isn't optimized if it includes Append plans
-- Need to turn of nestloop to make append appear the same on PG96 and PG10
set enable_nestloop = 'off';
CREATE OR REPLACE FUNCTION series_val()
RETURNS integer LANGUAGE PLPGSQL STABLE AS
$BODY$
BEGIN
RETURN 5;
END;
$BODY$;
-- ConstraintAwareAppend applied for SELECT
EXPLAIN (costs off)
SELECT FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------
Hash Join
Hash Cond: ("two_Partitions_1".series_1 = "two_Partitions".series_1)
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
-> Seq Scan on _hyper_1_1_chunk
-> Seq Scan on _hyper_1_2_chunk
-> Seq Scan on _hyper_1_3_chunk
-> Seq Scan on _hyper_1_4_chunk
-> Hash
-> HashAggregate
Group Key: "two_Partitions".series_1
-> Custom Scan (ConstraintAwareAppend)
Hypertable: two_Partitions
Chunks left after exclusion: 4
-> Append
-> Index Only Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Only Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Only Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Only Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
(23 rows)
-- ConstraintAwareAppend NOT applied for DELETE
EXPLAIN (costs off)
DELETE FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------
Delete on "two_Partitions"
Delete on "two_Partitions"
Delete on _hyper_1_1_chunk
Delete on _hyper_1_2_chunk
Delete on _hyper_1_3_chunk
Delete on _hyper_1_4_chunk
-> Hash Join
Hash Cond: ("two_Partitions_1".series_1 = "two_Partitions".series_1)
-> HashAggregate
Group Key: "two_Partitions_1".series_1
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash
-> Seq Scan on "two_Partitions"
-> Hash Join
Hash Cond: (_hyper_1_1_chunk.series_1 = "two_Partitions_1".series_1)
-> Seq Scan on _hyper_1_1_chunk
-> Hash
-> HashAggregate
Group Key: "two_Partitions_1".series_1
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash Join
Hash Cond: (_hyper_1_2_chunk.series_1 = "two_Partitions_1".series_1)
-> Seq Scan on _hyper_1_2_chunk
-> Hash
-> HashAggregate
Group Key: "two_Partitions_1".series_1
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash Join
Hash Cond: (_hyper_1_3_chunk.series_1 = "two_Partitions_1".series_1)
-> Seq Scan on _hyper_1_3_chunk
-> Hash
-> HashAggregate
Group Key: "two_Partitions_1".series_1
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash Join
Hash Cond: (_hyper_1_4_chunk.series_1 = "two_Partitions_1".series_1)
-> Seq Scan on _hyper_1_4_chunk
-> Hash
-> HashAggregate
Group Key: "two_Partitions_1".series_1
-> Append
-> Seq Scan on "two_Partitions" "two_Partitions_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_4_chunk_two_Partitions_timeCustom_series_1_idx" on _hyper_1_4_chunk _hyper_1_4_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
(91 rows)
SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
1257894000000001000 | dev1 | 2.5 | 3 | |
1257894001000000000 | dev1 | 3.5 | 4 | |
1257894002000000000 | dev1 | 5.5 | 6 | | t
1257894002000000000 | dev1 | 5.5 | 7 | | f
1257894002000000000 | dev1 | 2.5 | 3 | |
1257897600000000000 | dev1 | 4.5 | 5 | | f
(6 rows)
DELETE FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
1257894000000001000 | dev1 | 2.5 | 3 | |
1257894001000000000 | dev1 | 3.5 | 4 | |
1257894002000000000 | dev1 | 2.5 | 3 | |
1257897600000000000 | dev1 | 4.5 | 5 | | f
(4 rows)

View File

@ -28,6 +28,118 @@ INSERT INTO "one_Partition"("timeCustom", device_id, series_0, series_1) VALUES
(1257894000000000000, 'dev2', 1.5, 2);
\set QUIET on
\o
-- Make sure UPDATE isn't optimized if it includes Append plans
-- Need to turn of nestloop to make append appear the same on PG96 and PG10
set enable_nestloop = 'off';
CREATE OR REPLACE FUNCTION series_val()
RETURNS integer LANGUAGE PLPGSQL STABLE AS
$BODY$
BEGIN
RETURN 5;
END;
$BODY$;
-- ConstraintAwareAppend applied for SELECT
EXPLAIN (costs off)
SELECT FROM "one_Partition"
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
Hash Join
Hash Cond: ("one_Partition_1".series_1 = "one_Partition".series_1)
-> Append
-> Seq Scan on "one_Partition" "one_Partition_1"
-> Seq Scan on _hyper_1_1_chunk
-> Seq Scan on _hyper_1_2_chunk
-> Seq Scan on _hyper_1_3_chunk
-> Hash
-> HashAggregate
Group Key: "one_Partition".series_1
-> Custom Scan (ConstraintAwareAppend)
Hypertable: one_Partition
Chunks left after exclusion: 3
-> Append
-> Index Only Scan using "_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Only Scan using "_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Only Scan using "_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
(20 rows)
-- ConstraintAwareAppend NOT applied for UPDATE
EXPLAIN (costs off)
UPDATE "one_Partition"
SET series_1 = 8
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------
Update on "one_Partition"
Update on "one_Partition"
Update on _hyper_1_1_chunk
Update on _hyper_1_2_chunk
Update on _hyper_1_3_chunk
-> Hash Join
Hash Cond: ("one_Partition_1".series_1 = "one_Partition".series_1)
-> HashAggregate
Group Key: "one_Partition_1".series_1
-> Append
-> Seq Scan on "one_Partition" "one_Partition_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash
-> Seq Scan on "one_Partition"
-> Hash Join
Hash Cond: (_hyper_1_1_chunk.series_1 = "one_Partition_1".series_1)
-> Seq Scan on _hyper_1_1_chunk
-> Hash
-> HashAggregate
Group Key: "one_Partition_1".series_1
-> Append
-> Seq Scan on "one_Partition" "one_Partition_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash Join
Hash Cond: (_hyper_1_2_chunk.series_1 = "one_Partition_1".series_1)
-> Seq Scan on _hyper_1_2_chunk
-> Hash
-> HashAggregate
Group Key: "one_Partition_1".series_1
-> Append
-> Seq Scan on "one_Partition" "one_Partition_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Hash Join
Hash Cond: (_hyper_1_3_chunk.series_1 = "one_Partition_1".series_1)
-> Seq Scan on _hyper_1_3_chunk
-> Hash
-> HashAggregate
Group Key: "one_Partition_1".series_1
-> Append
-> Seq Scan on "one_Partition" "one_Partition_1"
Filter: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_1_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_1_chunk _hyper_1_1_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_2_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_2_chunk _hyper_1_2_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
-> Index Scan using "_hyper_1_3_chunk_one_Partition_timeCustom_series_1_idx" on _hyper_1_3_chunk _hyper_1_3_chunk_1
Index Cond: (series_1 > (series_val())::double precision)
(65 rows)
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
@ -45,6 +157,26 @@ SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
1257987600000000000 | dev1 | 1.5 | 2 | |
(12 rows)
UPDATE "one_Partition"
SET series_1 = 8
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
timeCustom | device_id | series_0 | series_1 | series_2 | series_bool
---------------------+-----------+----------+----------+----------+-------------
1257894000000000000 | dev1 | 1.5 | 1 | 2 | t
1257894000000000000 | dev1 | 1.5 | 2 | |
1257894000000000000 | dev2 | 1.5 | 2 | |
1257894000000000000 | dev2 | 1.5 | 1 | |
1257894000000001000 | dev1 | 2.5 | 3 | |
1257894001000000000 | dev1 | 3.5 | 4 | |
1257894002000000000 | dev1 | 5.5 | 8 | | t
1257894002000000000 | dev1 | 5.5 | 8 | | f
1257894002000000000 | dev1 | 2.5 | 3 | |
1257897600000000000 | dev1 | 4.5 | 5 | | f
1257987600000000000 | dev1 | 1.5 | 1 | |
1257987600000000000 | dev1 | 1.5 | 2 | |
(12 rows)
UPDATE "one_Partition" SET series_1 = 47;
UPDATE "one_Partition" SET series_bool = true;
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
@ -56,9 +188,9 @@ SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
1257894000000000000 | dev2 | 1.5 | 47 | | t
1257894000000001000 | dev1 | 2.5 | 47 | | t
1257894001000000000 | dev1 | 3.5 | 47 | | t
1257894002000000000 | dev1 | 5.5 | 47 | | t
1257894002000000000 | dev1 | 5.5 | 47 | | t
1257894002000000000 | dev1 | 2.5 | 47 | | t
1257894002000000000 | dev1 | 5.5 | 47 | | t
1257894002000000000 | dev1 | 5.5 | 47 | | t
1257897600000000000 | dev1 | 4.5 | 47 | | t
1257987600000000000 | dev1 | 1.5 | 47 | | t
1257987600000000000 | dev1 | 1.5 | 47 | | t

View File

@ -8,3 +8,30 @@ DELETE FROM "two_Partitions" WHERE series_0 = 1.5;
DELETE FROM "two_Partitions" WHERE series_0 = 100;
SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;
-- Make sure DELETE isn't optimized if it includes Append plans
-- Need to turn of nestloop to make append appear the same on PG96 and PG10
set enable_nestloop = 'off';
CREATE OR REPLACE FUNCTION series_val()
RETURNS integer LANGUAGE PLPGSQL STABLE AS
$BODY$
BEGIN
RETURN 5;
END;
$BODY$;
-- ConstraintAwareAppend applied for SELECT
EXPLAIN (costs off)
SELECT FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
-- ConstraintAwareAppend NOT applied for DELETE
EXPLAIN (costs off)
DELETE FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;
DELETE FROM "two_Partitions"
WHERE series_1 IN (SELECT series_1 FROM "two_Partitions" WHERE series_1 > series_val());
SELECT * FROM "two_Partitions" ORDER BY "timeCustom", device_id;

View File

@ -2,9 +2,35 @@
\ir include/insert_single.sql
\o
-- Make sure UPDATE isn't optimized if it includes Append plans
-- Need to turn of nestloop to make append appear the same on PG96 and PG10
set enable_nestloop = 'off';
CREATE OR REPLACE FUNCTION series_val()
RETURNS integer LANGUAGE PLPGSQL STABLE AS
$BODY$
BEGIN
RETURN 5;
END;
$BODY$;
-- ConstraintAwareAppend applied for SELECT
EXPLAIN (costs off)
SELECT FROM "one_Partition"
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
-- ConstraintAwareAppend NOT applied for UPDATE
EXPLAIN (costs off)
UPDATE "one_Partition"
SET series_1 = 8
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
UPDATE "one_Partition"
SET series_1 = 8
WHERE series_1 IN (SELECT series_1 FROM "one_Partition" WHERE series_1 > series_val());
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;
UPDATE "one_Partition" SET series_1 = 47;
UPDATE "one_Partition" SET series_bool = true;
SELECT * FROM "one_Partition" ORDER BY "timeCustom", device_id;