Commit b729896d authored by Monty's avatar Monty

MDEV-28073 Query performance degradation in newer MariaDB versions when using many tables

The issue was that best_extension_by_limited_search() had to go through
too many plans with the same cost as there where many EQ_REF tables.

Fixed by shortcutting EQ_REF (AND REF) when the result only contains one
row. This got the optimization time down from hours to sub seconds.

The only known downside with this patch is that in some cases a table
with ref and 1 record may be used before on EQ_REF table. The faster
optimzation phase should compensate for this.
parent f7dd8799
This diff is collapsed.
......@@ -221,8 +221,7 @@ explain select * from t1 where a=1 or b=1 {
}
},
"rows_for_plan": 2,
"cost_for_plan": 2.884903732,
"estimated_join_cardinality": 2
"cost_for_plan": 2.884903732
}
]
},
......
......@@ -227,7 +227,7 @@ explain select * from t1 where pk1 != 0 and key1 = 1 {
},
"rows_for_plan": 1,
"cost_for_plan": 1.325146475,
"estimated_join_cardinality": 1
"pruned_by_hanging_leaf": true
}
]
},
......
......@@ -107,8 +107,7 @@ select * from db1.t1 {
}
},
"rows_for_plan": 3,
"cost_for_plan": 2.605126953,
"estimated_join_cardinality": 3
"cost_for_plan": 2.605126953
}
]
},
......@@ -229,8 +228,7 @@ select * from db1.v1 {
}
},
"rows_for_plan": 3,
"cost_for_plan": 2.605126953,
"estimated_join_cardinality": 3
"cost_for_plan": 2.605126953
}
]
},
......
......@@ -466,11 +466,11 @@ where t0.a in ( select t1.a from t1,t2 where t2.a=t0.a and
t1.b=t2.b);
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY t0 ALL NULL NULL NULL NULL 5 100.00 Using where
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Using where; FirstMatch(t2)
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Start temporary
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00 Using where; End temporary
Warnings:
Note 1276 Field or reference 'test.t0.a' of SELECT #2 was resolved in SELECT #1
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`
update t1 set a=3, b=11 where a=4;
update t2 set b=11 where a=3;
select * from t0 where t0.a in
......
......@@ -477,11 +477,11 @@ where t0.a in ( select t1.a from t1,t2 where t2.a=t0.a and
t1.b=t2.b);
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY t0 ALL NULL NULL NULL NULL 5 100.00 Using where
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00 Using join buffer (flat, BKA join); Key-ordered Rowid-ordered scan
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Using where; FirstMatch(t2); Using join buffer (incremental, BKA join); Key-ordered Rowid-ordered scan
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Start temporary; Using join buffer (flat, BKA join); Key-ordered Rowid-ordered scan
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00 Using where; End temporary; Using join buffer (incremental, BKA join); Key-ordered Rowid-ordered scan
Warnings:
Note 1276 Field or reference 'test.t0.a' of SELECT #2 was resolved in SELECT #1
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`
update t1 set a=3, b=11 where a=4;
update t2 set b=11 where a=3;
# Not anymore:
......
......@@ -468,11 +468,11 @@ where t0.a in ( select t1.a from t1,t2 where t2.a=t0.a and
t1.b=t2.b);
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY t0 ALL NULL NULL NULL NULL 5 100.00 Using where
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Using where; FirstMatch(t2)
1 PRIMARY t1 ref a a 5 test.t0.a 1 100.00 Start temporary
1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t0.a 1 100.00 Using where; End temporary
Warnings:
Note 1276 Field or reference 'test.t0.a' of SELECT #2 was resolved in SELECT #1
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t1`.`b` = `test`.`t2`.`b`
Note 1003 select `test`.`t0`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1`) join `test`.`t0` where `test`.`t1`.`a` = `test`.`t0`.`a` and `test`.`t2`.`a` = `test`.`t0`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`
update t1 set a=3, b=11 where a=4;
update t2 set b=11 where a=3;
select * from t0 where t0.a in
......
......@@ -111,7 +111,14 @@ static void optimize_straight_join(JOIN *join, table_map join_tables);
static bool greedy_search(JOIN *join, table_map remaining_tables,
uint depth, uint prune_level,
uint use_cond_selectivity);
static bool best_extension_by_limited_search(JOIN *join,
enum enum_best_search {
SEARCH_ABORT= -2,
SEARCH_ERROR= -1,
SEARCH_OK= 0,
SEARCH_FOUND_EDGE=1
};
static enum_best_search
best_extension_by_limited_search(JOIN *join,
table_map remaining_tables,
uint idx, double record_count,
double read_time, uint depth,
......@@ -8446,6 +8453,7 @@ best_access_path(JOIN *join,
pos->records_read= records;
pos->read_time= best;
pos->key= best_key;
pos->type= best_type;
pos->table= s;
pos->ref_depend_map= best_ref_depends_map;
pos->loosescan_picker.loosescan_key= MAX_KEY;
......@@ -9101,9 +9109,12 @@ greedy_search(JOIN *join,
do {
/* Find the extension of the current QEP with the lowest cost */
join->best_read= DBL_MAX;
if (best_extension_by_limited_search(join, remaining_tables, idx, record_count,
read_time, search_depth, prune_level,
use_cond_selectivity))
if ((int) best_extension_by_limited_search(join, remaining_tables, idx,
record_count,
read_time, search_depth,
prune_level,
use_cond_selectivity) <
(int) SEARCH_OK)
DBUG_RETURN(TRUE);
/*
'best_read < DBL_MAX' means that optimizer managed to find
......@@ -9739,6 +9750,28 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
}
/*
Check if the table is an EQ_REF or similar table and there is no cost
to gain by moveing it to a later stage.
We call such a table a edge table (or hanging leaf) as it will read at
most one row and will not add to the number of row combinations in the join.
*/
static inline enum_best_search
check_if_edge_table(POSITION *pos,
double pushdown_cond_selectivity)
{
if ((pos->type == JT_EQ_REF ||
(pos->type == JT_REF &&
pos->records_read == 1 &&
!pos->range_rowid_filter_info)) &&
pushdown_cond_selectivity >= 0.999)
return SEARCH_FOUND_EDGE;
return SEARCH_OK;
}
/**
Find a good, possibly optimal, query execution plan (QEP) by a possibly
exhaustive search.
......@@ -9853,12 +9886,17 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
pushed to a table should be taken into account
@retval
FALSE ok
enum_best_search::SEARCH_OK All fine
@retval
TRUE Fatal error
enum_best_search::SEARCH_FOUND_EDGE All remaning tables are edge tables
@retval
enum_best_search::SEARCH_ABORT Killed by user
@retval
enum_best_search::SEARCH_ERROR Fatal error
*/
static bool
static enum_best_search
best_extension_by_limited_search(JOIN *join,
table_map remaining_tables,
uint idx,
......@@ -9868,9 +9906,17 @@ best_extension_by_limited_search(JOIN *join,
uint prune_level,
uint use_cond_selectivity)
{
DBUG_ENTER("best_extension_by_limited_search");
THD *thd= join->thd;
/*
'join' is a partial plan with lower cost than the best plan so far,
so continue expanding it further with the tables in 'remaining_tables'.
*/
JOIN_TAB *s;
double best_record_count= DBL_MAX;
double best_read_time= DBL_MAX;
bool disable_jbuf= join->thd->variables.join_cache_level == 0;
enum_best_search best_res;
DBUG_ENTER("best_extension_by_limited_search");
DBUG_EXECUTE_IF("show_explain_probe_best_ext_lim_search",
if (dbug_user_var_equals_int(thd,
......@@ -9880,19 +9926,7 @@ best_extension_by_limited_search(JOIN *join,
);
if (unlikely(thd->check_killed())) // Abort
DBUG_RETURN(TRUE);
DBUG_EXECUTE("opt", print_plan(join, idx, read_time, record_count, idx,
"SOFAR:"););
/*
'join' is a partial plan with lower cost than the best plan so far,
so continue expanding it further with the tables in 'remaining_tables'.
*/
JOIN_TAB *s;
double best_record_count= DBL_MAX;
double best_read_time= DBL_MAX;
bool disable_jbuf= join->thd->variables.join_cache_level == 0;
DBUG_RETURN(SEARCH_ABORT);
DBUG_EXECUTE("opt", print_plan(join, idx, record_count, read_time, read_time,
"part_plan"););
......@@ -9914,9 +9948,11 @@ best_extension_by_limited_search(JOIN *join,
(!idx || !check_interleaving_with_nj(s)))
{
double current_record_count, current_read_time;
double partial_join_cardinality;
POSITION *position= join->positions + idx;
POSITION loose_scan_pos;
Json_writer_object trace_one_table(thd);
if (unlikely(thd->trace_started()))
{
trace_plan_prefix(join, idx, remaining_tables);
......@@ -9924,7 +9960,6 @@ best_extension_by_limited_search(JOIN *join,
}
/* Find the best access method from 's' to the current partial plan */
POSITION loose_scan_pos;
best_access_path(join, s, remaining_tables, join->positions, idx,
disable_jbuf, record_count, position, &loose_scan_pos);
......@@ -10003,28 +10038,47 @@ best_extension_by_limited_search(JOIN *join,
~real_table_bit);
join->positions[idx].cond_selectivity= pushdown_cond_selectivity;
if (unlikely(thd->trace_started()) && pushdown_cond_selectivity < 1.0)
partial_join_cardinality= (current_record_count *
pushdown_cond_selectivity);
if (unlikely(thd->trace_started()))
{
if (pushdown_cond_selectivity < 1.0)
{
trace_one_table.add("selectivity", pushdown_cond_selectivity);
trace_one_table.add("estimated_join_cardinality",
partial_join_cardinality);
}
}
double partial_join_cardinality= current_record_count *
pushdown_cond_selectivity;
if ( (search_depth > 1) && (remaining_tables & ~real_table_bit) & allowed_tables )
{ /* Recursively expand the current partial plan */
if ((search_depth > 1) && (remaining_tables & ~real_table_bit) &
allowed_tables)
{
/* Recursively expand the current partial plan */
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
Json_writer_array trace_rest(thd, "rest_of_plan");
if (best_extension_by_limited_search(join,
remaining_tables & ~real_table_bit,
best_res=
best_extension_by_limited_search(join,
remaining_tables &
~real_table_bit,
idx + 1,
partial_join_cardinality,
current_read_time,
search_depth - 1,
prune_level,
use_cond_selectivity))
DBUG_RETURN(TRUE);
use_cond_selectivity);
if ((int) best_res < (int) SEARCH_OK)
DBUG_RETURN(best_res); // Abort
swap_variables(JOIN_TAB*, join->best_ref[idx], *pos);
if (best_res == SEARCH_FOUND_EDGE &&
check_if_edge_table(join->positions+ idx,
pushdown_cond_selectivity) !=
SEARCH_FOUND_EDGE)
best_res= SEARCH_OK;
}
else
{ /*
{
/*
'join' is either the best partial QEP with 'search_depth' relations,
or the best complete QEP so far, whichever is smaller.
*/
......@@ -10040,8 +10094,6 @@ best_extension_by_limited_search(JOIN *join,
trace_one_table.add("cost_for_sorting", current_record_count);
current_read_time= COST_ADD(current_read_time, current_record_count);
}
trace_one_table.add("estimated_join_cardinality",
partial_join_cardinality);
if (current_read_time < join->best_read)
{
memcpy((uchar*) join->best_positions, (uchar*) join->positions,
......@@ -10054,12 +10106,19 @@ best_extension_by_limited_search(JOIN *join,
read_time,
current_read_time,
"full_plan"););
best_res= check_if_edge_table(join->positions + idx,
pushdown_cond_selectivity);
}
restore_prev_nj_state(s);
restore_prev_sj_state(remaining_tables, s, idx);
if (best_res == SEARCH_FOUND_EDGE)
{
trace_one_table.add("pruned_by_hanging_leaf", true);
DBUG_RETURN(best_res);
}
}
DBUG_RETURN(FALSE);
}
DBUG_RETURN(SEARCH_OK);
}
......
......@@ -989,6 +989,8 @@ class POSITION
*/
enum sj_strategy_enum sj_strategy;
/* Type of join (EQ_REF, REF etc) */
enum join_type type;
/*
Valid only after fix_semijoin_strategies_for_picked_join_order() call:
if sj_strategy!=SJ_OPT_NONE, this is the number of subsequent tables that
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment