mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-11-21 03:13:05 +08:00
Improve planning of btree index scans using ScalarArrayOpExpr quals.
Since we taught btree to handle ScalarArrayOpExpr quals natively (commit9e8da0f757
), the planner has always included ScalarArrayOpExpr quals in index conditions if possible. However, if the qual is for a non-first index column, this could result in an inferior plan because we can no longer take advantage of index ordering (cf. commit807a40c551
). It can be better to omit the ScalarArrayOpExpr qual from the index condition and let it be done as a filter, so that the output doesn't need to get sorted. Indeed, this is true for the query introduced as a test case by the latter commit. To fix, restructure get_index_paths and build_index_paths so that we consider paths both with and without ScalarArrayOpExpr quals in non-first index columns. Redesign the API of build_index_paths so that it reports what it found, saving useless second or third calls. Report and patch by Andrew Gierth (though rather heavily modified by me). Back-patch to 9.2 where this code was introduced, since the issue can result in significant performance regressions compared to plans produced by 9.1 and earlier.
This commit is contained in:
parent
17009fb9eb
commit
a4523c5aa5
@ -45,14 +45,6 @@
|
|||||||
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
|
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
|
||||||
((idxcollation) == InvalidOid || (idxcollation) == (exprcollation))
|
((idxcollation) == InvalidOid || (idxcollation) == (exprcollation))
|
||||||
|
|
||||||
/* Whether to use ScalarArrayOpExpr to build index qualifications */
|
|
||||||
typedef enum
|
|
||||||
{
|
|
||||||
SAOP_PER_AM, /* Use ScalarArrayOpExpr if amsearcharray */
|
|
||||||
SAOP_ALLOW, /* Use ScalarArrayOpExpr for all indexes */
|
|
||||||
SAOP_REQUIRE /* Require ScalarArrayOpExpr to be used */
|
|
||||||
} SaOpControl;
|
|
||||||
|
|
||||||
/* Whether we are looking for plain indexscan, bitmap scan, or either */
|
/* Whether we are looking for plain indexscan, bitmap scan, or either */
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
@ -118,7 +110,9 @@ static void get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||||
IndexOptInfo *index, IndexClauseSet *clauses,
|
IndexOptInfo *index, IndexClauseSet *clauses,
|
||||||
bool useful_predicate,
|
bool useful_predicate,
|
||||||
SaOpControl saop_control, ScanTypeControl scantype);
|
ScanTypeControl scantype,
|
||||||
|
bool *skip_nonnative_saop,
|
||||||
|
bool *skip_lower_saop);
|
||||||
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
|
static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
|
||||||
List *clauses, List *other_clauses);
|
List *clauses, List *other_clauses);
|
||||||
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
|
static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||||
@ -726,6 +720,8 @@ bms_equal_any(Relids relids, List *relids_list)
|
|||||||
* index AM supports them natively, we should just include them in simple
|
* index AM supports them natively, we should just include them in simple
|
||||||
* index paths. If not, we should exclude them while building simple index
|
* index paths. If not, we should exclude them while building simple index
|
||||||
* paths, and then make a separate attempt to include them in bitmap paths.
|
* paths, and then make a separate attempt to include them in bitmap paths.
|
||||||
|
* Furthermore, we should consider excluding lower-order ScalarArrayOpExpr
|
||||||
|
* quals so as to create ordered paths.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||||
@ -733,16 +729,38 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
List **bitindexpaths)
|
List **bitindexpaths)
|
||||||
{
|
{
|
||||||
List *indexpaths;
|
List *indexpaths;
|
||||||
|
bool skip_nonnative_saop = false;
|
||||||
|
bool skip_lower_saop = false;
|
||||||
ListCell *lc;
|
ListCell *lc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
|
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
|
||||||
* clauses only if the index AM supports them natively.
|
* clauses only if the index AM supports them natively, and skip any such
|
||||||
|
* clauses for index columns after the first (so that we produce ordered
|
||||||
|
* paths if possible).
|
||||||
*/
|
*/
|
||||||
indexpaths = build_index_paths(root, rel,
|
indexpaths = build_index_paths(root, rel,
|
||||||
index, clauses,
|
index, clauses,
|
||||||
index->predOK,
|
index->predOK,
|
||||||
SAOP_PER_AM, ST_ANYSCAN);
|
ST_ANYSCAN,
|
||||||
|
&skip_nonnative_saop,
|
||||||
|
&skip_lower_saop);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we skipped any lower-order ScalarArrayOpExprs on an index with an AM
|
||||||
|
* that supports them, then try again including those clauses. This will
|
||||||
|
* produce paths with more selectivity but no ordering.
|
||||||
|
*/
|
||||||
|
if (skip_lower_saop)
|
||||||
|
{
|
||||||
|
indexpaths = list_concat(indexpaths,
|
||||||
|
build_index_paths(root, rel,
|
||||||
|
index, clauses,
|
||||||
|
index->predOK,
|
||||||
|
ST_ANYSCAN,
|
||||||
|
&skip_nonnative_saop,
|
||||||
|
NULL));
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Submit all the ones that can form plain IndexScan plans to add_path. (A
|
* Submit all the ones that can form plain IndexScan plans to add_path. (A
|
||||||
@ -770,16 +788,18 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If the index doesn't handle ScalarArrayOpExpr clauses natively, check
|
* If there were ScalarArrayOpExpr clauses that the index can't handle
|
||||||
* to see if there are any such clauses, and if so generate bitmap scan
|
* natively, generate bitmap scan paths relying on executor-managed
|
||||||
* paths relying on executor-managed ScalarArrayOpExpr.
|
* ScalarArrayOpExpr.
|
||||||
*/
|
*/
|
||||||
if (!index->amsearcharray)
|
if (skip_nonnative_saop)
|
||||||
{
|
{
|
||||||
indexpaths = build_index_paths(root, rel,
|
indexpaths = build_index_paths(root, rel,
|
||||||
index, clauses,
|
index, clauses,
|
||||||
false,
|
false,
|
||||||
SAOP_REQUIRE, ST_BITMAPSCAN);
|
ST_BITMAPSCAN,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
|
*bitindexpaths = list_concat(*bitindexpaths, indexpaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -802,26 +822,36 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
* Note that this routine should never be called at all if the index has an
|
* Note that this routine should never be called at all if the index has an
|
||||||
* unprovable predicate.
|
* unprovable predicate.
|
||||||
*
|
*
|
||||||
* saop_control indicates whether ScalarArrayOpExpr clauses can be used.
|
|
||||||
* When it's SAOP_REQUIRE, index paths are created only if we found at least
|
|
||||||
* one ScalarArrayOpExpr clause.
|
|
||||||
*
|
|
||||||
* scantype indicates whether we want to create plain indexscans, bitmap
|
* scantype indicates whether we want to create plain indexscans, bitmap
|
||||||
* indexscans, or both. When it's ST_BITMAPSCAN, we will not consider
|
* indexscans, or both. When it's ST_BITMAPSCAN, we will not consider
|
||||||
* index ordering while deciding if a Path is worth generating.
|
* index ordering while deciding if a Path is worth generating.
|
||||||
*
|
*
|
||||||
|
* If skip_nonnative_saop is non-NULL, we ignore ScalarArrayOpExpr clauses
|
||||||
|
* unless the index AM supports them directly, and we set *skip_nonnative_saop
|
||||||
|
* to TRUE if we found any such clauses (caller must initialize the variable
|
||||||
|
* to FALSE). If it's NULL, we do not ignore ScalarArrayOpExpr clauses.
|
||||||
|
*
|
||||||
|
* If skip_lower_saop is non-NULL, we ignore ScalarArrayOpExpr clauses for
|
||||||
|
* non-first index columns, and we set *skip_lower_saop to TRUE if we found
|
||||||
|
* any such clauses (caller must initialize the variable to FALSE). If it's
|
||||||
|
* NULL, we do not ignore non-first ScalarArrayOpExpr clauses, but they will
|
||||||
|
* result in considering the scan's output to be unordered.
|
||||||
|
*
|
||||||
* 'rel' is the index's heap relation
|
* 'rel' is the index's heap relation
|
||||||
* 'index' is the index for which we want to generate paths
|
* 'index' is the index for which we want to generate paths
|
||||||
* 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
|
* 'clauses' is the collection of indexable clauses (RestrictInfo nodes)
|
||||||
* 'useful_predicate' indicates whether the index has a useful predicate
|
* 'useful_predicate' indicates whether the index has a useful predicate
|
||||||
* 'saop_control' indicates whether ScalarArrayOpExpr clauses can be used
|
|
||||||
* 'scantype' indicates whether we need plain or bitmap scan support
|
* 'scantype' indicates whether we need plain or bitmap scan support
|
||||||
|
* 'skip_nonnative_saop' indicates whether to accept SAOP if index AM doesn't
|
||||||
|
* 'skip_lower_saop' indicates whether to accept non-first-column SAOP
|
||||||
*/
|
*/
|
||||||
static List *
|
static List *
|
||||||
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
||||||
IndexOptInfo *index, IndexClauseSet *clauses,
|
IndexOptInfo *index, IndexClauseSet *clauses,
|
||||||
bool useful_predicate,
|
bool useful_predicate,
|
||||||
SaOpControl saop_control, ScanTypeControl scantype)
|
ScanTypeControl scantype,
|
||||||
|
bool *skip_nonnative_saop,
|
||||||
|
bool *skip_lower_saop)
|
||||||
{
|
{
|
||||||
List *result = NIL;
|
List *result = NIL;
|
||||||
IndexPath *ipath;
|
IndexPath *ipath;
|
||||||
@ -833,7 +863,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
List *orderbyclausecols;
|
List *orderbyclausecols;
|
||||||
List *index_pathkeys;
|
List *index_pathkeys;
|
||||||
List *useful_pathkeys;
|
List *useful_pathkeys;
|
||||||
bool found_clause;
|
|
||||||
bool found_lower_saop_clause;
|
bool found_lower_saop_clause;
|
||||||
bool pathkeys_possibly_useful;
|
bool pathkeys_possibly_useful;
|
||||||
bool index_is_ordered;
|
bool index_is_ordered;
|
||||||
@ -868,11 +897,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
* (This order is depended on by btree and possibly other places.) The
|
* (This order is depended on by btree and possibly other places.) The
|
||||||
* lists can be empty, if the index AM allows that.
|
* lists can be empty, if the index AM allows that.
|
||||||
*
|
*
|
||||||
* found_clause is set true only if there's at least one index clause; and
|
* found_lower_saop_clause is set true if we accept a ScalarArrayOpExpr
|
||||||
* if saop_control is SAOP_REQUIRE, it has to be a ScalarArrayOpExpr
|
|
||||||
* clause.
|
|
||||||
*
|
|
||||||
* found_lower_saop_clause is set true if there's a ScalarArrayOpExpr
|
|
||||||
* index clause for a non-first index column. This prevents us from
|
* index clause for a non-first index column. This prevents us from
|
||||||
* assuming that the scan result is ordered. (Actually, the result is
|
* assuming that the scan result is ordered. (Actually, the result is
|
||||||
* still ordered if there are equality constraints for all earlier
|
* still ordered if there are equality constraints for all earlier
|
||||||
@ -885,7 +910,6 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
*/
|
*/
|
||||||
index_clauses = NIL;
|
index_clauses = NIL;
|
||||||
clause_columns = NIL;
|
clause_columns = NIL;
|
||||||
found_clause = false;
|
|
||||||
found_lower_saop_clause = false;
|
found_lower_saop_clause = false;
|
||||||
outer_relids = bms_copy(rel->lateral_relids);
|
outer_relids = bms_copy(rel->lateral_relids);
|
||||||
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
for (indexcol = 0; indexcol < index->ncolumns; indexcol++)
|
||||||
@ -898,17 +922,27 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
|
|
||||||
if (IsA(rinfo->clause, ScalarArrayOpExpr))
|
if (IsA(rinfo->clause, ScalarArrayOpExpr))
|
||||||
{
|
{
|
||||||
/* Ignore if not supported by index */
|
if (!index->amsearcharray)
|
||||||
if (saop_control == SAOP_PER_AM && !index->amsearcharray)
|
{
|
||||||
|
if (skip_nonnative_saop)
|
||||||
|
{
|
||||||
|
/* Ignore because not supported by index */
|
||||||
|
*skip_nonnative_saop = true;
|
||||||
continue;
|
continue;
|
||||||
found_clause = true;
|
}
|
||||||
|
/* Caller had better intend this only for bitmap scan */
|
||||||
|
Assert(scantype == ST_BITMAPSCAN);
|
||||||
|
}
|
||||||
if (indexcol > 0)
|
if (indexcol > 0)
|
||||||
|
{
|
||||||
|
if (skip_lower_saop)
|
||||||
|
{
|
||||||
|
/* Caller doesn't want to lose index ordering */
|
||||||
|
*skip_lower_saop = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
found_lower_saop_clause = true;
|
found_lower_saop_clause = true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (saop_control != SAOP_REQUIRE)
|
|
||||||
found_clause = true;
|
|
||||||
}
|
}
|
||||||
index_clauses = lappend(index_clauses, rinfo);
|
index_clauses = lappend(index_clauses, rinfo);
|
||||||
clause_columns = lappend_int(clause_columns, indexcol);
|
clause_columns = lappend_int(clause_columns, indexcol);
|
||||||
@ -988,7 +1022,7 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
* later merging or final output ordering, OR the index has a useful
|
* later merging or final output ordering, OR the index has a useful
|
||||||
* predicate, OR an index-only scan is possible.
|
* predicate, OR an index-only scan is possible.
|
||||||
*/
|
*/
|
||||||
if (found_clause || useful_pathkeys != NIL || useful_predicate ||
|
if (index_clauses != NIL || useful_pathkeys != NIL || useful_predicate ||
|
||||||
index_only_scan)
|
index_only_scan)
|
||||||
{
|
{
|
||||||
ipath = create_index_path(root, index,
|
ipath = create_index_path(root, index,
|
||||||
@ -1137,7 +1171,9 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
|
|||||||
indexpaths = build_index_paths(root, rel,
|
indexpaths = build_index_paths(root, rel,
|
||||||
index, &clauseset,
|
index, &clauseset,
|
||||||
useful_predicate,
|
useful_predicate,
|
||||||
SAOP_ALLOW, ST_BITMAPSCAN);
|
ST_BITMAPSCAN,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
result = list_concat(result, indexpaths);
|
result = list_concat(result, indexpaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2754,6 +2754,27 @@ ORDER BY unique1;
|
|||||||
42
|
42
|
||||||
(3 rows)
|
(3 rows)
|
||||||
|
|
||||||
|
explain (costs off)
|
||||||
|
SELECT thousand, tenthous FROM tenk1
|
||||||
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
|
ORDER BY thousand;
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------------------------------
|
||||||
|
Index Only Scan using tenk1_thous_tenthous on tenk1
|
||||||
|
Index Cond: (thousand < 2)
|
||||||
|
Filter: (tenthous = ANY ('{1001,3000}'::integer[]))
|
||||||
|
(3 rows)
|
||||||
|
|
||||||
|
SELECT thousand, tenthous FROM tenk1
|
||||||
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
|
ORDER BY thousand;
|
||||||
|
thousand | tenthous
|
||||||
|
----------+----------
|
||||||
|
0 | 3000
|
||||||
|
1 | 1001
|
||||||
|
(2 rows)
|
||||||
|
|
||||||
|
SET enable_indexonlyscan = OFF;
|
||||||
explain (costs off)
|
explain (costs off)
|
||||||
SELECT thousand, tenthous FROM tenk1
|
SELECT thousand, tenthous FROM tenk1
|
||||||
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
@ -2762,7 +2783,7 @@ ORDER BY thousand;
|
|||||||
--------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------
|
||||||
Sort
|
Sort
|
||||||
Sort Key: thousand
|
Sort Key: thousand
|
||||||
-> Index Only Scan using tenk1_thous_tenthous on tenk1
|
-> Index Scan using tenk1_thous_tenthous on tenk1
|
||||||
Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
|
Index Cond: ((thousand < 2) AND (tenthous = ANY ('{1001,3000}'::integer[])))
|
||||||
(4 rows)
|
(4 rows)
|
||||||
|
|
||||||
@ -2775,6 +2796,7 @@ ORDER BY thousand;
|
|||||||
1 | 1001
|
1 | 1001
|
||||||
(2 rows)
|
(2 rows)
|
||||||
|
|
||||||
|
RESET enable_indexscan;
|
||||||
--
|
--
|
||||||
-- Check elimination of constant-NULL subexpressions
|
-- Check elimination of constant-NULL subexpressions
|
||||||
--
|
--
|
||||||
|
@ -932,6 +932,19 @@ SELECT thousand, tenthous FROM tenk1
|
|||||||
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
ORDER BY thousand;
|
ORDER BY thousand;
|
||||||
|
|
||||||
|
SET enable_indexonlyscan = OFF;
|
||||||
|
|
||||||
|
explain (costs off)
|
||||||
|
SELECT thousand, tenthous FROM tenk1
|
||||||
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
|
ORDER BY thousand;
|
||||||
|
|
||||||
|
SELECT thousand, tenthous FROM tenk1
|
||||||
|
WHERE thousand < 2 AND tenthous IN (1001,3000)
|
||||||
|
ORDER BY thousand;
|
||||||
|
|
||||||
|
RESET enable_indexscan;
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Check elimination of constant-NULL subexpressions
|
-- Check elimination of constant-NULL subexpressions
|
||||||
--
|
--
|
||||||
|
Loading…
Reference in New Issue
Block a user