mirror of
https://github.com/postgres/postgres.git
synced 2025-07-17 06:41:09 +03:00
Support index-only scans using the visibility map to avoid heap fetches.
When a btree index contains all columns required by the query, and the visibility map shows that all tuples on a target heap page are visible-to-all, we don't need to fetch that heap page. This patch depends on the previous patches that made the visibility map reliable. There's a fair amount left to do here, notably trying to figure out a less chintzy way of estimating the cost of an index-only scan, but the core functionality seems ready to commit. Robert Haas and Ibrar Ahmed, with some previous work by Heikki Linnakangas.
This commit is contained in:
@ -110,6 +110,7 @@ Cost disable_cost = 1.0e10;
|
||||
|
||||
bool enable_seqscan = true;
|
||||
bool enable_indexscan = true;
|
||||
bool enable_indexonlyscan = true;
|
||||
bool enable_bitmapscan = true;
|
||||
bool enable_tidscan = true;
|
||||
bool enable_sort = true;
|
||||
@ -119,6 +120,9 @@ bool enable_material = true;
|
||||
bool enable_mergejoin = true;
|
||||
bool enable_hashjoin = true;
|
||||
|
||||
/* Possibly this should become a GUC too */
|
||||
static double visibility_fraction = 0.9;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PlannerInfo *root;
|
||||
@ -210,6 +214,7 @@ cost_seqscan(Path *path, PlannerInfo *root,
|
||||
* 'index' is the index to be used
|
||||
* 'indexQuals' is the list of applicable qual clauses (implicit AND semantics)
|
||||
* 'indexOrderBys' is the list of ORDER BY operators for amcanorderbyop indexes
|
||||
* 'indexonly' is true if it's an index-only scan
|
||||
* 'outer_rel' is the outer relation when we are considering using the index
|
||||
* scan as the inside of a nestloop join (hence, some of the indexQuals
|
||||
* are join clauses, and we should expect repeated scans of the index);
|
||||
@ -232,6 +237,7 @@ cost_index(IndexPath *path, PlannerInfo *root,
|
||||
IndexOptInfo *index,
|
||||
List *indexQuals,
|
||||
List *indexOrderBys,
|
||||
bool indexonly,
|
||||
RelOptInfo *outer_rel)
|
||||
{
|
||||
RelOptInfo *baserel = index->rel;
|
||||
@ -314,6 +320,12 @@ cost_index(IndexPath *path, PlannerInfo *root,
|
||||
* For partially-correlated indexes, we ought to charge somewhere between
|
||||
* these two estimates. We currently interpolate linearly between the
|
||||
* estimates based on the correlation squared (XXX is that appropriate?).
|
||||
*
|
||||
* If it's an index-only scan, then we will not need to fetch any heap
|
||||
* pages for which the visibility map shows all tuples are visible.
|
||||
* Unfortunately, we have no stats as to how much of the heap is
|
||||
* all-visible, and that's likely to be a rather unstable number anyway.
|
||||
* We use an arbitrary constant visibility_fraction to estimate this.
|
||||
*----------
|
||||
*/
|
||||
if (outer_rel != NULL && outer_rel->rows > 1)
|
||||
@ -333,6 +345,8 @@ cost_index(IndexPath *path, PlannerInfo *root,
|
||||
(double) index->pages,
|
||||
root);
|
||||
|
||||
pages_fetched = ceil(pages_fetched * visibility_fraction);
|
||||
|
||||
max_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans;
|
||||
|
||||
/*
|
||||
@ -352,6 +366,8 @@ cost_index(IndexPath *path, PlannerInfo *root,
|
||||
(double) index->pages,
|
||||
root);
|
||||
|
||||
pages_fetched = ceil(pages_fetched * visibility_fraction);
|
||||
|
||||
min_IO_cost = (pages_fetched * spc_random_page_cost) / num_scans;
|
||||
}
|
||||
else
|
||||
@ -365,11 +381,16 @@ cost_index(IndexPath *path, PlannerInfo *root,
|
||||
(double) index->pages,
|
||||
root);
|
||||
|
||||
pages_fetched = ceil(pages_fetched * visibility_fraction);
|
||||
|
||||
/* max_IO_cost is for the perfectly uncorrelated case (csquared=0) */
|
||||
max_IO_cost = pages_fetched * spc_random_page_cost;
|
||||
|
||||
/* min_IO_cost is for the perfectly correlated case (csquared=1) */
|
||||
pages_fetched = ceil(indexSelectivity * (double) baserel->pages);
|
||||
|
||||
pages_fetched = ceil(pages_fetched * visibility_fraction);
|
||||
|
||||
min_IO_cost = spc_random_page_cost;
|
||||
if (pages_fetched > 1)
|
||||
min_IO_cost += (pages_fetched - 1) * spc_seq_page_cost;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "access/skey.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "catalog/pg_am.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_operator.h"
|
||||
@ -88,6 +89,7 @@ static PathClauseUsage *classify_index_clause_usage(Path *path,
|
||||
List **clauselist);
|
||||
static void find_indexpath_quals(Path *bitmapqual, List **quals, List **preds);
|
||||
static int find_list_position(Node *node, List **nodelist);
|
||||
static bool check_index_only(RelOptInfo *rel, IndexOptInfo *index);
|
||||
static List *group_clauses_by_indexkey(IndexOptInfo *index,
|
||||
List *clauses, List *outer_clauses,
|
||||
Relids outer_relids,
|
||||
@ -314,6 +316,8 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
|
||||
bool useful_predicate;
|
||||
bool found_clause;
|
||||
bool index_is_ordered;
|
||||
bool index_only_scan = false;
|
||||
bool checked_index_only = false;
|
||||
|
||||
/*
|
||||
* Check that index supports the desired scan type(s)
|
||||
@ -438,6 +442,10 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
|
||||
*/
|
||||
if (found_clause || useful_pathkeys != NIL || useful_predicate)
|
||||
{
|
||||
/* First, detect whether index-only scan is possible */
|
||||
index_only_scan = check_index_only(rel, index);
|
||||
checked_index_only = true;
|
||||
|
||||
ipath = create_index_path(root, index,
|
||||
restrictclauses,
|
||||
orderbyclauses,
|
||||
@ -445,6 +453,7 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
|
||||
index_is_ordered ?
|
||||
ForwardScanDirection :
|
||||
NoMovementScanDirection,
|
||||
index_only_scan,
|
||||
outer_rel);
|
||||
result = lappend(result, ipath);
|
||||
}
|
||||
@ -462,11 +471,15 @@ find_usable_indexes(PlannerInfo *root, RelOptInfo *rel,
|
||||
index_pathkeys);
|
||||
if (useful_pathkeys != NIL)
|
||||
{
|
||||
if (!checked_index_only)
|
||||
index_only_scan = check_index_only(rel, index);
|
||||
|
||||
ipath = create_index_path(root, index,
|
||||
restrictclauses,
|
||||
NIL,
|
||||
useful_pathkeys,
|
||||
BackwardScanDirection,
|
||||
index_only_scan,
|
||||
outer_rel);
|
||||
result = lappend(result, ipath);
|
||||
}
|
||||
@ -1040,6 +1053,82 @@ find_list_position(Node *node, List **nodelist)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* check_index_only
|
||||
* Determine whether an index-only scan is possible for this index.
|
||||
*/
|
||||
static bool
|
||||
check_index_only(RelOptInfo *rel, IndexOptInfo *index)
|
||||
{
|
||||
bool result;
|
||||
Bitmapset *attrs_used = NULL;
|
||||
Bitmapset *index_attrs = NULL;
|
||||
ListCell *lc;
|
||||
int i;
|
||||
|
||||
/* Index-only scans must be enabled, and AM must be capable of it */
|
||||
if (!enable_indexonlyscan)
|
||||
return false;
|
||||
if (!index->amcanreturn)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Check that all needed attributes of the relation are available from
|
||||
* the index.
|
||||
*
|
||||
* XXX this is overly conservative for partial indexes, since we will
|
||||
* consider attributes involved in the index predicate as required even
|
||||
* though the predicate won't need to be checked at runtime. (The same
|
||||
* is true for attributes used only in index quals, if we are certain
|
||||
* that the index is not lossy.) However, it would be quite expensive
|
||||
* to determine that accurately at this point, so for now we take the
|
||||
* easy way out.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Add all the attributes needed for joins or final output. Note: we must
|
||||
* look at reltargetlist, not the attr_needed data, because attr_needed
|
||||
* isn't computed for inheritance child rels.
|
||||
*/
|
||||
pull_varattnos((Node *) rel->reltargetlist, rel->relid, &attrs_used);
|
||||
|
||||
/* Add all the attributes used by restriction clauses. */
|
||||
foreach(lc, rel->baserestrictinfo)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
|
||||
|
||||
pull_varattnos((Node *) rinfo->clause, rel->relid, &attrs_used);
|
||||
}
|
||||
|
||||
/* Construct a bitmapset of columns stored in the index. */
|
||||
for (i = 0; i < index->ncolumns; i++)
|
||||
{
|
||||
int attno = index->indexkeys[i];
|
||||
|
||||
/*
|
||||
* For the moment, we just ignore index expressions. It might be nice
|
||||
* to do something with them, later. We also ignore index columns
|
||||
* that are system columns (such as OID), because the virtual-tuple
|
||||
* coding used by IndexStoreHeapTuple() can't deal with them.
|
||||
*/
|
||||
if (attno <= 0)
|
||||
continue;
|
||||
|
||||
index_attrs =
|
||||
bms_add_member(index_attrs,
|
||||
attno - FirstLowInvalidHeapAttributeNumber);
|
||||
}
|
||||
|
||||
/* Do we have all the necessary attributes? */
|
||||
result = bms_is_subset(attrs_used, index_attrs);
|
||||
|
||||
bms_free(attrs_used);
|
||||
bms_free(index_attrs);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
* ---- ROUTINES TO CHECK RESTRICTIONS ----
|
||||
****************************************************************************/
|
||||
|
@ -95,7 +95,7 @@ static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
|
||||
static IndexScan *make_indexscan(List *qptlist, List *qpqual, Index scanrelid,
|
||||
Oid indexid, List *indexqual, List *indexqualorig,
|
||||
List *indexorderby, List *indexorderbyorig,
|
||||
ScanDirection indexscandir);
|
||||
ScanDirection indexscandir, bool indexonly);
|
||||
static BitmapIndexScan *make_bitmap_indexscan(Index scanrelid, Oid indexid,
|
||||
List *indexqual,
|
||||
List *indexqualorig);
|
||||
@ -1183,7 +1183,8 @@ create_indexscan_plan(PlannerInfo *root,
|
||||
stripped_indexquals,
|
||||
fixed_indexorderbys,
|
||||
indexorderbys,
|
||||
best_path->indexscandir);
|
||||
best_path->indexscandir,
|
||||
best_path->indexonly);
|
||||
|
||||
copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
|
||||
/* use the indexscan-specific rows estimate, not the parent rel's */
|
||||
@ -2841,7 +2842,8 @@ make_indexscan(List *qptlist,
|
||||
List *indexqualorig,
|
||||
List *indexorderby,
|
||||
List *indexorderbyorig,
|
||||
ScanDirection indexscandir)
|
||||
ScanDirection indexscandir,
|
||||
bool indexonly)
|
||||
{
|
||||
IndexScan *node = makeNode(IndexScan);
|
||||
Plan *plan = &node->scan.plan;
|
||||
@ -2858,6 +2860,7 @@ make_indexscan(List *qptlist,
|
||||
node->indexorderby = indexorderby;
|
||||
node->indexorderbyorig = indexorderbyorig;
|
||||
node->indexorderdir = indexscandir;
|
||||
node->indexonly = indexonly;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -3297,7 +3297,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
|
||||
/* Estimate the cost of index scan */
|
||||
indexScanPath = create_index_path(root, indexInfo,
|
||||
NIL, NIL, NIL,
|
||||
ForwardScanDirection, NULL);
|
||||
ForwardScanDirection, false, NULL);
|
||||
|
||||
return (seqScanAndSortPath.total_cost < indexScanPath->path.total_cost);
|
||||
}
|
||||
|
@ -25,8 +25,8 @@
|
||||
#include "optimizer/paths.h"
|
||||
#include "optimizer/tlist.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "utils/selfuncs.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/selfuncs.h"
|
||||
|
||||
|
||||
static List *translate_sub_tlist(List *tlist, int relid);
|
||||
@ -418,6 +418,7 @@ create_seqscan_path(PlannerInfo *root, RelOptInfo *rel)
|
||||
* 'indexscandir' is ForwardScanDirection or BackwardScanDirection
|
||||
* for an ordered index, or NoMovementScanDirection for
|
||||
* an unordered index.
|
||||
* 'indexonly' is true if an index-only scan is wanted.
|
||||
* 'outer_rel' is the outer relation if this is a join inner indexscan path.
|
||||
* (pathkeys and indexscandir are ignored if so.) NULL if not.
|
||||
*
|
||||
@ -430,6 +431,7 @@ create_index_path(PlannerInfo *root,
|
||||
List *indexorderbys,
|
||||
List *pathkeys,
|
||||
ScanDirection indexscandir,
|
||||
bool indexonly,
|
||||
RelOptInfo *outer_rel)
|
||||
{
|
||||
IndexPath *pathnode = makeNode(IndexPath);
|
||||
@ -468,6 +470,7 @@ create_index_path(PlannerInfo *root,
|
||||
|
||||
pathnode->isjoininner = (outer_rel != NULL);
|
||||
pathnode->indexscandir = indexscandir;
|
||||
pathnode->indexonly = indexonly;
|
||||
|
||||
if (outer_rel != NULL)
|
||||
{
|
||||
@ -506,7 +509,8 @@ create_index_path(PlannerInfo *root,
|
||||
pathnode->rows = rel->rows;
|
||||
}
|
||||
|
||||
cost_index(pathnode, root, index, indexquals, indexorderbys, outer_rel);
|
||||
cost_index(pathnode, root, index, indexquals, indexorderbys,
|
||||
indexonly, outer_rel);
|
||||
|
||||
return pathnode;
|
||||
}
|
||||
|
@ -210,6 +210,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
|
||||
info->relam = indexRelation->rd_rel->relam;
|
||||
info->amcostestimate = indexRelation->rd_am->amcostestimate;
|
||||
info->amcanorderbyop = indexRelation->rd_am->amcanorderbyop;
|
||||
info->amcanreturn = indexRelation->rd_am->amcanreturn;
|
||||
info->amoptionalkey = indexRelation->rd_am->amoptionalkey;
|
||||
info->amsearchnulls = indexRelation->rd_am->amsearchnulls;
|
||||
info->amhasgettuple = OidIsValid(indexRelation->rd_am->amgettuple);
|
||||
|
@ -34,6 +34,12 @@ typedef struct
|
||||
int sublevels_up;
|
||||
} pull_varnos_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Bitmapset *varattnos;
|
||||
Index varno;
|
||||
} pull_varattnos_context;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int var_location;
|
||||
@ -70,7 +76,7 @@ typedef struct
|
||||
|
||||
static bool pull_varnos_walker(Node *node,
|
||||
pull_varnos_context *context);
|
||||
static bool pull_varattnos_walker(Node *node, Bitmapset **varattnos);
|
||||
static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context);
|
||||
static bool contain_var_clause_walker(Node *node, void *context);
|
||||
static bool contain_vars_of_level_walker(Node *node, int *sublevels_up);
|
||||
static bool locate_var_of_level_walker(Node *node,
|
||||
@ -177,23 +183,31 @@ pull_varnos_walker(Node *node, pull_varnos_context *context)
|
||||
* pull_varattnos
|
||||
* Find all the distinct attribute numbers present in an expression tree,
|
||||
* and add them to the initial contents of *varattnos.
|
||||
* Only Vars that reference RTE 1 of rtable level zero are considered.
|
||||
* Only Vars of the given varno and rtable level zero are considered.
|
||||
*
|
||||
* Attribute numbers are offset by FirstLowInvalidHeapAttributeNumber so that
|
||||
* we can include system attributes (e.g., OID) in the bitmap representation.
|
||||
*
|
||||
* Currently, this does not support subqueries nor expressions containing
|
||||
* references to multiple tables; not needed since it's only applied to
|
||||
* index expressions and predicates.
|
||||
* Currently, this does not support unplanned subqueries; that is not needed
|
||||
* for current uses. It will handle already-planned SubPlan nodes, though,
|
||||
* looking into only the "testexpr" and the "args" list. (The subplan cannot
|
||||
* contain any other references to Vars of the current level.)
|
||||
*/
|
||||
void
|
||||
pull_varattnos(Node *node, Bitmapset **varattnos)
|
||||
pull_varattnos(Node *node, Index varno, Bitmapset **varattnos)
|
||||
{
|
||||
(void) pull_varattnos_walker(node, varattnos);
|
||||
pull_varattnos_context context;
|
||||
|
||||
context.varattnos = *varattnos;
|
||||
context.varno = varno;
|
||||
|
||||
(void) pull_varattnos_walker(node, &context);
|
||||
|
||||
*varattnos = context.varattnos;
|
||||
}
|
||||
|
||||
static bool
|
||||
pull_varattnos_walker(Node *node, Bitmapset **varattnos)
|
||||
pull_varattnos_walker(Node *node, pull_varattnos_context *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
@ -201,17 +215,18 @@ pull_varattnos_walker(Node *node, Bitmapset **varattnos)
|
||||
{
|
||||
Var *var = (Var *) node;
|
||||
|
||||
Assert(var->varno == 1);
|
||||
*varattnos = bms_add_member(*varattnos,
|
||||
var->varattno - FirstLowInvalidHeapAttributeNumber);
|
||||
if (var->varno == context->varno && var->varlevelsup == 0)
|
||||
context->varattnos =
|
||||
bms_add_member(context->varattnos,
|
||||
var->varattno - FirstLowInvalidHeapAttributeNumber);
|
||||
return false;
|
||||
}
|
||||
/* Should not find a subquery or subplan */
|
||||
|
||||
/* Should not find an unplanned subquery */
|
||||
Assert(!IsA(node, Query));
|
||||
Assert(!IsA(node, SubPlan));
|
||||
|
||||
return expression_tree_walker(node, pull_varattnos_walker,
|
||||
(void *) varattnos);
|
||||
(void *) context);
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user