1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-28 23:42:10 +03:00

Skip checking of scan keys required for directional scan in B-tree

Currently, B-tree code matches every scan key to every item on the page.
Imagine the ordered B-tree scan for the query like this.

SELECT * FROM tbl WHERE col > 'a' AND col < 'b' ORDER BY col;

The (col > 'a') scan key will be always matched once we find the location to
start the scan.  The (col < 'b') scan key will match every item on the page
as long as it matches the last item on the page.

This patch implements prechecking of the scan keys required for directional
scan on beginning of page scan.  If precheck is successful we can skip this
scan keys check for the items on the page.  That could lead to significant
acceleration especially if the comparison operator is expensive.

Idea from patch by Konstantin Knizhnik.

Discussion: https://postgr.es/m/079c3f8e-3371-abe2-e93c-fc8a0ae3f571%40garret.ru
Reviewed-by: Peter Geoghegan, Pavel Borisov
This commit is contained in:
Alexander Korotkov
2023-10-06 10:40:51 +03:00
parent 5da0a622e8
commit e0b1ee17dc
4 changed files with 119 additions and 17 deletions

View File

@ -1372,10 +1372,13 @@ _bt_mark_scankey_required(ScanKey skey)
* tupnatts: number of attributes in tupnatts (high key may be truncated)
* dir: direction we are scanning in
* continuescan: output parameter (will be set correctly in all cases)
* requiredMatchedByPrecheck: indicates that scan keys required for
* direction scan are already matched
*/
bool
_bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
ScanDirection dir, bool *continuescan)
ScanDirection dir, bool *continuescan,
bool requiredMatchedByPrecheck)
{
TupleDesc tupdesc;
BTScanOpaque so;
@ -1396,6 +1399,29 @@ _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
Datum datum;
bool isNull;
Datum test;
bool requiredSameDir = false,
requiredOppositeDir = false;
/*
* Check if the key is required for ordered scan in the same or
* opposite direction. Save as flag variables for future usage.
*/
if (((key->sk_flags & SK_BT_REQFWD) && ScanDirectionIsForward(dir)) ||
((key->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsBackward(dir)))
requiredSameDir = true;
else if (((key->sk_flags & SK_BT_REQFWD) && ScanDirectionIsBackward(dir)) ||
((key->sk_flags & SK_BT_REQBKWD) && ScanDirectionIsForward(dir)))
requiredOppositeDir = true;
/*
* Is the key required for scanning for either forward or backward
* direction? If so and called told us that these types of keys are
* known to be matched, skip the check. Except for the row keys,
* where NULLs could be found in the middle of matching values.
*/
if ((requiredSameDir || requiredOppositeDir) &&
!(key->sk_flags & SK_ROW_HEADER) && requiredMatchedByPrecheck)
continue;
if (key->sk_attno > tupnatts)
{
@ -1444,11 +1470,7 @@ _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
* scan direction, then we can conclude no further tuples will
* pass, either.
*/
if ((key->sk_flags & SK_BT_REQFWD) &&
ScanDirectionIsForward(dir))
*continuescan = false;
else if ((key->sk_flags & SK_BT_REQBKWD) &&
ScanDirectionIsBackward(dir))
if (requiredSameDir)
*continuescan = false;
/*
@ -1498,8 +1520,23 @@ _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
return false;
}
test = FunctionCall2Coll(&key->sk_func, key->sk_collation,
datum, key->sk_argument);
/*
* Apply the key checking function. When the key is required for
* opposite direction scan, it must be already satisfied by
* _bt_first() except for the NULLs checking, which have already done
* above.
*/
if (!requiredOppositeDir)
{
test = FunctionCall2Coll(&key->sk_func, key->sk_collation,
datum, key->sk_argument);
}
else
{
test = true;
Assert(test == FunctionCall2Coll(&key->sk_func, key->sk_collation,
datum, key->sk_argument));
}
if (!DatumGetBool(test))
{
@ -1513,11 +1550,7 @@ _bt_checkkeys(IndexScanDesc scan, IndexTuple tuple, int tupnatts,
* initial positioning in _bt_first() when they are available. See
* comments in _bt_first().
*/
if ((key->sk_flags & SK_BT_REQFWD) &&
ScanDirectionIsForward(dir))
*continuescan = false;
else if ((key->sk_flags & SK_BT_REQBKWD) &&
ScanDirectionIsBackward(dir))
if (requiredSameDir)
*continuescan = false;
/*