Skip to content

Commit 78ccd80

Browse files
committed
MDEV-39251: Add an option to count records_in_range calls and skip them for point lookups
Add @@optimizer_min_point_range_size_to_use_stats. If the range optimizer estimates a point lookup and the index statistics say that expected number of matching rows is less than that, then skip records_in_range() and use index statistics. The default is @@optimizer_min_point_range_size_to_use_stats=0 which means the old behavior (use records_in_range(), unless the lookup is over unique key, or @@eq_range_index_dive_limit is hit)
1 parent a13fbcf commit 78ccd80

9 files changed

Lines changed: 276 additions & 3 deletions

File tree

mysql-test/main/mysqld--help.result

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,9 @@ The following specify which files/extra groups are read (specified before remain
849849
The maximum number of SEL_ARG objects created when
850850
optimizing a range. If more objects would be needed, the
851851
range will not be used by the optimizer
852+
--optimizer-min-point-range-size-to-use-stats=#
853+
Minimum point range size for which the optimizer will use
854+
statistics instead of records_in_range call
852855
--optimizer-prune-level=#
853856
Controls the heuristic(s) applied during query
854857
optimization to prune less-promising partial plans from
@@ -1883,6 +1886,7 @@ optimizer-key-lookup-cost 0.435777
18831886
optimizer-key-next-find-cost 0.082347
18841887
optimizer-max-sel-arg-weight 32000
18851888
optimizer-max-sel-args 16000
1889+
optimizer-min-point-range-size-to-use-stats 0
18861890
optimizer-prune-level 2
18871891
optimizer-row-copy-cost 0.060866
18881892
optimizer-row-lookup-cost 0.130839

mysql-test/main/range.result

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3871,5 +3871,91 @@ l_orderkey
38713871
291
38723872
DROP TABLE lineitem;
38733873
#
3874+
# MDEV-39251: Add an option to count records_in_range calls and skip them for point lookups
3875+
#
3876+
create table t1 (
3877+
a int,
3878+
b int,
3879+
index(a),
3880+
index(b)
3881+
);
3882+
insert into t1 select seq, floor(seq/10) from seq_1_to_100;
3883+
#
3884+
# The default behavior is to make records_in_range calls:
3885+
#
3886+
flush status;
3887+
explain select * from t1 where a in (1,2);
3888+
id select_type table type possible_keys key key_len ref rows Extra
3889+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition
3890+
show status like 'Handler_records_in_range';
3891+
Variable_name Value
3892+
Handler_records_in_range 2
3893+
flush status;
3894+
explain select * from t1 where a=1;
3895+
id select_type table type possible_keys key key_len ref rows Extra
3896+
1 SIMPLE t1 ref a a 5 const 1
3897+
show status like 'Handler_records_in_range';
3898+
Variable_name Value
3899+
Handler_records_in_range 1
3900+
flush status;
3901+
explain select * from t1 where b=1;
3902+
id select_type table type possible_keys key key_len ref rows Extra
3903+
1 SIMPLE t1 ref b b 5 const 5
3904+
show status like 'Handler_records_in_range';
3905+
Variable_name Value
3906+
Handler_records_in_range 1
3907+
#
3908+
# Now, INDEX(a) should be handled without records_in_range:
3909+
#
3910+
set @tmp=@@optimizer_min_point_range_size_to_use_stats;
3911+
set optimizer_min_point_range_size_to_use_stats=5;
3912+
flush status;
3913+
explain select * from t1 where a in (1,2);
3914+
id select_type table type possible_keys key key_len ref rows Extra
3915+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition
3916+
show status like 'Handler_records_in_range';
3917+
Variable_name Value
3918+
Handler_records_in_range 0
3919+
flush status;
3920+
explain select * from t1 where a=1;
3921+
id select_type table type possible_keys key key_len ref rows Extra
3922+
1 SIMPLE t1 ref a a 5 const 1
3923+
show status like 'Handler_records_in_range';
3924+
Variable_name Value
3925+
Handler_records_in_range 0
3926+
flush status;
3927+
explain select * from t1 where b=1;
3928+
id select_type table type possible_keys key key_len ref rows Extra
3929+
1 SIMPLE t1 ref b b 5 const 5
3930+
show status like 'Handler_records_in_range';
3931+
Variable_name Value
3932+
Handler_records_in_range 1
3933+
#
3934+
# Now, both INDEX(a) and INDEX(a) should be handled without records_in_range:
3935+
#
3936+
set optimizer_min_point_range_size_to_use_stats=30;
3937+
flush status;
3938+
explain select * from t1 where a in (1,2);
3939+
id select_type table type possible_keys key key_len ref rows Extra
3940+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition
3941+
show status like 'Handler_records_in_range';
3942+
Variable_name Value
3943+
Handler_records_in_range 0
3944+
flush status;
3945+
explain select * from t1 where a=1;
3946+
id select_type table type possible_keys key key_len ref rows Extra
3947+
1 SIMPLE t1 ref a a 5 const 1
3948+
show status like 'Handler_records_in_range';
3949+
Variable_name Value
3950+
Handler_records_in_range 0
3951+
flush status;
3952+
explain select * from t1 where b=1;
3953+
id select_type table type possible_keys key key_len ref rows Extra
3954+
1 SIMPLE t1 ref b b 5 const 9
3955+
show status like 'Handler_records_in_range';
3956+
Variable_name Value
3957+
Handler_records_in_range 0
3958+
set optimizer_min_point_range_size_to_use_stats=@tmp;
3959+
#
38743960
# End of 11.0 tests
38753961
#

mysql-test/main/range.test

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2638,6 +2638,69 @@ EXPLAIN SELECT DISTINCT l_orderkey FROM lineitem FORCE KEY (i_l_orderkey, i_l_re
26382638
SELECT DISTINCT l_orderkey FROM lineitem FORCE KEY (i_l_orderkey, i_l_receiptdate) WHERE l_orderkey > 1 ORDER BY l_receiptdate;
26392639
DROP TABLE lineitem;
26402640

2641+
--echo #
2642+
--echo # MDEV-39251: Add an option to count records_in_range calls and skip them for point lookups
2643+
--echo #
2644+
create table t1 (
2645+
a int,
2646+
b int,
2647+
index(a),
2648+
index(b)
2649+
);
2650+
insert into t1 select seq, floor(seq/10) from seq_1_to_100;
2651+
2652+
--echo #
2653+
--echo # The default behavior is to make records_in_range calls:
2654+
--echo #
2655+
flush status;
2656+
explain select * from t1 where a in (1,2);
2657+
show status like 'Handler_records_in_range';
2658+
2659+
flush status;
2660+
explain select * from t1 where a=1;
2661+
show status like 'Handler_records_in_range';
2662+
2663+
flush status;
2664+
explain select * from t1 where b=1;
2665+
show status like 'Handler_records_in_range';
2666+
2667+
--echo #
2668+
--echo # Now, INDEX(a) should be handled without records_in_range:
2669+
--echo #
2670+
set @tmp=@@optimizer_min_point_range_size_to_use_stats;
2671+
set optimizer_min_point_range_size_to_use_stats=5;
2672+
2673+
flush status;
2674+
explain select * from t1 where a in (1,2);
2675+
show status like 'Handler_records_in_range';
2676+
2677+
flush status;
2678+
explain select * from t1 where a=1;
2679+
show status like 'Handler_records_in_range';
2680+
2681+
flush status;
2682+
explain select * from t1 where b=1;
2683+
show status like 'Handler_records_in_range';
2684+
2685+
--echo #
2686+
--echo # Now, both INDEX(a) and INDEX(a) should be handled without records_in_range:
2687+
--echo #
2688+
set optimizer_min_point_range_size_to_use_stats=30;
2689+
2690+
flush status;
2691+
explain select * from t1 where a in (1,2);
2692+
show status like 'Handler_records_in_range';
2693+
2694+
flush status;
2695+
explain select * from t1 where a=1;
2696+
show status like 'Handler_records_in_range';
2697+
2698+
flush status;
2699+
explain select * from t1 where b=1;
2700+
show status like 'Handler_records_in_range';
2701+
2702+
set optimizer_min_point_range_size_to_use_stats=@tmp;
2703+
26412704
--echo #
26422705
--echo # End of 11.0 tests
26432706
--echo #

mysql-test/main/range_mrr_icp.result

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3869,6 +3869,92 @@ l_orderkey
38693869
291
38703870
DROP TABLE lineitem;
38713871
#
3872+
# MDEV-39251: Add an option to count records_in_range calls and skip them for point lookups
3873+
#
3874+
create table t1 (
3875+
a int,
3876+
b int,
3877+
index(a),
3878+
index(b)
3879+
);
3880+
insert into t1 select seq, floor(seq/10) from seq_1_to_100;
3881+
#
3882+
# The default behavior is to make records_in_range calls:
3883+
#
3884+
flush status;
3885+
explain select * from t1 where a in (1,2);
3886+
id select_type table type possible_keys key key_len ref rows Extra
3887+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition; Rowid-ordered scan
3888+
show status like 'Handler_records_in_range';
3889+
Variable_name Value
3890+
Handler_records_in_range 2
3891+
flush status;
3892+
explain select * from t1 where a=1;
3893+
id select_type table type possible_keys key key_len ref rows Extra
3894+
1 SIMPLE t1 ref a a 5 const 1
3895+
show status like 'Handler_records_in_range';
3896+
Variable_name Value
3897+
Handler_records_in_range 1
3898+
flush status;
3899+
explain select * from t1 where b=1;
3900+
id select_type table type possible_keys key key_len ref rows Extra
3901+
1 SIMPLE t1 ref b b 5 const 5
3902+
show status like 'Handler_records_in_range';
3903+
Variable_name Value
3904+
Handler_records_in_range 1
3905+
#
3906+
# Now, INDEX(a) should be handled without records_in_range:
3907+
#
3908+
set @tmp=@@optimizer_min_point_range_size_to_use_stats;
3909+
set optimizer_min_point_range_size_to_use_stats=5;
3910+
flush status;
3911+
explain select * from t1 where a in (1,2);
3912+
id select_type table type possible_keys key key_len ref rows Extra
3913+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition; Rowid-ordered scan
3914+
show status like 'Handler_records_in_range';
3915+
Variable_name Value
3916+
Handler_records_in_range 0
3917+
flush status;
3918+
explain select * from t1 where a=1;
3919+
id select_type table type possible_keys key key_len ref rows Extra
3920+
1 SIMPLE t1 ref a a 5 const 1
3921+
show status like 'Handler_records_in_range';
3922+
Variable_name Value
3923+
Handler_records_in_range 0
3924+
flush status;
3925+
explain select * from t1 where b=1;
3926+
id select_type table type possible_keys key key_len ref rows Extra
3927+
1 SIMPLE t1 ref b b 5 const 5
3928+
show status like 'Handler_records_in_range';
3929+
Variable_name Value
3930+
Handler_records_in_range 1
3931+
#
3932+
# Now, both INDEX(a) and INDEX(a) should be handled without records_in_range:
3933+
#
3934+
set optimizer_min_point_range_size_to_use_stats=30;
3935+
flush status;
3936+
explain select * from t1 where a in (1,2);
3937+
id select_type table type possible_keys key key_len ref rows Extra
3938+
1 SIMPLE t1 range a a 5 NULL 2 Using index condition; Rowid-ordered scan
3939+
show status like 'Handler_records_in_range';
3940+
Variable_name Value
3941+
Handler_records_in_range 0
3942+
flush status;
3943+
explain select * from t1 where a=1;
3944+
id select_type table type possible_keys key key_len ref rows Extra
3945+
1 SIMPLE t1 ref a a 5 const 1
3946+
show status like 'Handler_records_in_range';
3947+
Variable_name Value
3948+
Handler_records_in_range 0
3949+
flush status;
3950+
explain select * from t1 where b=1;
3951+
id select_type table type possible_keys key key_len ref rows Extra
3952+
1 SIMPLE t1 ref b b 5 const 9
3953+
show status like 'Handler_records_in_range';
3954+
Variable_name Value
3955+
Handler_records_in_range 0
3956+
set optimizer_min_point_range_size_to_use_stats=@tmp;
3957+
#
38723958
# End of 11.0 tests
38733959
#
38743960
set optimizer_switch=@mrr_icp_extra_tmp;

mysql-test/suite/sys_vars/r/sysvars_server_embedded.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,6 +2572,16 @@ NUMERIC_BLOCK_SIZE 1
25722572
ENUM_VALUE_LIST NULL
25732573
READ_ONLY NO
25742574
COMMAND_LINE_ARGUMENT REQUIRED
2575+
VARIABLE_NAME OPTIMIZER_MIN_POINT_RANGE_SIZE_TO_USE_STATS
2576+
VARIABLE_SCOPE SESSION
2577+
VARIABLE_TYPE BIGINT UNSIGNED
2578+
VARIABLE_COMMENT Minimum point range size for which the optimizer will use statistics instead of records_in_range call
2579+
NUMERIC_MIN_VALUE 0
2580+
NUMERIC_MAX_VALUE 18446744073709551615
2581+
NUMERIC_BLOCK_SIZE 1
2582+
ENUM_VALUE_LIST NULL
2583+
READ_ONLY NO
2584+
COMMAND_LINE_ARGUMENT REQUIRED
25752585
VARIABLE_NAME OPTIMIZER_PRUNE_LEVEL
25762586
VARIABLE_SCOPE SESSION
25772587
VARIABLE_TYPE BIGINT UNSIGNED

mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2782,6 +2782,16 @@ NUMERIC_BLOCK_SIZE 1
27822782
ENUM_VALUE_LIST NULL
27832783
READ_ONLY NO
27842784
COMMAND_LINE_ARGUMENT REQUIRED
2785+
VARIABLE_NAME OPTIMIZER_MIN_POINT_RANGE_SIZE_TO_USE_STATS
2786+
VARIABLE_SCOPE SESSION
2787+
VARIABLE_TYPE BIGINT UNSIGNED
2788+
VARIABLE_COMMENT Minimum point range size for which the optimizer will use statistics instead of records_in_range call
2789+
NUMERIC_MIN_VALUE 0
2790+
NUMERIC_MAX_VALUE 18446744073709551615
2791+
NUMERIC_BLOCK_SIZE 1
2792+
ENUM_VALUE_LIST NULL
2793+
READ_ONLY NO
2794+
COMMAND_LINE_ARGUMENT REQUIRED
27852795
VARIABLE_NAME OPTIMIZER_PRUNE_LEVEL
27862796
VARIABLE_SCOPE SESSION
27872797
VARIABLE_TYPE BIGINT UNSIGNED

sql/multi_range_read.cc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,13 @@ handler::multi_range_read_info_const(uint keyno, RANGE_SEQ_IF *seq,
240240
*/
241241
single_point_ranges++;
242242
}
243-
else if (use_statistics_for_eq_range &&
244-
!(range.range_flag & NULL_RANGE) &&
243+
else if (!(range.range_flag & NULL_RANGE) &&
245244
(range.range_flag & EQ_RANGE) &&
246-
table->key_info[keyno].actual_rec_per_key(keyparts_used - 1) > 0.5)
245+
(use_statistics_for_eq_range ||
246+
(thd->variables.optimizer_min_point_range_size_to_use_stats >
247+
table->key_info[keyno].actual_rec_per_key(keyparts_used - 1))
248+
) &&
249+
table->key_info[keyno].actual_rec_per_key(keyparts_used - 1) > 0.5 )
247250
{
248251
rows= ((ha_rows) table->key_info[keyno].
249252
actual_rec_per_key(keyparts_used-1));

sql/sql_class.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,7 @@ typedef struct system_variables
775775
ha_rows select_limit;
776776
ha_rows max_join_size;
777777
ha_rows expensive_subquery_limit;
778+
ha_rows optimizer_min_point_range_size_to_use_stats;
778779

779780
#ifdef WITH_WSREP
780781
/*

sql/sys_vars.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,16 @@ static Sys_var_ulong Sys_optimizer_adjust_secondary_key_costs(
31023102
NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), ON_UPDATE(0),
31033103
DEPRECATED(1100, ""));
31043104

3105+
static Sys_var_harows Sys_optimizer_min_point_range_size_to_use_stats(
3106+
"optimizer_min_point_range_size_to_use_stats",
3107+
"Minimum point range size for which the optimizer will use "
3108+
"statistics instead of records_in_range call",
3109+
SESSION_VAR(optimizer_min_point_range_size_to_use_stats),
3110+
CMD_LINE(REQUIRED_ARG),
3111+
VALID_RANGE(0, HA_POS_ERROR), DEFAULT(0), BLOCK_SIZE(1),
3112+
NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0),
3113+
ON_UPDATE(0));
3114+
31053115
static Sys_var_charptr_fscs Sys_pid_file(
31063116
"pid_file", "Pid file used by mariadbd-safe",
31073117
READ_ONLY GLOBAL_VAR(pidfile_name_ptr), CMD_LINE(REQUIRED_ARG),

0 commit comments

Comments
 (0)