Skip to content

Commit da1d92f

Browse files
author
Olav Sandstaa
committed
Fix for Bug#14503142 PERFORMANCE REGRESSION FOR DESCENDING RANGE SCAN WHEN
USING ICP The performance regression is caused by use of ICP in combination with a descending range scan that is using QUICK_SELECT_DESC. The QUICK_SELECT_DESC object executes the range scan by reading the index using ha_index_prev() and then evaluating if the record is within the current range. This works fine when we have not pushed any index condition to the storage engine since every record will be returned and evaluated by QUICK_SELECT_DESC.get_next(). When ICP is used, the call to ha_index_prev() will only return records that do not get filtered by the pushed index condition. In this case, as soon as QUICK_SELECT_DESC.get_next() tries to read the first record that is outside the current range, the ICP implementation in the storage engine will filter it away and continue reading from the index without finding any qualifying records. It will continue reading from the index until it has read to the end of the index. The increased execution time is caused by the ICP implementation reading to the end of the index instead of stopping when it has reached the end of the current range. This patch solves this problem by adding a new handler call handler::set_end_range() which informs the storage engine about the end of the current index scan. The ICP implementation in the storage engine will use this information and check that the next record is within this range. For each range that QUICK_SELECT_DESC.get_next() needs to read, it will use this call to let the storage engine know about the end of the range.
1 parent aa9add5 commit da1d92f

File tree

6 files changed

+93
-16
lines changed

6 files changed

+93
-16
lines changed

sql/ha_partition.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5390,6 +5390,7 @@ int ha_partition::read_range_first(const key_range *start_key,
53905390
key_compare_result_on_equal=
53915391
((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
53925392
(end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
5393+
range_scan_direction= RANGE_SCAN_ASC;
53935394
}
53945395

53955396
range_key_part= m_curr_key_info[0]->key_part;

sql/handler.cc

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6550,6 +6550,7 @@ int handler::read_range_first(const key_range *start_key,
65506550
save_end_range= *end_key;
65516551
key_compare_result_on_equal= ((end_key->flag == HA_READ_BEFORE_KEY) ? 1 :
65526552
(end_key->flag == HA_READ_AFTER_KEY) ? -1 : 0);
6553+
range_scan_direction= RANGE_SCAN_ASC;
65536554
}
65546555
range_key_part= table->key_info[active_index].key_part;
65556556

@@ -6626,6 +6627,20 @@ int handler::read_range_next()
66266627
}
66276628

66286629

6630+
void handler::set_end_range(const key_range* range,
6631+
enum_range_scan_direction direction)
6632+
{
6633+
DBUG_ASSERT(range != NULL);
6634+
6635+
save_end_range= *range;
6636+
end_range= &save_end_range;
6637+
range_key_part= table->key_info[active_index].key_part;
6638+
key_compare_result_on_equal= ((range->flag == HA_READ_BEFORE_KEY) ? 1 :
6639+
(range->flag == HA_READ_AFTER_KEY) ? -1 : 0);
6640+
range_scan_direction= direction;
6641+
}
6642+
6643+
66296644
/**
66306645
Compare if found key (in row) is over max-value.
66316646
@@ -6654,18 +6669,35 @@ int handler::compare_key(key_range *range)
66546669

66556670

66566671
/*
6657-
Same as compare_key() but doesn't check have in_range_check_pushed_down.
6658-
This is used by index condition pushdown implementation.
6672+
Compare if a found key (in row) is within the range.
6673+
6674+
This function is similar to compare_key() but checks the range scan
6675+
direction to determine if this is a descending scan. This function
6676+
is used by the index condition pushdown implementation to determine
6677+
if the read record is within the range scan.
6678+
6679+
@param range Range to compare to row. May be NULL for no range.
6680+
6681+
@seealso
6682+
handler::compare_key()
6683+
6684+
@return Returns whether the key is within the range
6685+
6686+
- 0 : Key is equal to range or 'range' == 0 (no range)
6687+
- -1 : Key is within the current range
6688+
- 1 : Key is outside the current range
66596689
*/
66606690

6661-
int handler::compare_key2(key_range *range)
6691+
int handler::compare_key_icp(const key_range *range) const
66626692
{
66636693
int cmp;
66646694
if (!range)
66656695
return 0; // no max range
66666696
cmp= key_cmp(range_key_part, range->key, range->length);
66676697
if (!cmp)
66686698
cmp= key_compare_result_on_equal;
6699+
if (range_scan_direction == RANGE_SCAN_DESC)
6700+
cmp= -cmp;
66696701
return cmp;
66706702
}
66716703

sql/handler.h

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,20 @@ class handler :public Sql_alloc
17781778
KEY_PART_INFO *range_key_part;
17791779
int key_compare_result_on_equal;
17801780
bool eq_range;
1781+
1782+
/*
1783+
The direction of the current range or index scan. This is used by
1784+
the ICP implementation to determine if it has reached the end
1785+
of the current range.
1786+
*/
1787+
enum enum_range_scan_direction {
1788+
RANGE_SCAN_ASC,
1789+
RANGE_SCAN_DESC
1790+
};
1791+
protected:
1792+
enum_range_scan_direction range_scan_direction;
1793+
1794+
public:
17811795
/*
17821796
TRUE <=> the engine guarantees that returned records are within the range
17831797
being scanned.
@@ -1863,7 +1877,8 @@ class handler :public Sql_alloc
18631877
handler(handlerton *ht_arg, TABLE_SHARE *share_arg)
18641878
:table_share(share_arg), table(0),
18651879
estimation_rows_to_insert(0), ht(ht_arg),
1866-
ref(0), end_range(NULL), in_range_check_pushed_down(false),
1880+
ref(0), end_range(NULL), range_scan_direction(RANGE_SCAN_ASC),
1881+
in_range_check_pushed_down(false),
18671882
key_used_on_scan(MAX_KEY), active_index(MAX_KEY),
18681883
ref_length(sizeof(my_off_t)),
18691884
ft_handler(0), inited(NONE),
@@ -2203,8 +2218,19 @@ class handler :public Sql_alloc
22032218
const key_range *end_key,
22042219
bool eq_range, bool sorted);
22052220
virtual int read_range_next();
2221+
2222+
/**
2223+
Set the end position for a range scan. This is used for checking
2224+
for when to end the range scan and by the ICP code to determine
2225+
that the next record is within the current range.
2226+
2227+
@param range The end value for the range scan
2228+
@param direction Direction of the range scan
2229+
*/
2230+
void set_end_range(const key_range* range,
2231+
enum_range_scan_direction direction);
22062232
int compare_key(key_range *range);
2207-
int compare_key2(key_range *range);
2233+
int compare_key_icp(const key_range *range) const;
22082234
virtual int ft_init() { return HA_ERR_WRONG_COMMAND; }
22092235
void ft_end() { ft_handler=NULL; }
22102236
virtual FT_INFO *ft_init_ext(uint flags, uint inx,String *key)

sql/opt_range.cc

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10441,9 +10441,11 @@ int QUICK_SELECT_DESC::get_next()
1044110441

1044210442
/* The max key is handled as follows:
1044310443
* - if there is NO_MAX_RANGE, start at the end and move backwards
10444-
* - if it is an EQ_RANGE, which means that max key covers the entire
10445-
* key, go directly to the key and read through it (sorting backwards is
10446-
* same as sorting forwards)
10444+
* - if it is an EQ_RANGE (which means that max key covers the entire
10445+
* key) and the query does not use any hidden key fields that are
10446+
* not considered when the range optimzier sets EQ_RANGE (e.g. the
10447+
* primary key added by InnoDB), then go directly to the key and
10448+
* read through it (sorting backwards is same as sorting forwards).
1044710449
* - if it is NEAR_MAX, go to the key or next, step back once, and
1044810450
* move backwards
1044910451
* - otherwise (not NEAR_MAX == include the key), go after the key,
@@ -10472,6 +10474,24 @@ int QUICK_SELECT_DESC::get_next()
1047210474
if (!(last_range= rev_it++))
1047310475
DBUG_RETURN(HA_ERR_END_OF_FILE); // All ranges used
1047410476

10477+
// Case where we can avoid descending scan, see comment above
10478+
const bool eqrange_all_keyparts= (last_range->flag & EQ_RANGE) &&
10479+
(used_key_parts <= head->key_info[index].key_parts);
10480+
10481+
/*
10482+
If we have pushed an index condition (ICP) and this quick select
10483+
will use ha_index_prev() to read data, we need to let the
10484+
handler know where to end the scan in order to avoid that the
10485+
ICP implemention continues to read past the range boundary.
10486+
*/
10487+
if (file->pushed_idx_cond && !eqrange_all_keyparts)
10488+
{
10489+
key_range min_range;
10490+
last_range->make_min_endpoint(&min_range);
10491+
if(min_range.length > 0)
10492+
file->set_end_range(&min_range, handler::RANGE_SCAN_DESC);
10493+
}
10494+
1047510495
if (last_range->flag & NO_MAX_RANGE) // Read last record
1047610496
{
1047710497
int local_error;
@@ -10483,8 +10503,7 @@ int QUICK_SELECT_DESC::get_next()
1048310503
continue;
1048410504
}
1048510505

10486-
if (last_range->flag & EQ_RANGE &&
10487-
used_key_parts <= head->key_info[index].key_parts)
10506+
if (eqrange_all_keyparts)
1048810507

1048910508
{
1049010509
result= file->ha_index_read_map(record, last_range->max_key,

storage/innobase/handler/ha_innodb.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16332,7 +16332,7 @@ innobase_index_cond(
1633216332
DBUG_ASSERT(h->pushed_idx_cond);
1633316333
DBUG_ASSERT(h->pushed_idx_cond_keyno != MAX_KEY);
1633416334

16335-
if (h->end_range && h->compare_key2(h->end_range) > 0) {
16335+
if (h->end_range && h->compare_key_icp(h->end_range) > 0) {
1633616336

1633716337
/* caller should return HA_ERR_END_OF_FILE already */
1633816338
DBUG_RETURN(ICP_OUT_OF_RANGE);

storage/myisam/ha_myisam.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,11 +1605,10 @@ C_MODE_START
16051605
ICP_RESULT index_cond_func_myisam(void *arg)
16061606
{
16071607
ha_myisam *h= (ha_myisam*)arg;
1608-
if (h->end_range)
1609-
{
1610-
if (h->compare_key2(h->end_range) > 0)
1611-
return ICP_OUT_OF_RANGE; /* caller should return HA_ERR_END_OF_FILE already */
1612-
}
1608+
1609+
if (h->end_range && h->compare_key_icp(h->end_range) > 0)
1610+
return ICP_OUT_OF_RANGE; /* caller should return HA_ERR_END_OF_FILE already */
1611+
16131612
return (ICP_RESULT) test(h->pushed_idx_cond->val_int());
16141613
}
16151614

0 commit comments

Comments
 (0)