diff --git a/mysql-test/r/skip_records_in_range.result b/mysql-test/r/skip_records_in_range.result index e9a622f52f3..a5affa065ae 100644 --- a/mysql-test/r/skip_records_in_range.result +++ b/mysql-test/r/skip_records_in_range.result @@ -528,3 +528,19 @@ select * from v1; v3 DROP VIEW v1; DROP TABLE t1; +# +# Bug #117791: FORCE INDEX does not skip records_in_range() for 'x IS NULL' +# +CREATE TABLE t1 (c1 INT, KEY (c1)); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5), (6), (7), (8), (NULL), (NULL); +ANALYZE TABLE t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +set debug='+d,crash_records_in_range'; +EXPLAIN SELECT * FROM t1 FORCE INDEX(c1) WHERE c1 IS NULL; +id select_type table partitions type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 NULL ref c1 c1 5 const 1 100.00 Using where; Using index +Warnings: +Note 1003 /* select#1 */ select `test`.`t1`.`c1` AS `c1` from `test`.`t1` FORCE INDEX (`c1`) where (`test`.`t1`.`c1` is null) +set debug='-d,crash_records_in_range'; +DROP TABLE t1; diff --git a/mysql-test/t/skip_records_in_range.test b/mysql-test/t/skip_records_in_range.test index 09da3de714f..9756fea7bbd 100644 --- a/mysql-test/t/skip_records_in_range.test +++ b/mysql-test/t/skip_records_in_range.test @@ -116,3 +116,19 @@ eval $query1; DROP VIEW v1; DROP TABLE t1; + +--echo # +--echo # Bug #117791: FORCE INDEX does not skip records_in_range() for 'x IS NULL' +--echo # + +CREATE TABLE t1 (c1 INT, KEY (c1)); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5), (6), (7), (8), (NULL), (NULL); +ANALYZE TABLE t1; + +set debug='+d,crash_records_in_range'; + +EXPLAIN SELECT * FROM t1 FORCE INDEX(c1) WHERE c1 IS NULL; + +set debug='-d,crash_records_in_range'; + +DROP TABLE t1; diff --git a/sql/handler.cc b/sql/handler.cc index d7d947e481f..cf48e954f8f 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -6404,26 +6404,18 @@ ha_rows handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq, is actually 0, so the row estimate may be too high in this case. Also note: ranges of the form "x IS NULL" may have more than 1 matching row so records_in_range() is called for these. - 2) SKIP_RECORDS_IN_RANGE will be set when skip_records_in_range or - use_index_statistics are true. - Ranges of the form "x IS NULL" will not use index statistics - because the number of rows with this value are likely to be - very different than the values in the index statistics. - - Note: With SKIP_RECORDS_IN_RANGE, use Index statistics if: - a) Index statistics is available. - b) The range is an equality range but the index is either not - unique or all of the keyparts are not used. + 2) SKIP_RECORDS_IN_RANGE is set. + There are supportive statistics and the user requested to use + statistics instead of records_in_range(); or the user is not + interested in cost calculation at all. */ int keyparts_used = 0; if ((range.range_flag & UNIQUE_RANGE) && // 1) !(range.range_flag & NULL_RANGE)) rows = 1; /* there can be at most one row */ - else if (range.range_flag & SKIP_RECORDS_IN_RANGE && // 2) - !(range.range_flag & NULL_RANGE)) { + else if (range.range_flag & SKIP_RECORDS_IN_RANGE) { // 2) if ((range.range_flag & EQ_RANGE) && - (keyparts_used = std::popcount(range.start_key.keypart_map)) && - table->key_info[keyno].has_records_per_key(keyparts_used - 1)) { + (keyparts_used = std::popcount(range.start_key.keypart_map))) { rows = static_cast( table->key_info[keyno].records_per_key(keyparts_used - 1)); } else { diff --git a/sql/range_optimizer/index_range_scan_plan.cc b/sql/range_optimizer/index_range_scan_plan.cc index c35cc113113..891eb13bbbb 100644 --- a/sql/range_optimizer/index_range_scan_plan.cc +++ b/sql/range_optimizer/index_range_scan_plan.cc @@ -516,13 +516,6 @@ static uint sel_arg_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range) { if (is_eq_range_pred) { range->range_flag = EQ_RANGE; - /* - Use statistics instead of index dives for estimates of rows in - this range if the user requested it - */ - if (param->use_index_statistics) - range->range_flag |= SKIP_RECORDS_IN_RANGE; - /* An equality range is a unique range (0 or 1 rows in the range) if the index is unique (1) and all keyparts are used (2). @@ -560,6 +553,28 @@ static uint sel_arg_range_seq_next(range_seq_t rseq, KEY_MULTI_RANGE *range) { seq->range_count++; seq->max_key_part = max(seq->max_key_part, key_tree->part); + /* + Use statistics instead of index dives for estimates of rows in + this range if: + + 1) The user has requested it. + 2) Index statistics is supportive for the range, which means + a) It is an equality range. + b) It contains no null part. + 3) Index statistics is available. + + Ranges of the form "x IS NULL" will not use index statistics + because the number of rows with this value are likely to be + very different than the values in the index statistics. + */ + int keyparts_used = 0; + if (param->use_index_statistics && // 1) + (range->range_flag & EQ_RANGE) && // 2a) + !(range->range_flag & NULL_RANGE) && // 2b) + (keyparts_used = std::popcount(range->start_key.keypart_map)) && // 3) + param->table->key_info[seq->keyno].has_records_per_key(keyparts_used - 1)) + range->range_flag |= SKIP_RECORDS_IN_RANGE; + if (seq->skip_records_in_range) range->range_flag |= SKIP_RECORDS_IN_RANGE; return 0;