On Fri, Jul 10, 2009 at 2:48 PM, Tom Lane<t...@sss.pgh.pa.us> wrote: > "Kevin Grittner" <kevin.gritt...@wicourts.gov> writes: >> You do, but it's been pretty rare in my experience, and we're >> considering alternatives which give a lot less flexibility that this. > > I dunno about "considering". We've already wasted vastly more time on > this than it's worth. AFAIR there has never been one single user > request for the ability to partially constrain join order. I think we > should do an enable_join_ordering boolean and quit wasting brainpower on > the issue.
Patch attached. ...Robert
*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 1798,1803 **** archive_command = 'copy "%p" "C:\\server\\archivedir\\%f"' # Windows --- 1798,1823 ---- </listitem> </varlistentry> + <varlistentry id="guc-enable-join-ordering" xreflabel="enable_join_ordering"> + <term><varname>enable_join_ordering</varname> (<type>boolean</type>)</term> + <indexterm> + <primary><varname>enable_join_ordering</> configuration parameter</primary> + </indexterm> + <listitem> + <para> + Allows the planner to reorder joins, which is generally appropriate + for most uses. Setting it to false prevents any reordering of + explicit <literal>JOIN</>s. Thus, the explicit join order + specified in the query will be the actual order in which the + relations are joined. The query planner does not always choose + the optimal join order; advanced users can elect to + temporarily set this variable to false, and then specify the join + order they desire explicitly. + For more information see <xref linkend="explicit-joins">. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-enable-mergejoin" xreflabel="enable_mergejoin"> <term><varname>enable_mergejoin</varname> (<type>boolean</type>)</term> <indexterm> *************** *** 2251,2313 **** SELECT * FROM parent WHERE key = 2400; </listitem> </varlistentry> - <varlistentry id="guc-from-collapse-limit" xreflabel="from_collapse_limit"> - <term><varname>from_collapse_limit</varname> (<type>integer</type>)</term> - <indexterm> - <primary><varname>from_collapse_limit</> configuration parameter</primary> - </indexterm> - <listitem> - <para> - The planner will merge sub-queries into upper queries if the - resulting <literal>FROM</literal> list would have no more than - this many items. Smaller values reduce planning time but might - yield inferior query plans. The default is eight. - For more information see <xref linkend="explicit-joins">. - </para> - - <para> - Setting this value to <xref linkend="guc-geqo-threshold"> or more - may trigger use of the GEQO planner, resulting in nondeterministic - plans. See <xref linkend="runtime-config-query-geqo">. - </para> - </listitem> - </varlistentry> - - <varlistentry id="guc-join-collapse-limit" xreflabel="join_collapse_limit"> - <term><varname>join_collapse_limit</varname> (<type>integer</type>)</term> - <indexterm> - <primary><varname>join_collapse_limit</> configuration parameter</primary> - </indexterm> - <listitem> - <para> - The planner will rewrite explicit <literal>JOIN</> - constructs (except <literal>FULL JOIN</>s) into lists of - <literal>FROM</> items whenever a list of no more than this many items - would result. Smaller values reduce planning time but might - yield inferior query plans. - </para> - - <para> - By default, this variable is set the same as - <varname>from_collapse_limit</varname>, which is appropriate - for most uses. Setting it to 1 prevents any reordering of - explicit <literal>JOIN</>s. Thus, the explicit join order - specified in the query will be the actual order in which the - relations are joined. The query planner does not always choose - the optimal join order; advanced users can elect to - temporarily set this variable to 1, and then specify the join - order they desire explicitly. - For more information see <xref linkend="explicit-joins">. - </para> - - <para> - Setting this value to <xref linkend="guc-geqo-threshold"> or more - may trigger use of the GEQO planner, resulting in nondeterministic - plans. See <xref linkend="runtime-config-query-geqo">. - </para> - </listitem> - </varlistentry> - </variablelist> </sect2> </sect1> --- 2271,2276 ---- *** a/doc/src/sgml/perform.sgml --- b/doc/src/sgml/perform.sgml *************** *** 692,699 **** SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id); <para> To force the planner to follow the join order laid out by explicit <literal>JOIN</>s, ! set the <xref linkend="guc-join-collapse-limit"> run-time parameter to 1. ! (Other possible values are discussed below.) </para> <para> --- 692,699 ---- <para> To force the planner to follow the join order laid out by explicit <literal>JOIN</>s, ! set the <xref linkend="guc-enable-join-ordering"> run-time parameter to ! false. </para> <para> *************** *** 703,710 **** SELECT * FROM a JOIN (b JOIN c ON (b.ref = c.id)) ON (a.id = b.id); <programlisting> SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...; </programlisting> ! With <varname>join_collapse_limit</> = 1, this ! forces the planner to join A to B before joining them to other tables, but doesn't constrain its choices otherwise. In this example, the number of possible join orders is reduced by a factor of 5. </para> --- 703,709 ---- <programlisting> SELECT * FROM a CROSS JOIN b, c, d, e WHERE ...; </programlisting> ! This forces the planner to join A to B before joining them to other tables, but doesn't constrain its choices otherwise. In this example, the number of possible join orders is reduced by a factor of 5. </para> *************** *** 741,765 **** SELECT * FROM x, y, a, b, c WHERE something AND somethingelse; we have increased the planning time; here, we have a five-way join problem replacing two separate three-way join problems. Because of the exponential growth of the number of possibilities, this makes a big ! difference. The planner tries to avoid getting stuck in huge join search ! problems by not collapsing a subquery if more than <varname>from_collapse_limit</> ! <literal>FROM</> items would result in the parent ! query. You can trade off planning time against quality of plan by ! adjusting this run-time parameter up or down. ! </para> ! ! <para> ! <xref linkend="guc-from-collapse-limit"> and <xref ! linkend="guc-join-collapse-limit"> ! are similarly named because they do almost the same thing: one controls ! when the planner will <quote>flatten out</> subqueries, and the ! other controls when it will flatten out explicit joins. Typically ! you would either set <varname>join_collapse_limit</> equal to ! <varname>from_collapse_limit</> (so that explicit joins and subqueries ! act similarly) or set <varname>join_collapse_limit</> to 1 (if you want ! to control join order with explicit joins). But you might set them ! differently if you are trying to fine-tune the trade-off between planning ! time and run time. </para> </sect1> --- 740,746 ---- we have increased the planning time; here, we have a five-way join problem replacing two separate three-way join problems. Because of the exponential growth of the number of possibilities, this makes a big ! difference. </para> </sect1> *** a/src/backend/optimizer/README --- b/src/backend/optimizer/README *************** *** 89,106 **** single join relation. 2) Normally, any explicit JOIN clauses are "flattened" so that we just have a list of relations to join. However, FULL OUTER JOIN clauses are ! never flattened, and other kinds of JOIN might not be either, if the ! flattening process is stopped by join_collapse_limit or from_collapse_limit ! restrictions. Therefore, we end up with a planning problem that contains ! lists of relations to be joined in any order, where any individual item ! might be a sub-list that has to be joined together before we can consider ! joining it to its siblings. We process these sub-problems recursively, ! bottom up. Note that the join list structure constrains the possible join ! orders, but it doesn't constrain the join implementation method at each ! join (nestloop, merge, hash), nor does it say which rel is considered outer ! or inner at each join. We consider all these possibilities in building ! Paths. We generate a Path for each feasible join method, and select the ! cheapest Path. For each planning problem, therefore, we will have a list of relations that are either base rels or joinrels constructed per sub-join-lists. --- 89,105 ---- 2) Normally, any explicit JOIN clauses are "flattened" so that we just have a list of relations to join. However, FULL OUTER JOIN clauses are ! never flattened and other kinds of JOIN might not be either, if ! enable_join_ordering is set to false. Therefore, we end up with a planning ! problem that contains lists of relations to be joined in any order, where ! any individual item might be a sub-list that has to be joined together ! before we can consider joining it to its siblings. We process these ! sub-problems recursively, bottom up. Note that the join list structure ! constrains the possible join orders, but it doesn't constrain the join ! implementation method at each join (nestloop, merge, hash), nor does it ! say which rel is considered outer or inner at each join. We consider ! all these possibilities in building Paths. We generate a Path for each ! feasible join method, and select the cheapest Path. For each planning problem, therefore, we will have a list of relations that are either base rels or joinrels constructed per sub-join-lists. *** a/src/backend/optimizer/plan/initsplan.c --- b/src/backend/optimizer/plan/initsplan.c *************** *** 33,41 **** #include "utils/syscache.h" ! /* These parameters are set by GUC */ ! int from_collapse_limit; ! int join_collapse_limit; static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, --- 33,40 ---- #include "utils/syscache.h" ! /* This parameter is set by GUC */ ! bool enable_join_ordering; static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, *************** *** 214,221 **** add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed) * joinlist must be joined in an order to be determined by make_one_rel() * (note that legal orders may be constrained by SpecialJoinInfo nodes). * A sub-joinlist represents a subproblem to be planned separately. Currently ! * sub-joinlists arise only from FULL OUTER JOIN or when collapsing of ! * subproblems is stopped by join_collapse_limit or from_collapse_limit. * * NOTE: when dealing with inner joins, it is appropriate to let a qual clause * be evaluated at the lowest level where all the variables it mentions are --- 213,220 ---- * joinlist must be joined in an order to be determined by make_one_rel() * (note that legal orders may be constrained by SpecialJoinInfo nodes). * A sub-joinlist represents a subproblem to be planned separately. Currently ! * sub-joinlists arise only from FULL OUTER JOIN or when enable_join_ordering ! * is set to false. * * NOTE: when dealing with inner joins, it is appropriate to let a qual clause * be evaluated at the lowest level where all the variables it mentions are *************** *** 284,302 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, else if (IsA(jtnode, FromExpr)) { FromExpr *f = (FromExpr *) jtnode; - int remaining; ListCell *l; /* * First, recurse to handle child joins. We collapse subproblems into ! * a single joinlist whenever the resulting joinlist wouldn't exceed ! * from_collapse_limit members. Also, always collapse one-element ! * subproblems, since that won't lengthen the joinlist anyway. */ *qualscope = NULL; *inner_join_rels = NULL; joinlist = NIL; - remaining = list_length(f->fromlist); foreach(l, f->fromlist) { Relids sub_qualscope; --- 283,297 ---- else if (IsA(jtnode, FromExpr)) { FromExpr *f = (FromExpr *) jtnode; ListCell *l; /* * First, recurse to handle child joins. We collapse subproblems into ! * a single joinlist. */ *qualscope = NULL; *inner_join_rels = NULL; joinlist = NIL; foreach(l, f->fromlist) { Relids sub_qualscope; *************** *** 309,320 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, inner_join_rels); *qualscope = bms_add_members(*qualscope, sub_qualscope); sub_members = list_length(sub_joinlist); ! remaining--; ! if (sub_members <= 1 || ! list_length(joinlist) + sub_members + remaining <= from_collapse_limit) ! joinlist = list_concat(joinlist, sub_joinlist); ! else ! joinlist = lappend(joinlist, sub_joinlist); } /* --- 304,310 ---- inner_join_rels); *qualscope = bms_add_members(*qualscope, sub_qualscope); sub_members = list_length(sub_joinlist); ! joinlist = list_concat(joinlist, sub_joinlist); } /* *************** *** 469,484 **** deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, /* * Finally, compute the output joinlist. We fold subproblems together ! * except at a FULL JOIN or where join_collapse_limit would be ! * exceeded. */ if (j->jointype == JOIN_FULL) { /* force the join order exactly at this node */ joinlist = list_make1(list_make2(leftjoinlist, rightjoinlist)); } ! else if (list_length(leftjoinlist) + list_length(rightjoinlist) <= ! join_collapse_limit) { /* OK to combine subproblems */ joinlist = list_concat(leftjoinlist, rightjoinlist); --- 459,472 ---- /* * Finally, compute the output joinlist. We fold subproblems together ! * except at a FULL JOIN or when enable_join_ordering is false. */ if (j->jointype == JOIN_FULL) { /* force the join order exactly at this node */ joinlist = list_make1(list_make2(leftjoinlist, rightjoinlist)); } ! else if (enable_join_ordering) { /* OK to combine subproblems */ joinlist = list_concat(leftjoinlist, rightjoinlist); *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 656,661 **** static struct config_bool ConfigureNamesBool[] = --- 656,669 ---- true, NULL, NULL }, { + {"enable_join_ordering", PGC_USERSET, QUERY_TUNING_METHOD, + gettext_noop("Enables the planner to reorder explicit joins."), + NULL + }, + &enable_join_ordering, + true, NULL, NULL + }, + { {"geqo", PGC_USERSET, QUERY_TUNING_GEQO, gettext_noop("Enables genetic query optimization."), gettext_noop("This algorithm attempts to do planning without " *************** *** 1259,1286 **** static struct config_int ConfigureNamesInt[] = 100, 1, 10000, NULL, NULL }, { - {"from_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, - gettext_noop("Sets the FROM-list size beyond which subqueries " - "are not collapsed."), - gettext_noop("The planner will merge subqueries into upper " - "queries if the resulting FROM list would have no more than " - "this many items.") - }, - &from_collapse_limit, - 8, 1, INT_MAX, NULL, NULL - }, - { - {"join_collapse_limit", PGC_USERSET, QUERY_TUNING_OTHER, - gettext_noop("Sets the FROM-list size beyond which JOIN " - "constructs are not flattened."), - gettext_noop("The planner will flatten explicit JOIN " - "constructs into lists of FROM items whenever a " - "list of no more than this many items would result.") - }, - &join_collapse_limit, - 8, 1, INT_MAX, NULL, NULL - }, - { {"geqo_threshold", PGC_USERSET, QUERY_TUNING_GEQO, gettext_noop("Sets the threshold of FROM items beyond which GEQO is used."), NULL --- 1267,1272 ---- *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 190,195 **** --- 190,196 ---- #enable_hashagg = on #enable_hashjoin = on #enable_indexscan = on + #enable_join_reordering = on #enable_mergejoin = on #enable_nestloop = on #enable_seqscan = on *************** *** 219,227 **** #default_statistics_target = 100 # range 1-10000 #constraint_exclusion = partition # on, off, or partition #cursor_tuple_fraction = 0.1 # range 0.0-1.0 - #from_collapse_limit = 8 - #join_collapse_limit = 8 # 1 disables collapsing of explicit - # JOIN clauses #------------------------------------------------------------------------------ --- 220,225 ---- *** a/src/include/optimizer/planmain.h --- b/src/include/optimizer/planmain.h *************** *** 79,86 **** extern bool is_projection_capable_plan(Plan *plan); /* * prototypes for plan/initsplan.c */ ! extern int from_collapse_limit; ! extern int join_collapse_limit; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); --- 79,85 ---- /* * prototypes for plan/initsplan.c */ ! extern bool enable_join_ordering; extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); *** a/src/test/regress/expected/rangefuncs.out --- b/src/test/regress/expected/rangefuncs.out *************** *** 1,16 **** SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%'; ! name | setting ! -------------------+--------- ! enable_bitmapscan | on ! enable_hashagg | on ! enable_hashjoin | on ! enable_indexscan | on ! enable_mergejoin | on ! enable_nestloop | on ! enable_seqscan | on ! enable_sort | on ! enable_tidscan | on ! (9 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11); --- 1,17 ---- SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%'; ! name | setting ! ----------------------+--------- ! enable_bitmapscan | on ! enable_hashagg | on ! enable_hashjoin | on ! enable_indexscan | on ! enable_join_ordering | on ! enable_mergejoin | on ! enable_nestloop | on ! enable_seqscan | on ! enable_sort | on ! enable_tidscan | on ! (10 rows) CREATE TABLE foo2(fooid int, f2 int); INSERT INTO foo2 VALUES(1, 11);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers