Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Correctly re-use hash tables in buildSubPlanHash().
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 29 Feb 2020 18:48:09 +0000 (13:48 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 29 Feb 2020 18:48:09 +0000 (13:48 -0500)
Commit 356687bd8 omitted to remove leftover code for destroying
a hashed subplan's hash tables, with the result that the tables
were always rebuilt not reused; this leads to severe memory
leakage if a hashed subplan is re-executed enough times.
Moreover, the code for reusing the hashnulls table had a typo
that would have made it do the wrong thing if it were reached.

Looking at the code coverage report shows severe under-coverage
of the potential callers of ResetTupleHashTable, so add some test
cases that exercise them.

Andreas Karlsson and Tom Lane, per reports from Ranier Vilela
and Justin Pryzby.

Backpatch to v11, as the faulty commit was.

Discussion: https://postgr.es/m/edb62547-c453-c35b-3ed6-a069e4d6b937@proxel.se
Discussion: https://postgr.es/m/CAEudQAo=DCebm1RXtig9OH+QivpS97sMkikt0A9qHmMUs+g6ZA@mail.gmail.com
Discussion: https://postgr.es/m/20200210032547.GA1412@telsasoft.com

src/backend/executor/nodeSubplan.c
src/test/regress/expected/subselect.out
src/test/regress/sql/subselect.sql

index ff95317879731e83004389e03eb71be4e20b5aea..298b7757f57ccf1c4ec3645223e9dab28e0b87ee 100644 (file)
@@ -495,8 +495,6 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
     * need to store subplan output rows that contain NULL.
     */
    MemoryContextReset(node->hashtablecxt);
-   node->hashtable = NULL;
-   node->hashnulls = NULL;
    node->havehashrows = false;
    node->havenullrows = false;
 
@@ -533,7 +531,7 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
        }
 
        if (node->hashnulls)
-           ResetTupleHashTable(node->hashtable);
+           ResetTupleHashTable(node->hashnulls);
        else
            node->hashnulls = BuildTupleHashTableExt(node->parent,
                                                     node->descRight,
@@ -549,6 +547,8 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext)
                                                     node->hashtempcxt,
                                                     false);
    }
+   else
+       node->hashnulls = NULL;
 
    /*
     * We are probably in a short-lived expression-evaluation context. Switch
index 71a677b768070fbca1a525de6c61c79c9416b055..4c6cd5f1466920a27647a8f5b416caee36247620 100644 (file)
@@ -873,6 +873,129 @@ explain (verbose, costs off)
                  One-Time Filter: ("*VALUES*".column1 = "*VALUES*".column1)
 (8 rows)
 
+--
+-- Test rescan of a hashed subplan (the use of random() is to prevent the
+-- sub-select from being pulled up, which would result in not hashing)
+--
+explain (verbose, costs off)
+select sum(ss.tst::int) from
+  onek o cross join lateral (
+  select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+         random() as r
+  from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+                                                                                         QUERY PLAN                                                                                          
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   Output: sum((((hashed SubPlan 1)))::integer)
+   ->  Nested Loop
+         Output: ((hashed SubPlan 1))
+         ->  Seq Scan on public.onek o
+               Output: o.unique1, o.unique2, o.two, o.four, o.ten, o.twenty, o.hundred, o.thousand, o.twothousand, o.fivethous, o.tenthous, o.odd, o.even, o.stringu1, o.stringu2, o.string4
+               Filter: (o.ten = 0)
+         ->  Index Scan using onek_unique1 on public.onek i
+               Output: (hashed SubPlan 1), random()
+               Index Cond: (i.unique1 = o.unique1)
+               SubPlan 1
+                 ->  Seq Scan on public.int4_tbl
+                       Output: int4_tbl.f1
+                       Filter: (int4_tbl.f1 <= $0)
+(14 rows)
+
+select sum(ss.tst::int) from
+  onek o cross join lateral (
+  select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+         random() as r
+  from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+ sum 
+-----
+ 100
+(1 row)
+
+--
+-- Test rescan of a SetOp node
+--
+explain (costs off)
+select count(*) from
+  onek o cross join lateral (
+    select * from onek i1 where i1.unique1 = o.unique1
+    except
+    select * from onek i2 where i2.unique1 = o.unique2
+  ) ss
+where o.ten = 1;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on onek o
+               Filter: (ten = 1)
+         ->  Subquery Scan on ss
+               ->  HashSetOp Except
+                     ->  Append
+                           ->  Subquery Scan on "*SELECT* 1"
+                                 ->  Index Scan using onek_unique1 on onek i1
+                                       Index Cond: (unique1 = o.unique1)
+                           ->  Subquery Scan on "*SELECT* 2"
+                                 ->  Index Scan using onek_unique1 on onek i2
+                                       Index Cond: (unique1 = o.unique2)
+(13 rows)
+
+select count(*) from
+  onek o cross join lateral (
+    select * from onek i1 where i1.unique1 = o.unique1
+    except
+    select * from onek i2 where i2.unique1 = o.unique2
+  ) ss
+where o.ten = 1;
+ count 
+-------
+   100
+(1 row)
+
+--
+-- Test rescan of a RecursiveUnion node
+--
+explain (costs off)
+select sum(o.four), sum(ss.a) from
+  onek o cross join lateral (
+    with recursive x(a) as
+      (select o.four as a
+       union
+       select a + 1 from x
+       where a < 10)
+    select * from x
+  ) ss
+where o.ten = 1;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on onek o
+               Filter: (ten = 1)
+         ->  CTE Scan on x
+               CTE x
+                 ->  Recursive Union
+                       ->  Result
+                       ->  WorkTable Scan on x x_1
+                             Filter: (a < 10)
+(10 rows)
+
+select sum(o.four), sum(ss.a) from
+  onek o cross join lateral (
+    with recursive x(a) as
+      (select o.four as a
+       union
+       select a + 1 from x
+       where a < 10)
+    select * from x
+  ) ss
+where o.ten = 1;
+ sum  | sum  
+------+------
+ 1700 | 5350
+(1 row)
+
 --
 -- Check we don't misoptimize a NOT IN where the subquery returns no rows.
 --
index bd8d2f63d804bbd300838f06920c9ec243d84353..893d8d0f62122e37a955c549304d7aa52fddf8c1 100644 (file)
@@ -493,6 +493,71 @@ explain (verbose, costs off)
   select x, x from
     (select (select random() where y=y) as x from (values(1),(2)) v(y)) ss;
 
+--
+-- Test rescan of a hashed subplan (the use of random() is to prevent the
+-- sub-select from being pulled up, which would result in not hashing)
+--
+explain (verbose, costs off)
+select sum(ss.tst::int) from
+  onek o cross join lateral (
+  select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+         random() as r
+  from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+
+select sum(ss.tst::int) from
+  onek o cross join lateral (
+  select i.ten in (select f1 from int4_tbl where f1 <= o.hundred) as tst,
+         random() as r
+  from onek i where i.unique1 = o.unique1 ) ss
+where o.ten = 0;
+
+--
+-- Test rescan of a SetOp node
+--
+explain (costs off)
+select count(*) from
+  onek o cross join lateral (
+    select * from onek i1 where i1.unique1 = o.unique1
+    except
+    select * from onek i2 where i2.unique1 = o.unique2
+  ) ss
+where o.ten = 1;
+
+select count(*) from
+  onek o cross join lateral (
+    select * from onek i1 where i1.unique1 = o.unique1
+    except
+    select * from onek i2 where i2.unique1 = o.unique2
+  ) ss
+where o.ten = 1;
+
+--
+-- Test rescan of a RecursiveUnion node
+--
+explain (costs off)
+select sum(o.four), sum(ss.a) from
+  onek o cross join lateral (
+    with recursive x(a) as
+      (select o.four as a
+       union
+       select a + 1 from x
+       where a < 10)
+    select * from x
+  ) ss
+where o.ten = 1;
+
+select sum(o.four), sum(ss.a) from
+  onek o cross join lateral (
+    with recursive x(a) as
+      (select o.four as a
+       union
+       select a + 1 from x
+       where a < 10)
+    select * from x
+  ) ss
+where o.ten = 1;
+
 --
 -- Check we don't misoptimize a NOT IN where the subquery returns no rows.
 --