]> granicus.if.org Git - postgresql/commitdiff
If a LIMIT is applied to a UNION ALL query, plan each UNION arm as
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Jun 2005 02:21:05 +0000 (02:21 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 10 Jun 2005 02:21:05 +0000 (02:21 +0000)
if the limit were directly applied to it.  This does not actually
add a LIMIT plan node to the generated subqueries --- that would be
useless overhead --- but it does cause the planner to prefer fast-
start plans when the limit is small.  After an idea from Phil Endecott.

src/backend/optimizer/plan/planner.c
src/backend/optimizer/prep/prepunion.c
src/include/optimizer/prep.h

index 76ffe04078fabfe7014f2a9fc28d9aa3189c92f3..df8d0556b42cfdc9dbbce31abe716811d8664769 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.188 2005/06/05 22:32:56 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.189 2005/06/10 02:21:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,6 +58,8 @@ static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind);
 static void preprocess_qual_conditions(PlannerInfo *root, Node *jtnode);
 static Plan *inheritance_planner(PlannerInfo *root, List *inheritlist);
 static Plan *grouping_planner(PlannerInfo *root, double tuple_fraction);
+static double adjust_tuple_fraction_for_limit(PlannerInfo *root,
+                                                                                         double tuple_fraction);
 static bool choose_hashed_grouping(PlannerInfo *root, double tuple_fraction,
                                           Path *cheapest_path, Path *sorted_path,
                                           List *sort_pathkeys, List *group_pathkeys,
@@ -648,15 +650,30 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        List       *current_pathkeys;
        List       *sort_pathkeys;
 
+       /* Tweak caller-supplied tuple_fraction if have LIMIT */
+       if (parse->limitCount != NULL)
+               tuple_fraction = adjust_tuple_fraction_for_limit(root, tuple_fraction);
+
        if (parse->setOperations)
        {
                List       *set_sortclauses;
 
+               /*
+                * If there's a top-level ORDER BY, assume we have to fetch all
+                * the tuples.  This might seem too simplistic given all the
+                * hackery below to possibly avoid the sort ... but a nonzero
+                * tuple_fraction is only of use to plan_set_operations() when
+                * the setop is UNION ALL, and the result of UNION ALL is always
+                * unsorted.
+                */
+               if (parse->sortClause)
+                       tuple_fraction = 0.0;
+
                /*
                 * Construct the plan for set operations.  The result will not
                 * need any work except perhaps a top-level sort and/or LIMIT.
                 */
-               result_plan = plan_set_operations(root,
+               result_plan = plan_set_operations(root, tuple_fraction,
                                                                                  &set_sortclauses);
 
                /*
@@ -769,108 +786,6 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
                else
                        root->query_pathkeys = NIL;
 
-               /*
-                * Adjust tuple_fraction if we see that we are going to apply
-                * limiting/grouping/aggregation/etc.  This is not overridable by
-                * the caller, since it reflects plan actions that this routine
-                * will certainly take, not assumptions about context.
-                */
-               if (parse->limitCount != NULL)
-               {
-                       /*
-                        * A LIMIT clause limits the absolute number of tuples
-                        * returned. However, if it's not a constant LIMIT then we
-                        * have to punt; for lack of a better idea, assume 10% of the
-                        * plan's result is wanted.
-                        */
-                       double          limit_fraction = 0.0;
-
-                       if (IsA(parse->limitCount, Const))
-                       {
-                               Const      *limitc = (Const *) parse->limitCount;
-                               int32           count = DatumGetInt32(limitc->constvalue);
-
-                               /*
-                                * A NULL-constant LIMIT represents "LIMIT ALL", which we
-                                * treat the same as no limit (ie, expect to retrieve all
-                                * the tuples).
-                                */
-                               if (!limitc->constisnull && count > 0)
-                               {
-                                       limit_fraction = (double) count;
-                                       /* We must also consider the OFFSET, if present */
-                                       if (parse->limitOffset != NULL)
-                                       {
-                                               if (IsA(parse->limitOffset, Const))
-                                               {
-                                                       int32           offset;
-
-                                                       limitc = (Const *) parse->limitOffset;
-                                                       offset = DatumGetInt32(limitc->constvalue);
-                                                       if (!limitc->constisnull && offset > 0)
-                                                               limit_fraction += (double) offset;
-                                               }
-                                               else
-                                               {
-                                                       /* OFFSET is an expression ... punt ... */
-                                                       limit_fraction = 0.10;
-                                               }
-                                       }
-                               }
-                       }
-                       else
-                       {
-                               /* LIMIT is an expression ... punt ... */
-                               limit_fraction = 0.10;
-                       }
-
-                       if (limit_fraction > 0.0)
-                       {
-                               /*
-                                * If we have absolute limits from both caller and LIMIT,
-                                * use the smaller value; if one is fractional and the
-                                * other absolute, treat the fraction as a fraction of the
-                                * absolute value; else we can multiply the two fractions
-                                * together.
-                                */
-                               if (tuple_fraction >= 1.0)
-                               {
-                                       if (limit_fraction >= 1.0)
-                                       {
-                                               /* both absolute */
-                                               tuple_fraction = Min(tuple_fraction, limit_fraction);
-                                       }
-                                       else
-                                       {
-                                               /* caller absolute, limit fractional */
-                                               tuple_fraction *= limit_fraction;
-                                               if (tuple_fraction < 1.0)
-                                                       tuple_fraction = 1.0;
-                                       }
-                               }
-                               else if (tuple_fraction > 0.0)
-                               {
-                                       if (limit_fraction >= 1.0)
-                                       {
-                                               /* caller fractional, limit absolute */
-                                               tuple_fraction *= limit_fraction;
-                                               if (tuple_fraction < 1.0)
-                                                       tuple_fraction = 1.0;
-                                       }
-                                       else
-                                       {
-                                               /* both fractional */
-                                               tuple_fraction *= limit_fraction;
-                                       }
-                               }
-                               else
-                               {
-                                       /* no info from caller, just use limit */
-                                       tuple_fraction = limit_fraction;
-                               }
-                       }
-               }
-
                /*
                 * With grouping or aggregation, the tuple fraction to pass to
                 * query_planner() may be different from what it is at top level.
@@ -1242,6 +1157,114 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
        return result_plan;
 }
 
+/*
+ * adjust_tuple_fraction_for_limit - adjust tuple fraction for LIMIT
+ *
+ * If the query contains LIMIT, we adjust the caller-supplied tuple_fraction
+ * accordingly.  This is not overridable by the caller, since it reflects plan
+ * actions that grouping_planner() will certainly take, not assumptions about
+ * context.
+ */
+static double
+adjust_tuple_fraction_for_limit(PlannerInfo *root, double tuple_fraction)
+{
+       Query      *parse = root->parse;
+       double          limit_fraction = 0.0;
+
+       /* Should not be called unless LIMIT */
+       Assert(parse->limitCount != NULL);
+
+       /*
+        * A LIMIT clause limits the absolute number of tuples returned. However,
+        * if it's not a constant LIMIT then we have to punt; for lack of a better
+        * idea, assume 10% of the plan's result is wanted.
+        */
+       if (IsA(parse->limitCount, Const))
+       {
+               Const      *limitc = (Const *) parse->limitCount;
+               int32           count = DatumGetInt32(limitc->constvalue);
+
+               /*
+                * A NULL-constant LIMIT represents "LIMIT ALL", which we treat the
+                * same as no limit (ie, expect to retrieve all the tuples).
+                */
+               if (!limitc->constisnull && count > 0)
+               {
+                       limit_fraction = (double) count;
+                       /* We must also consider the OFFSET, if present */
+                       if (parse->limitOffset != NULL)
+                       {
+                               if (IsA(parse->limitOffset, Const))
+                               {
+                                       int32           offset;
+
+                                       limitc = (Const *) parse->limitOffset;
+                                       offset = DatumGetInt32(limitc->constvalue);
+                                       if (!limitc->constisnull && offset > 0)
+                                               limit_fraction += (double) offset;
+                               }
+                               else
+                               {
+                                       /* OFFSET is an expression ... punt ... */
+                                       limit_fraction = 0.10;
+                               }
+                       }
+               }
+       }
+       else
+       {
+               /* LIMIT is an expression ... punt ... */
+               limit_fraction = 0.10;
+       }
+
+       if (limit_fraction > 0.0)
+       {
+               /*
+                * If we have absolute limits from both caller and LIMIT, use the
+                * smaller value; if one is fractional and the other absolute,
+                * treat the fraction as a fraction of the absolute value;
+                * else we can multiply the two fractions together.
+                */
+               if (tuple_fraction >= 1.0)
+               {
+                       if (limit_fraction >= 1.0)
+                       {
+                               /* both absolute */
+                               tuple_fraction = Min(tuple_fraction, limit_fraction);
+                       }
+                       else
+                       {
+                               /* caller absolute, limit fractional */
+                               tuple_fraction *= limit_fraction;
+                               if (tuple_fraction < 1.0)
+                                       tuple_fraction = 1.0;
+                       }
+               }
+               else if (tuple_fraction > 0.0)
+               {
+                       if (limit_fraction >= 1.0)
+                       {
+                               /* caller fractional, limit absolute */
+                               tuple_fraction *= limit_fraction;
+                               if (tuple_fraction < 1.0)
+                                       tuple_fraction = 1.0;
+                       }
+                       else
+                       {
+                               /* both fractional */
+                               tuple_fraction *= limit_fraction;
+                       }
+               }
+               else
+               {
+                       /* no info from caller, just use limit */
+                       tuple_fraction = limit_fraction;
+               }
+       }
+
+       return tuple_fraction;
+}
+
 /*
  * choose_hashed_grouping - should we use hashed grouping?
  */
index 2139ac23f1c1e3b0eb0a063465dbeffbefb68f54..1fedd9791cada261d3ef12a433f75ba29ffed60b 100644 (file)
@@ -14,7 +14,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.123 2005/06/09 04:18:59 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/optimizer/prep/prepunion.c,v 1.124 2005/06/10 02:21:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -50,16 +50,19 @@ typedef struct
 } adjust_inherited_attrs_context;
 
 static Plan *recurse_set_operations(Node *setOp, PlannerInfo *root,
-                                          List *colTypes, bool junkOK,
-                                          int flag, List *refnames_tlist,
-                                          List **sortClauses);
+                                                                       double tuple_fraction,
+                                                                       List *colTypes, bool junkOK,
+                                                                       int flag, List *refnames_tlist,
+                                                                       List **sortClauses);
 static Plan *generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
-                                       List *refnames_tlist, List **sortClauses);
+                                                                double tuple_fraction,
+                                                                List *refnames_tlist, List **sortClauses);
 static Plan *generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
                                           List *refnames_tlist, List **sortClauses);
 static List *recurse_union_children(Node *setOp, PlannerInfo *root,
-                                          SetOperationStmt *top_union,
-                                          List *refnames_tlist);
+                                                                       double tuple_fraction,
+                                                                       SetOperationStmt *top_union,
+                                                                       List *refnames_tlist);
 static List *generate_setop_tlist(List *colTypes, int flag,
                                         Index varno,
                                         bool hack_constants,
@@ -85,11 +88,17 @@ static List *adjust_inherited_tlist(List *tlist,
  * Any top-level ORDER BY requested in root->parse->sortClause will be added
  * when we return to grouping_planner.
  *
+ * tuple_fraction is the fraction of tuples we expect will be retrieved.
+ * tuple_fraction is interpreted as for grouping_planner(); in particular,
+ * zero means "all the tuples will be fetched".  Any LIMIT present at the
+ * top level has already been factored into tuple_fraction.
+ *
  * *sortClauses is an output argument: it is set to a list of SortClauses
  * representing the result ordering of the topmost set operation.
  */
 Plan *
-plan_set_operations(PlannerInfo *root, List **sortClauses)
+plan_set_operations(PlannerInfo *root, double tuple_fraction,
+                                       List **sortClauses)
 {
        Query      *parse = root->parse;
        SetOperationStmt *topop = (SetOperationStmt *) parse->setOperations;
@@ -124,7 +133,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
         * output from the top-level node, plus possibly resjunk working
         * columns (we can rely on upper-level nodes to deal with that).
         */
-       return recurse_set_operations((Node *) topop, root,
+       return recurse_set_operations((Node *) topop, root, tuple_fraction,
                                                                  topop->colTypes, true, -1,
                                                                  leftmostQuery->targetList,
                                                                  sortClauses);
@@ -134,6 +143,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
  * recurse_set_operations
  *       Recursively handle one step in a tree of set operations
  *
+ * tuple_fraction: fraction of tuples we expect to retrieve from node
  * colTypes: list of type OIDs of expected output columns
  * junkOK: if true, child resjunk columns may be left in the result
  * flag: if >= 0, add a resjunk output column indicating value of flag
@@ -142,6 +152,7 @@ plan_set_operations(PlannerInfo *root, List **sortClauses)
  */
 static Plan *
 recurse_set_operations(Node *setOp, PlannerInfo *root,
+                                          double tuple_fraction,
                                           List *colTypes, bool junkOK,
                                           int flag, List *refnames_tlist,
                                           List **sortClauses)
@@ -159,7 +170,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
                /*
                 * Generate plan for primitive subquery
                 */
-               subplan = subquery_planner(subquery, 0.0 /* default case */, NULL);
+               subplan = subquery_planner(subquery, tuple_fraction, NULL);
 
                /*
                 * Add a SubqueryScan with the caller-requested targetlist
@@ -189,10 +200,12 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 
                /* UNIONs are much different from INTERSECT/EXCEPT */
                if (op->op == SETOP_UNION)
-                       plan = generate_union_plan(op, root, refnames_tlist,
+                       plan = generate_union_plan(op, root, tuple_fraction,
+                                                                          refnames_tlist,
                                                                           sortClauses);
                else
-                       plan = generate_nonunion_plan(op, root, refnames_tlist,
+                       plan = generate_nonunion_plan(op, root,
+                                                                                 refnames_tlist,
                                                                                  sortClauses);
 
                /*
@@ -235,6 +248,7 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
  */
 static Plan *
 generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
+                                       double tuple_fraction,
                                        List *refnames_tlist,
                                        List **sortClauses)
 {
@@ -242,6 +256,20 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
        List       *tlist;
        Plan       *plan;
 
+       /*
+        * If plain UNION, tell children to fetch all tuples.
+        *
+        * Note: in UNION ALL, we pass the top-level tuple_fraction unmodified
+        * to each arm of the UNION ALL.  One could make a case for reducing
+        * the tuple fraction for later arms (discounting by the expected size
+        * of the earlier arms' results) but it seems not worth the trouble.
+        * The normal case where tuple_fraction isn't already zero is a LIMIT
+        * at top level, and passing it down as-is is usually enough to get the
+        * desired result of preferring fast-start plans.
+        */
+       if (!op->all)
+               tuple_fraction = 0.0;
+
        /*
         * If any of my children are identical UNION nodes (same op, all-flag,
         * and colTypes) then they can be merged into this node so that we
@@ -249,8 +277,10 @@ generate_union_plan(SetOperationStmt *op, PlannerInfo *root,
         * such nodes and compute their children's plans.
         */
        planlist = list_concat(recurse_union_children(op->larg, root,
+                                                                                                 tuple_fraction,
                                                                                                  op, refnames_tlist),
                                                   recurse_union_children(op->rarg, root,
+                                                                                                 tuple_fraction,
                                                                                                  op, refnames_tlist));
 
        /*
@@ -309,10 +339,12 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
 
        /* Recurse on children, ensuring their outputs are marked */
        lplan = recurse_set_operations(op->larg, root,
+                                                                  0.0 /* all tuples needed */,
                                                                   op->colTypes, false, 0,
                                                                   refnames_tlist,
                                                                   &child_sortclauses);
        rplan = recurse_set_operations(op->rarg, root,
+                                                                  0.0 /* all tuples needed */,
                                                                   op->colTypes, false, 1,
                                                                   refnames_tlist,
                                                                   &child_sortclauses);
@@ -377,6 +409,7 @@ generate_nonunion_plan(SetOperationStmt *op, PlannerInfo *root,
  */
 static List *
 recurse_union_children(Node *setOp, PlannerInfo *root,
+                                          double tuple_fraction,
                                           SetOperationStmt *top_union,
                                           List *refnames_tlist)
 {
@@ -392,9 +425,11 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
                {
                        /* Same UNION, so fold children into parent's subplan list */
                        return list_concat(recurse_union_children(op->larg, root,
+                                                                                                         tuple_fraction,
                                                                                                          top_union,
                                                                                                          refnames_tlist),
                                                           recurse_union_children(op->rarg, root,
+                                                                                                         tuple_fraction,
                                                                                                          top_union,
                                                                                                          refnames_tlist));
                }
@@ -411,6 +446,7 @@ recurse_union_children(Node *setOp, PlannerInfo *root,
         * resjunk anyway.
         */
        return list_make1(recurse_set_operations(setOp, root,
+                                                                                        tuple_fraction,
                                                                                         top_union->colTypes, false,
                                                                                         -1, refnames_tlist,
                                                                                         &child_sortclauses));
index e4b9cb2f45c20f98fbbeb13eb8fa034aaca80500..35907e6e72aa25f782a508a73558872d742facf3 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.50 2005/06/05 22:32:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/optimizer/prep.h,v 1.51 2005/06/10 02:21:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,7 +45,8 @@ extern List *preprocess_targetlist(PlannerInfo *root, List *tlist);
 /*
  * prototypes for prepunion.c
  */
-extern Plan *plan_set_operations(PlannerInfo *root, List **sortClauses);
+extern Plan *plan_set_operations(PlannerInfo *root, double tuple_fraction,
+                                                                List **sortClauses);
 
 extern List *find_all_inheritors(Oid parentrel);