Apply a band-aid fix for the problem that 8.2 and up completely misestimate
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Aug 2007 23:35:30 +0000 (23:35 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 31 Aug 2007 23:35:30 +0000 (23:35 +0000)
the number of rows likely to be produced by a query such as
SELECT * FROM t1 LEFT JOIN t2 USING (key) WHERE t2.key IS NULL;
What this is doing is selecting for t1 rows with no match in t2, and thus
it may produce a significant number of rows even if the t2.key table column
contains no nulls at all.  8.2 thinks the table column's null fraction is
relevant and thus may estimate no rows out, which results in terrible plans
if there are more joins above this one.  A proper fix for this will involve
passing much more information about the context of a clause to the selectivity
estimator functions than we ever have.  There's no time left to write such a
patch for 8.3, and it wouldn't be back-patchable into 8.2 anyway.  Instead,
put in an ad-hoc test to defeat the normal table-stats-based estimation when
an IS NULL test is evaluated at an outer join, and just use a constant
estimate instead --- I went with 0.5 for lack of a better idea.  This won't
catch every case but it will catch the typical ways of writing such queries,
and it seems unlikely to make things worse for other queries.

src/backend/optimizer/path/clausesel.c
src/backend/utils/adt/selfuncs.c
src/include/utils/selfuncs.h

index e1bc17848323a7d9e740e40971744646066eda25..ed416f608cd489b7a078a3996785c1d2da5269db 100644 (file)
@@ -219,7 +219,9 @@ clauselist_selectivity(PlannerInfo *root,
                                s2 = rqlist->hibound + rqlist->lobound - 1.0;
 
                                /* Adjust for double-exclusion of NULLs */
-                               s2 += nulltestsel(root, IS_NULL, rqlist->var, varRelid);
+                               /* HACK: disable nulltestsel's special outer-join logic */
+                               s2 += nulltestsel(root, IS_NULL, rqlist->var,
+                                                                 varRelid, JOIN_INNER);
 
                                /*
                                 * A zero or slightly negative s2 should be converted into a
@@ -702,7 +704,8 @@ clause_selectivity(PlannerInfo *root,
                s1 = nulltestsel(root,
                                                 ((NullTest *) clause)->nulltesttype,
                                                 (Node *) ((NullTest *) clause)->arg,
-                                                varRelid);
+                                                varRelid,
+                                                jointype);
        }
        else if (IsA(clause, BooleanTest))
        {
index c9cf0c813005e5abb8e8b5c0eed199814a8f3207..16d6d513cf65222717dcd6c8debbd428f30a5a86 100644 (file)
@@ -1385,11 +1385,24 @@ booltestsel(PlannerInfo *root, BoolTestType booltesttype, Node *arg,
  */
 Selectivity
 nulltestsel(PlannerInfo *root, NullTestType nulltesttype,
-                       Node *arg, int varRelid)
+                       Node *arg, int varRelid, JoinType jointype)
 {
        VariableStatData vardata;
        double          selec;
 
+       /*
+        * Special hack: an IS NULL test being applied at an outer join should not
+        * be taken at face value, since it's very likely being used to select the
+        * outer-side rows that don't have a match, and thus its selectivity has
+        * nothing whatever to do with the statistics of the original table
+        * column.  We do not have nearly enough context here to determine its
+        * true selectivity, so for the moment punt and guess at 0.5.  Eventually
+        * the planner should be made to provide enough info about the clause's
+        * context to let us do better.
+        */
+       if (IS_OUTER_JOIN(jointype) && nulltesttype == IS_NULL)
+               return (Selectivity) 0.5;
+
        examine_variable(root, arg, varRelid, &vardata);
 
        if (HeapTupleIsValid(vardata.statsTuple))
index d3deae8be80a59bbc1b45309aa63dfb939c076ad..42264af5cc09f97f6ecec5e31f8292ed1c210954 100644 (file)
@@ -149,7 +149,7 @@ extern Datum icnlikejoinsel(PG_FUNCTION_ARGS);
 extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype,
                        Node *arg, int varRelid, JoinType jointype);
 extern Selectivity nulltestsel(PlannerInfo *root, NullTestType nulltesttype,
-                       Node *arg, int varRelid);
+                       Node *arg, int varRelid, JoinType jointype);
 extern Selectivity scalararraysel(PlannerInfo *root,
                           ScalarArrayOpExpr *clause,
                           bool is_join_clause,