diff --git a/mysql-test/main/join_cache.result b/mysql-test/main/join_cache.result index c07a8b1bf6b..49f3b0910fa 100644 --- a/mysql-test/main/join_cache.result +++ b/mysql-test/main/join_cache.result @@ -6396,5 +6396,27 @@ b b d c c 10 NULL NULL NULL NULL DROP TABLE t1,t2,t3,t4; # +# MDEV-21102: Server crashes in JOIN_CACHE::write_record_data upon EXPLAIN with subqueries and constant tables +# +CREATE TABLE t1 (a int, b int) ENGINE=MyISAM; +CREATE TABLE t2 (c int, d int) ENGINE=MyISAM; +INSERT INTO t2 VALUES (1,10); +CREATE TABLE t3 (e int, key (e)) ENGINE=MyISAM; +INSERT INTO t3 VALUES (2),(3); +# Must not crash, must use join buffer in subquery +EXPLAIN +SELECT * FROM t1 +WHERE a > b OR a IN ( +SELECT c FROM t2 WHERE EXISTS ( +SELECT * FROM t3 t3a JOIN t3 t3b WHERE t3a.e < d +) +); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +2 DEPENDENT SUBQUERY t2 system NULL NULL NULL NULL 1 +3 SUBQUERY t3a index e e 5 NULL 2 Using where; Using index +3 SUBQUERY t3b index NULL e 5 NULL 2 Using index; Using join buffer (flat, BNL join) +DROP TABLE t1,t2,t3; +# # End of 10.4 tests # diff --git a/mysql-test/main/join_cache.test b/mysql-test/main/join_cache.test index 868fc3f7e8a..8d341c9e0fc 100644 --- a/mysql-test/main/join_cache.test +++ b/mysql-test/main/join_cache.test @@ -4305,6 +4305,27 @@ eval $q2; DROP TABLE t1,t2,t3,t4; +--echo # +--echo # MDEV-21102: Server crashes in JOIN_CACHE::write_record_data upon EXPLAIN with subqueries and constant tables +--echo # +CREATE TABLE t1 (a int, b int) ENGINE=MyISAM; + +CREATE TABLE t2 (c int, d int) ENGINE=MyISAM; +INSERT INTO t2 VALUES (1,10); + +CREATE TABLE t3 (e int, key (e)) ENGINE=MyISAM; +INSERT INTO t3 VALUES (2),(3); + +--echo # Must not crash, must use join buffer in subquery +EXPLAIN +SELECT * FROM t1 +WHERE a > b OR a IN ( + SELECT c FROM t2 WHERE EXISTS ( + SELECT * FROM t3 t3a JOIN t3 t3b WHERE t3a.e < d + ) +); +DROP TABLE t1,t2,t3; + --echo # --echo # End of 10.4 tests --echo # diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index a8824adbdd8..d39c85e6f76 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -562,6 +562,8 @@ void Item_subselect::recalc_used_tables(st_select_lex *new_parent, This measure is used instead of JOIN::read_time, because it is considered to be much more reliable than the cost estimate. + Note: the logic in this function must agree with JOIN::init_join_caches(). + @return true if the subquery is expensive @return false otherwise */ diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 41d7dd89f55..c33c2f029cd 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -1589,6 +1589,7 @@ bool JOIN_CACHE::put_record() { bool is_full; uchar *link= 0; + DBUG_ASSERT(!for_explain_only); if (prev_cache) link= prev_cache->get_curr_rec_link(); write_record_data(link, &is_full); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 9e8b8e4ebe0..efcd42be074 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1876,6 +1876,26 @@ JOIN::init_range_rowid_filters() int JOIN::init_join_caches() { + bool init_for_explain= false; + + /* + Can we use lightweight initalization mode just for EXPLAINs? We can if + we're certain that the optimizer will not execute the subquery. + The optimzier will not execute the subquery if it's too expensive. For + the exact criteria, see Item_subselect::is_expensive(). + Note that the subquery might be a UNION and we might not yet know if it is + expensive. + What we do know is that if this SELECT is too expensive, then the whole + subquery will be too expensive as well. + So, we can use lightweight initialization (init_for_explain=true) if this + SELECT examines more than @@expensive_subquery_limit rows. + */ + if ((select_options & SELECT_DESCRIBE) && + get_examined_rows() >= thd->variables.expensive_subquery_limit) + { + init_for_explain= true; + } + JOIN_TAB *tab; for (tab= first_linear_tab(this, WITH_BUSH_ROOTS, WITHOUT_CONST_TABLES); @@ -1895,7 +1915,8 @@ int JOIN::init_join_caches() { table->prepare_for_keyread(tab->index, table->read_set); } - if (tab->cache && tab->cache->init(select_options & SELECT_DESCRIBE)) + + if (tab->cache && tab->cache->init(init_for_explain)) revise_cache_usage(tab); else tab->remove_redundant_bnl_scan_conds();