From 47743e7eafe8022097a72e48153a78efdc5ca65f Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Fri, 24 Oct 2025 22:09:45 +0300 Subject: [PATCH] MDEV-37913: disable_index_merge_plans causes SELECT data loss when more than 100 ORs (Variant 2) SEL_TREE* tree_or(SEL_TREE *X, SEL_TREE *Y) tries to conserve memory by reusing object *X for the return value when possible. MDEV-34620 has added logic to disable construction of index_merge plans for N-way ORs with large N. That logic interfered with object reuse logic: for the parameters of: X = SEL_TREE{ trees=[key1_treeA, key2_treeB]} Y = SEL_TREE{ trees=[key1_treeC]} we would decide to reuse object X. For key1, we would produce key_or(key1_treeA, key1_treeC) but key2_treeB would be just left there. Then, we would construct a range scan from key2_treeB. Fixed by moving the "disable building index_merge plans" logic into a location where it would not interfere with object reuse logic. --- mysql-test/main/range.result | 21 +++++++++++++++++++++ mysql-test/main/range.test | 26 ++++++++++++++++++++++++++ mysql-test/main/range_mrr_icp.result | 21 +++++++++++++++++++++ sql/opt_range.cc | 9 +++++++-- 4 files changed, 75 insertions(+), 2 deletions(-) diff --git a/mysql-test/main/range.result b/mysql-test/main/range.result index fa1f62b076308..6943a295acb46 100644 --- a/mysql-test/main/range.result +++ b/mysql-test/main/range.result @@ -3736,6 +3736,27 @@ DROP TABLE t1; # # End of 10.5 tests # +# +# MDEV-37913: disable_index_merge_plans causes SELECT data loss when more than 100 ORs +# +CREATE TABLE t1 ( +id int primary key, +key1 int, +index(key1) +); +INSERT INTO t1 VALUES +(1, 1), +(2, 1), +(3, 2); +$query; +id key1 +1 1 +2 1 +3 2 +drop table t1; +# +# End of 10.11 tests +# set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; diff --git a/mysql-test/main/range.test b/mysql-test/main/range.test index 2da200f4730d7..a77cef34e4c19 100644 --- a/mysql-test/main/range.test +++ b/mysql-test/main/range.test @@ -2533,6 +2533,32 @@ DROP TABLE t1; --echo # End of 10.5 tests --echo # +--echo # +--echo # MDEV-37913: disable_index_merge_plans causes SELECT data loss when more than 100 ORs +--echo # +CREATE TABLE t1 ( + id int primary key, + key1 int, + index(key1) +); + +INSERT INTO t1 VALUES +(1, 1), +(2, 1), +(3, 2); + +let $query=` + select concat('select * from t1 where (key1 = 2 AND id = 3) ', + REPEAT(' OR (key1 = 1)', 100)) +`; + +evalp $query; + +drop table t1; + +--echo # +--echo # End of 10.11 tests +--echo # set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; diff --git a/mysql-test/main/range_mrr_icp.result b/mysql-test/main/range_mrr_icp.result index a49ad31ecdf8f..7a5a1405c7375 100644 --- a/mysql-test/main/range_mrr_icp.result +++ b/mysql-test/main/range_mrr_icp.result @@ -3725,6 +3725,27 @@ DROP TABLE t1; # # End of 10.5 tests # +# +# MDEV-37913: disable_index_merge_plans causes SELECT data loss when more than 100 ORs +# +CREATE TABLE t1 ( +id int primary key, +key1 int, +index(key1) +); +INSERT INTO t1 VALUES +(1, 1), +(2, 1), +(3, 2); +$query; +id key1 +1 1 +2 1 +3 2 +drop table t1; +# +# End of 10.11 tests +# set global innodb_stats_persistent= @innodb_stats_persistent_save; set global innodb_stats_persistent_sample_pages= @innodb_stats_persistent_sample_pages_save; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index c6c4a5d7b4f5a..75abbe3bae2f8 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -9974,8 +9974,6 @@ tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) { bool must_be_ored= sel_trees_must_be_ored(param, tree1, tree2, ored_keys); no_imerge_from_ranges= must_be_ored; - if (param->disable_index_merge_plans) - no_imerge_from_ranges= true; if (no_imerge_from_ranges && no_merges1 && no_merges2) { @@ -10025,6 +10023,13 @@ tree_or(RANGE_OPT_PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) DBUG_RETURN(result); } + /* + Ok, the result now has the ranges that one gets for (RT1 OR RT2). + If construction of SEL_IMERGE is disabled, stop right here. + */ + if (param->disable_index_merge_plans) + DBUG_RETURN(result); + SEL_IMERGE *imerge_from_ranges; if (!(imerge_from_ranges= new SEL_IMERGE())) result= NULL;