1
0
mirror of https://github.com/MariaDB/server.git synced 2025-04-18 21:44:20 +03:00

MDEV-34413 Index Condition Pushdown for reverse ordered scans

Allows index condition pushdown for reverse ordered scans, a previously
disabled feature due to poor performance.  This patch adds a new
API to the handler class called set_end_range which allows callers to
tell the handler what the end of the index range will be when scanning.
Combined with a pushed index condition, the handler can scan the index
efficiently and not read beyond the end of the given range.  When
checking if the pushed index condition matches, the handler will also
check if scanning has reached the end of the provided range and stop if
so.

If we instead only enabled ICP for reverse ordered scans without
also calling this new API, then the handler would perform unnecessary
index condition checks.  In fact this would continue until the end of
the index is reached.

These changes are agnostic of storage engine.  That is, any storage
engine that supports index condition pushdown will inhereit this new
behavior as it is implemented in the SQL and storage engine
API layers.

The partitioned tables storage meta-engine (ha_partition) adds an
override of set_end_range which recursively calls set_end_range on its
child storage engine (handler) implementations.

This commit updates the test made in an earlier commit to show that
ICP matches happen for the reverse ordered case.

This patch is based on changes written by Olav Sandstaa in
MySQL commit da1d92fd46071cd86de61058b6ea39fd9affcd87
This commit is contained in:
Dave Gosselin 2025-02-10 13:56:25 -05:00 committed by Dave Gosselin
parent 261d5520a2
commit 7e4233746e
20 changed files with 218 additions and 72 deletions

View File

@ -19,7 +19,7 @@ a b c
10 10 10
explain select * from t10 force index(a) where a between 10 and 20 and b+1 <3333 order by a desc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t10 range a a 5 NULL 11 Using where
1 SIMPLE t10 range a a 5 NULL 11 Using index condition
flush status;
select * from t10 force index(a) where a between 10 and 20 and b+1 <3333 order by a desc, b desc;
a b c
@ -36,8 +36,8 @@ a b c
10 10 10
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 11
HANDLER_ICP_MATCH 11
select * from t10 force index(a) where a between 10 and 20 and b+1 <3333 order by a asc, b asc;
a b c
10 10 10
@ -77,15 +77,15 @@ a b c
10 10 10
explain select * from t10 force index(a) where a=10 and b+1 <3333 order by a desc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t10 ref a a 5 const 1 Using where
1 SIMPLE t10 ref a a 5 const 1 Using index condition; Using where
flush status;
select * from t10 force index(a) where a=10 and b+1 <3333 order by a desc, b desc;
a b c
10 10 10
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 1
HANDLER_ICP_MATCH 1
select * from t10 force index(a) where a=10 and b+1 <3333 order by a asc, b asc;
a b c
10 10 10
@ -105,15 +105,15 @@ a b c
10 10 10
explain select * from t10 force index(a) where a=10 and b+1 <3333 order by a asc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t10 ref a a 5 const 1 Using where
1 SIMPLE t10 ref a a 5 const 1 Using index condition; Using where
flush status;
select * from t10 force index(a) where a=10 and b+1 <3333 order by a asc, b desc;
a b c
10 10 10
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 1
HANDLER_ICP_MATCH 1
select * from t10 force index(a) where a=10 and b+1 <3333 order by a desc, b asc;
a b c
10 10 10
@ -135,15 +135,15 @@ a b c
3 30 300
explain select * from t1 where a >= 3 and a <= 3 order by a desc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 5 NULL 1 Using where
1 SIMPLE t1 range a a 5 NULL 1 Using index condition
flush status;
select * from t1 where a >= 3 and a <= 3 order by a desc, b desc;
a b c
3 30 300
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 1
HANDLER_ICP_MATCH 1
select * from t1 where a >= 3 and a <= 3 order by a asc, b asc;
a b c
3 30 300
@ -166,15 +166,15 @@ a b c
2 20 200
explain select * from t1 where a >= 2 and a <= 2 order by a desc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 5 NULL 1 Using where
1 SIMPLE t1 range a a 5 NULL 1 Using index condition
flush status;
select * from t1 where a >= 2 and a <= 2 order by a desc, b desc;
a b c
2 20 200
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 1
HANDLER_ICP_MATCH 1
select * from t1 where a >= 2 and a <= 2 order by a asc, b asc;
a b c
2 20 200
@ -214,7 +214,7 @@ a b c
3 3 3
explain select * from t1 where a >= 3 and a <= 7 order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 4 NULL 5 Using where
1 SIMPLE t1 range a a 4 NULL 5 Using index condition
flush status;
select * from t1 where a >= 3 and a <= 7 order by a desc;
a b c
@ -225,8 +225,8 @@ a b c
3 3 3
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 6
HANDLER_ICP_MATCH 6
select * from t1 where a >= 3 and a <= 7 order by a desc, b desc;
a b c
7 7 7
@ -236,7 +236,7 @@ a b c
3 3 3
explain select * from t1 where a >= 3 and a <= 7 order by a desc, b desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 4 NULL 5 Using where
1 SIMPLE t1 range a a 4 NULL 5 Using index condition
flush status;
select * from t1 where a >= 3 and a <= 7 order by a desc, b desc;
a b c
@ -247,8 +247,8 @@ a b c
3 3 3
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_MATCH 0
HANDLER_ICP_ATTEMPTS 6
HANDLER_ICP_MATCH 6
drop table t1;
create table t1 (
pk int primary key,
@ -273,6 +273,6 @@ select * from t1 where kp1 between 950 and 960 and kp2+1 >33333 order by kp1 des
pk kp1 kp2 col1
SELECT * FROM information_schema.SESSION_STATUS WHERE VARIABLE_NAME LIKE '%icp%';
VARIABLE_NAME VARIABLE_VALUE
HANDLER_ICP_ATTEMPTS 0
HANDLER_ICP_ATTEMPTS 11
HANDLER_ICP_MATCH 0
drop table t1;

View File

@ -1,4 +1,3 @@
--source include/have_innodb.inc
--source include/have_partition.inc
--source include/have_sequence.inc

View File

@ -1779,7 +1779,7 @@ FLUSH STATUS;
FLUSH TABLES;
EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where
1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using index condition
Warnings:
Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where `test`.`t2`.`i` > 10 and `test`.`t2`.`i` <= 18 order by `test`.`t2`.`i` desc limit 5
# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution
@ -2291,7 +2291,7 @@ FLUSH STATUS;
FLUSH TABLES;
EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where
1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using index condition
Warnings:
Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where `test`.`t2`.`i` > 10 and `test`.`t2`.`i` <= 18 order by `test`.`t2`.`i` desc limit 5
# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution

View File

@ -165,7 +165,7 @@ WHERE ts BETWEEN '0000-00-00' AND '2010-00-01 00:00:00'
ORDER BY ts DESC
LIMIT 2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 4 Using where
1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 4 Using index condition
DROP TABLE t1;
#
@ -1016,12 +1016,12 @@ set optimizer_switch='mrr=off';
# Must not use ICP:
explain select * from t1 where a between 5 and 8 order by a desc, col desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 5 NULL 40 Using where
1 SIMPLE t1 range a a 5 NULL 40 Using index condition
set optimizer_switch= @tmp_10000051;
# Must not use ICP:
explain select * from t1 where a=3 and col > 500 order by a desc, col desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a a 10 NULL 10 Using where
1 SIMPLE t1 range a a 10 NULL 10 Using index condition
drop table t0, t1;
#
# MDEV-13628: ORed condition in pushed index condition is not removed from the WHERE

View File

@ -42,11 +42,11 @@ pk1 count(*)
# The following should use range(ux_pk1_fd5), two key parts (key_len=5+8=13)
EXPLAIN SELECT * FROM t2 USE INDEX(ux_pk1_fd5) WHERE pk1=9 AND fd5 < 500 ORDER BY fd5 DESC LIMIT 10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 range ux_pk1_fd5 ux_pk1_fd5 13 NULL 138 Using where
1 SIMPLE t2 range ux_pk1_fd5 ux_pk1_fd5 13 NULL 138 Using index condition
# This also must use range, not ref. key_len must be 13
EXPLAIN SELECT * FROM t2 WHERE pk1=9 AND fd5 < 500 ORDER BY fd5 DESC LIMIT 10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t2 range PRIMARY,ux_pk1_fd5 ux_pk1_fd5 13 NULL 138 Using where
1 SIMPLE t2 range PRIMARY,ux_pk1_fd5 ux_pk1_fd5 13 NULL 138 Using index condition
drop table t2;
#
# MDEV-6814: Server crashes in calculate_key_len on query with ORDER BY

View File

@ -3826,15 +3826,18 @@ ANALYZE
"loops": 1,
"r_loops": 1,
"rows": 250,
"r_rows": 250,
"r_index_rows": 250,
"r_rows": 2,
"cost": "REPLACED",
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": REPLACED,
"filtered": 4.799086094,
"r_total_filtered": 0.8,
"attached_condition": "t1.a <=> 30100 and t1.b in (30100,30101,30102) and 30100 + t1.pk > 38539",
"r_filtered": 0.8
"index_condition": "30100 + t1.pk > 38539",
"r_icp_filtered": 0.8,
"attached_condition": "t1.a <=> 30100 and t1.b in (30100,30101,30102)",
"r_filtered": 100
}
}
]
@ -3870,15 +3873,18 @@ ANALYZE
"loops": 1,
"r_loops": 1,
"rows": 250,
"r_rows": 250,
"r_index_rows": 250,
"r_rows": 2,
"cost": "REPLACED",
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": REPLACED,
"filtered": 4.799086094,
"r_total_filtered": 0.8,
"attached_condition": "t1.a <=> 30100 and t1.b in (30100,30101,30102) and 30100 + t1.pk > 38539",
"r_filtered": 0.8
"index_condition": "30100 + t1.pk > 38539",
"r_icp_filtered": 0.8,
"attached_condition": "t1.a <=> 30100 and t1.b in (30100,30101,30102)",
"r_filtered": 100
}
}
]

View File

@ -3197,7 +3197,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -3200,7 +3200,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -3202,7 +3202,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -3198,7 +3198,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -3203,7 +3203,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -3198,7 +3198,7 @@ ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system PRIMARY NULL NULL NULL 1
1 PRIMARY r const PRIMARY PRIMARY 4 const 1
2 SUBQUERY t2 range cb cb 40 NULL 3 Using where
2 SUBQUERY t2 range cb cb 40 NULL 3 Using index condition
SELECT sql_no_cache t1.a, r.a, r.b FROM t1 LEFT JOIN t2 r
ON r.a = (SELECT t2.a FROM t2 WHERE t2.c = t1.a AND t2.b <= '359899'
ORDER BY t2.c DESC, t2.b DESC LIMIT 1) WHERE t1.a = 10;

View File

@ -167,7 +167,7 @@ WHERE ts BETWEEN '0000-00-00' AND '2010-00-01 00:00:00'
ORDER BY ts DESC
LIMIT 2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 4 Using where
1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 4 Using index condition
DROP TABLE t1;
#

View File

@ -6367,7 +6367,7 @@ int ha_partition::read_range_first(const key_range *start_key,
m_ordered= sorted;
eq_range= eq_range_arg;
set_end_range(end_key);
set_end_range(end_key, RANGE_SCAN_ASC);
range_key_part= m_curr_key_info[0]->key_part;
if (start_key)
@ -6403,6 +6403,17 @@ int ha_partition::read_range_next()
DBUG_RETURN(handle_unordered_next(table->record[0], eq_range));
}
void ha_partition::set_end_range(const key_range *end_key,
enum_range_scan_direction direction)
{
for (uint i= bitmap_get_first_set(&m_part_info->read_partitions);
i < m_tot_parts;
i= bitmap_get_next_set(&m_part_info->read_partitions, i))
m_file[i]->set_end_range(end_key, direction);
}
/**
Create a copy of all keys used by multi_range_read()

View File

@ -859,7 +859,8 @@ public:
const key_range * end_key,
bool eq_range, bool sorted) override;
int read_range_next() override;
void set_end_range(const key_range *end_key,
enum_range_scan_direction direction) override;
HANDLER_BUFFER *m_mrr_buffer;
uint *m_mrr_buffer_size;

View File

@ -7132,7 +7132,7 @@ int handler::read_range_first(const key_range *start_key,
DBUG_ENTER("handler::read_range_first");
eq_range= eq_range_arg;
set_end_range(end_key);
set_end_range(end_key, RANGE_SCAN_ASC);
range_key_part= table->key_info[active_index].key_part;
if (!start_key) // Read first record
@ -7208,9 +7208,17 @@ int handler::read_range_next()
}
void handler::set_end_range(const key_range *end_key)
/*
@brief
Inform the Storage Engine about the end of range to be scanned.
See opt_index_cond_pushdown.cc, "End-of-range checks".
*/
void handler::set_end_range(const key_range *end_key,
enum_range_scan_direction direction)
{
end_range= 0;
range_scan_direction= direction;
if (end_key)
{
end_range= &save_end_range;
@ -7218,6 +7226,7 @@ void handler::set_end_range(const key_range *end_key)
key_compare_result_on_equal=
((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
(end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
range_key_part= table->key_info[active_index].key_part;
}
}
@ -7250,8 +7259,11 @@ int handler::compare_key(key_range *range)
/*
Same as compare_key() but doesn't check have in_range_check_pushed_down.
This is used by index condition pushdown implementation.
Same as compare_key() but
- doesn't check in_range_check_pushed_down,
- supports reverse index scans.
This is used by Index Condition Pushdown implementation.
*/
int handler::compare_key2(key_range *range) const
@ -7262,6 +7274,8 @@ int handler::compare_key2(key_range *range) const
cmp= key_cmp(range_key_part, range->key, range->length);
if (!cmp)
cmp= key_compare_result_on_equal;
if (range_scan_direction == RANGE_SCAN_DESC)
cmp= -cmp;
return cmp;
}
@ -7286,6 +7300,10 @@ extern "C" check_result_t handler_index_cond_check(void* h_arg)
if (killed > abort_at)
return CHECK_ABORTED_BY_USER;
}
/*
Before checking the Pushed Index Condition, check if we went out of range.
See opt_index_cond_pushdown.cc, "End-of-range checks".
*/
if (unlikely(h->end_range) && h->compare_key2(h->end_range) > 0)
return CHECK_OUT_OF_RANGE;
h->increment_statistics(&SSV::ha_icp_attempts);

View File

@ -3244,6 +3244,17 @@ class handler :public Sql_alloc
{
public:
typedef ulonglong Table_flags;
/*
The direction of the current range or index scan. This is used by
the ICP implementation to determine if it has reached the end
of the current range.
*/
enum enum_range_scan_direction {
RANGE_SCAN_ASC,
RANGE_SCAN_DESC
};
protected:
TABLE_SHARE *table_share; /* The table definition */
TABLE *table; /* The current open table */
@ -3259,6 +3270,7 @@ protected:
*/
ha_handler_stats active_handler_stats;
void set_handler_stats();
public:
handlerton *ht; /* storage engine of this handler */
OPTIMIZER_COSTS *costs; /* Points to table->share->costs */
@ -3285,7 +3297,11 @@ public:
KEY_MULTI_RANGE mrr_cur_range;
/** The following are for read_range() */
private:
/* Used by Index Condition Pushdown, handler_index_cond_check()/compare_key2() */
enum_range_scan_direction range_scan_direction{RANGE_SCAN_ASC};
public:
/** The following are for read_range_first/next() and ICP */
key_range save_end_range, *end_range;
KEY_PART_INFO *range_key_part;
int key_compare_result_on_equal;
@ -3535,7 +3551,8 @@ public:
DBUG_ASSERT(inited==INDEX);
inited= NONE;
active_index= MAX_KEY;
end_range= NULL;
end_range= NULL;
range_scan_direction= RANGE_SCAN_ASC;
DBUG_RETURN(index_end());
}
/* This is called after index_init() if we need to do a index scan */
@ -4335,7 +4352,8 @@ public:
const key_range *end_key,
bool eq_range, bool sorted);
virtual int read_range_next();
void set_end_range(const key_range *end_key);
virtual void set_end_range(const key_range *end_key,
enum_range_scan_direction direction = RANGE_SCAN_ASC);
int compare_key(key_range *range);
int compare_key2(key_range *range) const;
virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }

View File

@ -19,9 +19,89 @@
#include "sql_test.h"
#include "opt_trace.h"
/****************************************************************************
* Index Condition Pushdown code starts
***************************************************************************/
/*
Index Condition Pushdown Module
===============================
Storage Engine API
==================
SQL layer can push a condition to be checked for index tuple by calling
handler::idx_cond_push(uint keyno, Item *cond)
After that, the SQL layer is expected to start an index scan on the specified
index. The scan should be non-index-only (that is, do not use HA_EXTRA_KEYREAD
option).
Then, any call that reads rows from the index:
handler->some_index_read_function()
will check the index condition (see handler_index_cond_check()) and ignore
index tuples that do not match it.
Pushing index condition requires pushing end-of-range check, too
================================================================
Suppose we're computing
select *
from t1
where key1 between 10 and 20 and extra_index_cond
by using a range scan on (10 <= key1 <= 20) and pushing extra_index_cond as
pushed index condition.
SQL could use these calls to read rows:
h->idx_cond_push(key1, extra_index_cond);
h->index_read_map(key1=10, HA_READ_KEY_OR_NEXT); // (read-1)
while (h->index_next() != HA_ERR_END_OF_FILE) { // (read-2)
if (cmp_key(h->record, "key1=20" ) < 0)
break; // end of range
//process row.
}
Suppose an index read function above (either (read-1) or (read-2)) encounters
key1=21. Suppose extra_index_cond evaluates to false for this row. Then, it
will proceed to read next row, e.g. key1=22. If extra_index_cond again
evaluates to false it will continue further. This way, the index scan can
continue till the end of the index, ignoring the fact that we are not
interested in rows with key1>20.
The solution is: whenever ICP is used, the storage engine must be aware of the
end of the range being scanned so it can stop the scan as soon as it is reached.
End-of-range checks
===================
There are four call patterns:
1. Index Navigation commands. End of range check is setup with set_end_range
call:
handler->set_end_range(endpoint, direction);
handler->index_read_XXXX();
while (handler->index_next() == 0) // or index_prev()
{ ... }
2. Range Read API. set_end_range is called from read_range_first:
handler->read_range_first(start_range, end_range);
while (handler->read_range_next() == 0) { ... }
3. Equality lookups
handler->index_read_map(lookup_tuple, HA_READ_KEY_EXACT);
while (handler->index_next_same() == 0) { ... }
Here, set_end_range is not necessary, because index scanning code
will not read index tuples that do not match the lookup tuple.
4. multi_range_read calls.
These either fall-back to Range Read API or use their own ICP
implementation with its own ICP checks.
*/
/*
Check if given expression uses only table fields covered by the given index

View File

@ -13766,16 +13766,24 @@ int QUICK_SELECT_DESC::get_next()
continue;
}
if (last_range->flag & EQ_RANGE &&
used_key_parts <= head->key_info[index].user_defined_key_parts)
// Case where we can avoid descending scan, see comment above
const bool eqrange_all_keyparts= (last_range->flag & EQ_RANGE) &&
(used_key_parts <= head->key_info[index].user_defined_key_parts);
if (eqrange_all_keyparts)
{
file->set_end_range(NULL, handler::RANGE_SCAN_ASC);
result= file->ha_index_read_map(record, last_range->max_key,
last_range->max_keypart_map,
HA_READ_KEY_EXACT);
}
else
{
key_range min_range;
last_range->make_min_endpoint(&min_range);
if (min_range.length > 0)
file->set_end_range(&min_range, handler::RANGE_SCAN_DESC);
DBUG_ASSERT(last_range->flag & NEAR_MAX ||
(last_range->flag & EQ_RANGE &&
used_key_parts > head->key_info[index].user_defined_key_parts) ||

View File

@ -25016,6 +25016,20 @@ join_read_last_key(JOIN_TAB *tab)
report_error(table,error);
return -1;
}
/*
Tell the storage engine what the range endpoint is so that it can stop
scanning once it has hit that point.
*/
key_range range_endpoint
{
tab->ref.key_buff,
tab->ref.key_length,
make_prev_keypart_map(tab->ref.key_parts),
HA_READ_PREFIX_LAST
};
table->file->set_end_range(&range_endpoint, handler::RANGE_SCAN_DESC);
if (unlikely((error=
table->file->ha_index_read_map(table->record[0],
tab->ref.key_buff,
@ -27042,9 +27056,9 @@ void compute_part_of_sort_key_for_equals(JOIN *join, TABLE *table,
@detail
- Disable "Range checked for each record" (Is this strictly necessary
here?)
- Disable Index Condition Pushdown and Rowid Filtering.
- Disable Rowid Filtering.
IndexConditionPushdownAndReverseScans, RowidFilteringAndReverseScans:
RowidFilteringAndReverseScans:
Suppose we're computing
select * from t1
@ -27076,10 +27090,10 @@ void compute_part_of_sort_key_for_equals(JOIN *join, TABLE *table,
}
Note that the check whether we've walked beyond the key=10 endpoint is
made at the SQL layer. The storage engine has no information about the left
made at the SQL layer. The storage engine has no information about the left
endpoint of the interval we're scanning. If all rows before that endpoint
do not satisfy ICP condition or do not pass the Rowid Filter, the storage
engine will enumerate the records until the table start.
do not pass the Rowid Filter, the storage engine will enumerate the records
until the table start.
In MySQL, the API is extended with set_end_range() call so that the storage
engine "knows" when to stop scanning.
@ -27094,16 +27108,7 @@ static void prepare_for_reverse_ordered_access(JOIN_TAB *tab)
tab->read_first_record= join_init_read_record;
}
/*
Cancel Pushed Index Condition, as it doesn't work for reverse scans.
*/
if (tab->select && tab->select->pre_idx_push_select_cond)
{
tab->set_cond(tab->select->pre_idx_push_select_cond);
tab->table->file->cancel_pushed_idx_cond();
}
/*
The same with Rowid Filter: it doesn't work with reverse scans so cancel
it, too.
Rowid filtering does not work with reverse scans so cancel it.
*/
{
/*