From db20eaf01609935762e848063643deb99cb4a68e Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Tue, 13 Jan 2026 14:49:09 +1100 Subject: [PATCH] MDEV-38327 Do not use rowid filter when switching to index merge Index merge and rowid filter should not be used together, however, even if index merge is not chosen earlier in best_access_path, it may be chosen again in make_join_select. Therefore this patch ensures that rowid filter is not used when index merge is chosen in make_join_select. --- mysql-test/main/index_intersect.result | 66 +++++++++++++++++++ mysql-test/main/index_intersect.test | 49 ++++++++++++++ mysql-test/main/index_intersect_innodb.result | 66 +++++++++++++++++++ sql/opt_range.h | 6 ++ sql/rowid_filter.h | 2 +- sql/sql_delete.cc | 6 +- sql/sql_select.cc | 31 +++------ sql/sql_select.h | 7 ++ storage/innobase/row/row0sel.cc | 2 +- 9 files changed, 207 insertions(+), 28 deletions(-) diff --git a/mysql-test/main/index_intersect.result b/mysql-test/main/index_intersect.result index c6332591ff5d2..a6cf0dccd5023 100644 --- a/mysql-test/main/index_intersect.result +++ b/mysql-test/main/index_intersect.result @@ -973,3 +973,69 @@ f1 f4 f5 DROP TABLE t1; SET SESSION optimizer_switch='index_merge_sort_intersection=on'; SET SESSION optimizer_switch='rowid_filter=default'; +# +# MDEV-38327 wrong result with index_merge_sort_intersection and rowid_filter=on +# +CREATE TABLE t1 (c int, b int, a int , d int, PRIMARY KEY (c), KEY ib (b), KEY iad (a,d)); +INSERT INTO t1 +SELECT seq + 1000000, FLOOR(seq / 5) % 1350 + 1000000, FLOOR(seq / 5) % 1350 , seq % 10 FROM seq_1_to_7000 ; +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +set optimizer_switch='index_merge_sort_intersection=on'; +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status Table is already up to date +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +drop table t1; +## MDEV-28878 case +CREATE TABLE t1 (f int); +INSERT INTO t1 VALUES (0),(4); +CREATE TABLE t2 (pk int, a int, b varchar(10), PRIMARY KEY (pk), KEY a (a), KEY b (b)); +INSERT INTO t2 VALUES +(1,2,'v'),(2,3,'p'),(3,4,'p'),(4,2,'y'),(5,7,'q'), +(6,4,'a'),(7,1,'d'),(8,5,'a'),(9,5,'z'),(10,1,'t'), +(11,1,'y'),(12,5,'o'),(13,4,'a'),(14,5,'s'),(15,5,'m'); +ANALYZE TABLE t1, t2 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +test.t2 analyze status Engine-independent statistics collected +test.t2 analyze status OK +SET optimizer_switch='rowid_filter=on'; +SET optimizer_switch='index_merge_sort_intersection=off'; +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; +f pk a b +4 3 4 p +SET optimizer_switch='index_merge_sort_intersection=on'; +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; +f pk a b +4 3 4 p +DROP TABLE t1, t2; diff --git a/mysql-test/main/index_intersect.test b/mysql-test/main/index_intersect.test index d6208f67a9210..60bcb135dcfce 100644 --- a/mysql-test/main/index_intersect.test +++ b/mysql-test/main/index_intersect.test @@ -476,3 +476,52 @@ DROP TABLE t1; SET SESSION optimizer_switch='index_merge_sort_intersection=on'; SET SESSION optimizer_switch='rowid_filter=default'; + +--echo # +--echo # MDEV-38327 wrong result with index_merge_sort_intersection and rowid_filter=on +--echo # +--source include/have_sequence.inc +CREATE TABLE t1 (c int, b int, a int , d int, PRIMARY KEY (c), KEY ib (b), KEY iad (a,d)); +INSERT INTO t1 + SELECT seq + 1000000, FLOOR(seq / 5) % 1350 + 1000000, FLOOR(seq / 5) % 1350 , seq % 10 FROM seq_1_to_7000 ; + +let $query= +select a, b, c from t1 where a=1000 and b=1001000; + +eval explain $query; +eval $query; + +set optimizer_switch='index_merge_sort_intersection=on'; + +eval explain $query; +eval $query; + +analyze table t1; + +eval explain $query; +eval $query; + +drop table t1; + +--echo ## MDEV-28878 case + +CREATE TABLE t1 (f int); +INSERT INTO t1 VALUES (0),(4); + +CREATE TABLE t2 (pk int, a int, b varchar(10), PRIMARY KEY (pk), KEY a (a), KEY b (b)); +INSERT INTO t2 VALUES + (1,2,'v'),(2,3,'p'),(3,4,'p'),(4,2,'y'),(5,7,'q'), + (6,4,'a'),(7,1,'d'),(8,5,'a'),(9,5,'z'),(10,1,'t'), + (11,1,'y'),(12,5,'o'),(13,4,'a'),(14,5,'s'),(15,5,'m'); + +ANALYZE TABLE t1, t2 PERSISTENT FOR ALL; + +SET optimizer_switch='rowid_filter=on'; # Default + +SET optimizer_switch='index_merge_sort_intersection=off'; # Default +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; + +SET optimizer_switch='index_merge_sort_intersection=on'; +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; + +DROP TABLE t1, t2; diff --git a/mysql-test/main/index_intersect_innodb.result b/mysql-test/main/index_intersect_innodb.result index 2aba9dc6fcfb2..727a79ad09a40 100644 --- a/mysql-test/main/index_intersect_innodb.result +++ b/mysql-test/main/index_intersect_innodb.result @@ -979,6 +979,72 @@ f1 f4 f5 DROP TABLE t1; SET SESSION optimizer_switch='index_merge_sort_intersection=on'; SET SESSION optimizer_switch='rowid_filter=default'; +# +# MDEV-38327 wrong result with index_merge_sort_intersection and rowid_filter=on +# +CREATE TABLE t1 (c int, b int, a int , d int, PRIMARY KEY (c), KEY ib (b), KEY iad (a,d)); +INSERT INTO t1 +SELECT seq + 1000000, FLOOR(seq / 5) % 1350 + 1000000, FLOOR(seq / 5) % 1350 , seq % 10 FROM seq_1_to_7000 ; +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +set optimizer_switch='index_merge_sort_intersection=on'; +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +explain select a, b, c from t1 where a=1000 and b=1001000; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge ib,iad ib,iad 5,5 NULL 1 Using sort_intersect(ib,iad); Using where +select a, b, c from t1 where a=1000 and b=1001000; +a b c +1000 1001000 1005000 +1000 1001000 1005001 +1000 1001000 1005002 +1000 1001000 1005003 +1000 1001000 1005004 +drop table t1; +## MDEV-28878 case +CREATE TABLE t1 (f int); +INSERT INTO t1 VALUES (0),(4); +CREATE TABLE t2 (pk int, a int, b varchar(10), PRIMARY KEY (pk), KEY a (a), KEY b (b)); +INSERT INTO t2 VALUES +(1,2,'v'),(2,3,'p'),(3,4,'p'),(4,2,'y'),(5,7,'q'), +(6,4,'a'),(7,1,'d'),(8,5,'a'),(9,5,'z'),(10,1,'t'), +(11,1,'y'),(12,5,'o'),(13,4,'a'),(14,5,'s'),(15,5,'m'); +ANALYZE TABLE t1, t2 PERSISTENT FOR ALL; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +test.t2 analyze status Engine-independent statistics collected +test.t2 analyze status OK +SET optimizer_switch='rowid_filter=on'; +SET optimizer_switch='index_merge_sort_intersection=off'; +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; +f pk a b +4 3 4 p +SET optimizer_switch='index_merge_sort_intersection=on'; +SELECT * FROM t1 JOIN t2 ON t1.f = t2.a WHERE t2.b >= 'j' AND t2.a != 5; +f pk a b +4 3 4 p +DROP TABLE t1, t2; 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.h b/sql/opt_range.h index fae6bdc1afc5d..172c4c9208d6f 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -1576,6 +1576,7 @@ class QUICK_INDEX_SORT_SELECT : public QUICK_SELECT_I +/* Index merge sort union */ class QUICK_INDEX_MERGE_SELECT : public QUICK_INDEX_SORT_SELECT { private: @@ -1593,6 +1594,7 @@ class QUICK_INDEX_MERGE_SELECT : public QUICK_INDEX_SORT_SELECT void add_keys_and_lengths(String *key_names, String *used_lengths) override; }; +/* Index merge sort intersection */ class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT { protected: @@ -1611,6 +1613,8 @@ class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT /* + Index merge intersection + Rowid-Ordered Retrieval (ROR) index intersection quick select. This quick select produces intersection of row sequences returned by several QUICK_RANGE_SELECTs it "merges". @@ -1698,6 +1702,8 @@ class QUICK_ROR_INTERSECT_SELECT : public QUICK_SELECT_I /* + Index merge union + Rowid-Ordered Retrieval index union select. This quick select produces union of row sequences returned by several quick select it "merges". diff --git a/sql/rowid_filter.h b/sql/rowid_filter.h index 3f18c2edb60c9..0b041e7aeabdb 100644 --- a/sql/rowid_filter.h +++ b/sql/rowid_filter.h @@ -129,7 +129,7 @@ 5. At the execution stage In the function sub_select() just before the first access of a join table s employing a range filter - The method JOIN_TAB::build_range_rowid_filter_if_needed() is called + The method JOIN_TAB::build_range_rowid_filter() is called The method fills the filter using the quick select created by JOIN::make_range_rowid_filters(). diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c6d6916e0a052..45d194cbee713 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -159,11 +159,7 @@ bool Update_plan::save_explain_data_intern(THD *thd, /* Set jtype */ if (select && select->quick) { - int quick_type= select->quick->get_type(); - if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || - (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + if (is_index_merge(select->quick->get_type())) explain->jtype= JT_INDEX_MERGE; else explain->jtype= JT_RANGE; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b986e59656e08..ada98c1ea4f68 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -14456,6 +14456,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) } tab->type= JT_RANGE; tab->use_quick=1; + if (is_index_merge(tab->quick->get_type())) + tab->clear_range_rowid_filter(); tab->ref.key= -1; tab->ref.key_parts=0; // Don't use ref key. join->best_positions[i].records_read= rows2double(tab->quick->records); @@ -27404,17 +27406,13 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, } else if (select && select->quick) // Range found by opt_range { - int quick_type= select->quick->get_type(); - /* - assume results are not ordered when index merge is used - TODO: sergeyp: Results of all index merge selects actually are ordered + /* + assume results are not ordered when index merge is used + TODO: sergeyp: Results of all index merge selects actually are ordered by clustered PK values. */ - - if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) + + if (is_index_merge(select->quick->get_type())) { /* we set ref_key=MAX_KEY instead of -1, because test_if_cheaper_ordering() @@ -27654,10 +27652,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, goto skipped_filesort; quick_type= select->quick->get_type(); - if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || + if (is_index_merge(quick_type) || quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) { tab->limit= 0; @@ -30850,10 +30845,7 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, { cur_quick= tab_select->quick; quick_type= cur_quick->get_type(); - if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || - (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + if (is_index_merge(quick_type)) tab_type= type == JT_HASH ? JT_HASH_INDEX_MERGE : JT_INDEX_MERGE; else tab_type= type == JT_HASH ? JT_HASH_RANGE : JT_RANGE; @@ -31055,10 +31047,7 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, eta->pushed_index_cond= cache_idx_cond; } - if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + if (is_index_merge(quick_type)) { eta->push_extra(ET_USING); } diff --git a/sql/sql_select.h b/sql/sql_select.h index 82e975407edda..518675398ef6b 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -2013,6 +2013,13 @@ void copy_fields(TMP_TABLE_PARAM *param); bool copy_funcs(Item **func_ptr, const THD *thd); uint find_shortest_key(TABLE *table, const key_map *usable_keys); bool is_indexed_agg_distinct(JOIN *join, List *out_args); +inline bool is_index_merge(int qtype) +{ + return qtype == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || + qtype == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || + qtype == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || + qtype == QUICK_SELECT_I::QS_TYPE_ROR_UNION; +} /* functions from opt_sum.cc */ bool simple_pred(Item_func *func_item, Item **args, bool *inv_order); diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index 98ed2b6d7a402..fcbc9e955599d 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -4034,7 +4034,7 @@ row_search_idx_cond_check( byte* mysql_rec, /*!< out: record in MySQL format (invalid unless prebuilt->idx_cond!=NULL and - we return ICP_MATCH) */ + we return CHECK_POS) */ row_prebuilt_t* prebuilt, /*!< in/out: prebuilt struct for the table handle */ const rec_t* rec, /*!< in: InnoDB record */