From 2d88d09203fba8d0154430eeebb417f4659268be Mon Sep 17 00:00:00 2001 From: OmarGamal10 Date: Mon, 2 Mar 2026 21:52:19 +0200 Subject: [PATCH] MDEV-38877: Unnecessary filesort on derived table materialization Fixes unnecessary filesort on derived tables when ordered/grouped by a field in the key. The data is inherently sorted, wrapping the result set in a filesort is redundant. --- mysql-test/main/derived_cond_pushdown.result | 2 +- mysql-test/main/derived_split_innodb.result | 4 +- mysql-test/main/mdev-38877.result | 32 +++++++++++++++ mysql-test/main/mdev-38877.test | 39 +++++++++++++++++++ .../main/opt_hints_split_materialized.result | 4 +- sql/sql_select.cc | 18 +++++++++ sql/sql_select.h | 6 ++- 7 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 mysql-test/main/mdev-38877.result create mode 100644 mysql-test/main/mdev-38877.test diff --git a/mysql-test/main/derived_cond_pushdown.result b/mysql-test/main/derived_cond_pushdown.result index 8a03938837520..cc6da86c5d48a 100644 --- a/mysql-test/main/derived_cond_pushdown.result +++ b/mysql-test/main/derived_cond_pushdown.result @@ -21164,7 +21164,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY ALL NULL NULL NULL NULL 7 100.00 2 DERIVED ALL NULL NULL NULL NULL 7 100.00 Using temporary; Using filesort 2 DERIVED a2 eq_ref PRIMARY PRIMARY 4 a1.f 1 100.00 Using index -4 DERIVED t1 index PRIMARY PRIMARY 4 NULL 7 100.00 Using index; Using temporary; Using filesort +4 DERIVED t1 index PRIMARY PRIMARY 4 NULL 7 100.00 Using index Warnings: Note 1003 /* select#1 */ select `s`.`f` AS `f`,`s`.`c` AS `c` from (/* select#2 */ select straight_join `a2`.`f` AS `f`,count(0) AS `c` from (/* select#4 */ select `test`.`t1`.`f` AS `f`,count(0) AS `c` from `test`.`t1` group by `test`.`t1`.`f`) `a1` join `test`.`t1` `a2` where `a2`.`f` = `a1`.`f` group by `a2`.`f`) `s` SELECT * FROM ( SELECT STRAIGHT_JOIN f, COUNT(*) as c FROM v1 GROUP BY f ) AS s; diff --git a/mysql-test/main/derived_split_innodb.result b/mysql-test/main/derived_split_innodb.result index 6951da7371816..c70aa84703e2b 100644 --- a/mysql-test/main/derived_split_innodb.result +++ b/mysql-test/main/derived_split_innodb.result @@ -131,7 +131,7 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t const f2 NULL NULL NULL 0 Impossible ON condition 1 PRIMARY const key0,key1 NULL NULL NULL 0 Impossible ON condition 1 PRIMARY t1 ALL NULL NULL NULL NULL 3 -2 DERIVED t2 ALL PRIMARY NULL NULL NULL 3 Using temporary; Using filesort +2 DERIVED t2 index PRIMARY PRIMARY 4 NULL 3 set statement optimizer_switch='split_materialized=off' for explain select t.f2 from t1 left join @@ -998,7 +998,7 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 2 1 PRIMARY t2 ref a a 5 test.t1.a 1 Using where; Using index 1 PRIMARY ref key0 key0 5 func 1 Using where -2 DERIVED t10 index grp_id grp_id 5 NULL 10000 Using index; Using temporary; Using filesort +2 DERIVED t10 index grp_id grp_id 5 NULL 10000 Using index drop table t1,t2, t10; drop view v1; # diff --git a/mysql-test/main/mdev-38877.result b/mysql-test/main/mdev-38877.result new file mode 100644 index 0000000000000..a0b114f50ac4a --- /dev/null +++ b/mysql-test/main/mdev-38877.result @@ -0,0 +1,32 @@ +# +# MDEV-38877: Unnecessary filesort on derived table materialization +# when derived table is inherently sorted by the grouping columns +# +CREATE TABLE t1 ( +groups_20 int NOT NULL, +groups_20_2 int NOT NULL, +b int, +PRIMARY KEY (groups_20, groups_20_2) +); +CREATE TABLE t2 (a int, b int, index(a)); +INSERT INTO t1 SELECT seq/1000, seq+1, seq from seq_1_to_10000; +INSERT INTO t2 SELECT seq, seq from seq_1_to_1000; +ANALYZE TABLE t1, t2; +EXPLAIN +SELECT +a, +SUM(b) +FROM ( +SELECT +groups_20 +FROM t1 +GROUP BY groups_20 +HAVING COUNT(*) != 1000 +) DT +JOIN t2 ON a = groups_20 +GROUP BY a; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL a NULL NULL NULL 1000 Using where; Using temporary; Using filesort +1 PRIMARY ref key0 key0 4 test.t2.a 1 +2 DERIVED t1 index PRIMARY PRIMARY 8 NULL 10000 Using index +DROP TABLE t1, t2; diff --git a/mysql-test/main/mdev-38877.test b/mysql-test/main/mdev-38877.test new file mode 100644 index 0000000000000..ae6699261937d --- /dev/null +++ b/mysql-test/main/mdev-38877.test @@ -0,0 +1,39 @@ +--source include/have_sequence.inc + +--echo # +--echo # MDEV-38877: Unnecessary filesort on derived table materialization +--echo # when derived table is inherently sorted by the grouping columns +--echo # + +CREATE TABLE t1 ( + groups_20 int NOT NULL, + groups_20_2 int NOT NULL, + b int, + PRIMARY KEY (groups_20, groups_20_2) +); + +CREATE TABLE t2 (a int, b int, index(a)); + +--disable_result_log +INSERT INTO t1 SELECT seq/1000, seq+1, seq from seq_1_to_10000; + +INSERT INTO t2 SELECT seq, seq from seq_1_to_1000; + +ANALYZE TABLE t1, t2; +--enable_result_log + +EXPLAIN +SELECT + a, + SUM(b) +FROM ( + SELECT + groups_20 + FROM t1 + GROUP BY groups_20 + HAVING COUNT(*) != 1000 +) DT +JOIN t2 ON a = groups_20 +GROUP BY a; + +DROP TABLE t1, t2; \ No newline at end of file diff --git a/mysql-test/main/opt_hints_split_materialized.result b/mysql-test/main/opt_hints_split_materialized.result index 798b3b53c7cd9..b1e656b3e606d 100644 --- a/mysql-test/main/opt_hints_split_materialized.result +++ b/mysql-test/main/opt_hints_split_materialized.result @@ -763,7 +763,7 @@ one_k T1, (select grp, count(*) from t1000 group by grp) TBL where TBL.grp=T1.a; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY T1 ALL NULL NULL NULL NULL 1000 Using where 1 PRIMARY ref key0 key0 5 test.T1.a 1 -2 DERIVED t1000 index grp grp 5 NULL 1000 Using index; Using temporary; Using filesort +2 DERIVED t1000 index grp grp 5 NULL 1000 Using index explain select /*+ SPLIT_MATERIALIZED(TBL) */ * from @@ -771,7 +771,7 @@ one_k T1, (select grp, count(*) from t1000 group by grp) TBL where TBL.grp=T1.a; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY ALL NULL NULL NULL NULL 100 1 PRIMARY T1 ALL NULL NULL NULL NULL 1000 Using where; Using join buffer (flat, BNL join) -2 DERIVED t1000 index grp grp 5 NULL 1000 Using index; Using temporary; Using filesort +2 DERIVED t1000 index grp grp 5 NULL 1000 Using index drop table one_k, t1000; # # end case 4 diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b8fbbe2f62f58..e401080bc5595 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -32727,9 +32727,24 @@ void JOIN::save_query_plan(Join_plan_state *save_to) for (uint i= 0; i < table_count; i++) { + uint num_keys= join_tab[i].table->s->keys; save_to->join_tab_keyuse[i]= join_tab[i].keyuse; join_tab[i].keyuse= NULL; save_to->join_tab_checked_keys[i]= join_tab[i].checked_keys; + if (num_keys > 0) + { + if (!save_to->const_key_parts[i]) + { + save_to->const_key_parts[i]= + (key_part_map *) thd->calloc(sizeof(key_part_map) * num_keys); + } + + if (save_to->const_key_parts[i]) + { + memcpy(save_to->const_key_parts[i], join_tab[i].table->const_key_parts, + sizeof(key_part_map) * num_keys); + } + } join_tab[i].checked_keys.clear_all(); } memcpy((uchar*) save_to->best_positions, (uchar*) best_positions, @@ -32777,6 +32792,9 @@ void JOIN::restore_query_plan(Join_plan_state *restore_from) { join_tab[i].keyuse= restore_from->join_tab_keyuse[i]; join_tab[i].checked_keys= restore_from->join_tab_checked_keys[i]; + memcpy(join_tab[i].table->const_key_parts, + restore_from->const_key_parts[i], + sizeof(key_part_map) * join_tab[i].table->s->keys); } memcpy((uchar*) best_positions, (uchar*) restore_from->best_positions, diff --git a/sql/sql_select.h b/sql/sql_select.h index 1b001494e2fa2..2e540a5203e5e 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1235,6 +1235,8 @@ class JOIN :public Sql_alloc KEYUSE **join_tab_keyuse; /* Copies of JOIN_TAB::checked_keys for each JOIN_TAB. */ key_map *join_tab_checked_keys; + /* Copies of TABLE::key_part_map for each JOIN_TAB->TABLE */ + key_part_map **const_key_parts; SJ_MATERIALIZATION_INFO **sj_mat_info; my_bool error; public: @@ -1244,7 +1246,7 @@ class JOIN :public Sql_alloc keyuse.buffer= NULL; keyuse.malloc_flags= 0; best_positions= 0; /* To detect errors */ - error= my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME), + error= my_multi_malloc(PSI_INSTRUMENT_ME, MYF(MY_WME | MY_ZEROFILL), &best_positions, sizeof(*best_positions) * (tables + 1), &join_tab_keyuse, @@ -1253,6 +1255,8 @@ class JOIN :public Sql_alloc sizeof(*join_tab_checked_keys) * tables, &sj_mat_info, sizeof(sj_mat_info) * tables, + &const_key_parts, + sizeof(*const_key_parts) * tables, NullS) == 0; } Join_plan_state(JOIN *join);