mirror of
https://github.com/MariaDB/server.git
synced 2025-08-07 00:04:31 +03:00
Merge branch '10.6' into 10.8
This commit is contained in:
@@ -5811,7 +5811,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
|
||||
/*
|
||||
Perform range analysis if there are keys it could use (1).
|
||||
Don't do range analysis for materialized subqueries (2).
|
||||
Don't do range analysis for materialized derived tables (3)
|
||||
Don't do range analysis for materialized derived tables/views (3)
|
||||
*/
|
||||
if ((!s->const_keys.is_clear_all() ||
|
||||
!bitmap_is_clear_all(&s->table->cond_set)) && // (1)
|
||||
@@ -7594,20 +7594,28 @@ void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
|
||||
Estimate how many records we will get if we read just this table and apply
|
||||
a part of WHERE that can be checked for it.
|
||||
|
||||
@param s Current JOIN_TAB
|
||||
@param use_cond_selectivity Value of optimizer_use_condition_selectivity.
|
||||
If > 1 then use table->cond_selecitivity.
|
||||
@param force_estiamte Set to 1 if we should not call
|
||||
use_found_constraint. To be deleted in 11.0
|
||||
@return 0.0 No matching rows
|
||||
@return >= 1.0 Number of expected matching rows
|
||||
|
||||
@detail
|
||||
Estimate how many records we will get if we
|
||||
- read the given table with its "independent" access method (either quick
|
||||
select or full table/index scan),
|
||||
- apply the part of WHERE that refers only to this table.
|
||||
|
||||
@seealso
|
||||
@see also
|
||||
table_cond_selectivity() produces selectivity of condition that is checked
|
||||
after joining rows from this table to rows from preceding tables.
|
||||
*/
|
||||
|
||||
inline
|
||||
double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint,
|
||||
uint use_cond_selectivity)
|
||||
static double apply_selectivity_for_table(JOIN_TAB *s,
|
||||
uint use_cond_selectivity,
|
||||
bool *force_estimate)
|
||||
{
|
||||
ha_rows records;
|
||||
double dbl_records;
|
||||
@@ -7618,34 +7626,47 @@ double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint,
|
||||
double sel= table->cond_selectivity;
|
||||
double table_records= rows2double(s->records);
|
||||
dbl_records= table_records * sel;
|
||||
*force_estimate= 1; // Don't call use_found_constraint()
|
||||
return dbl_records;
|
||||
}
|
||||
|
||||
records = s->found_records;
|
||||
|
||||
/*
|
||||
If there is a filtering condition on the table (i.e. ref analyzer found
|
||||
at least one "table.keyXpartY= exprZ", where exprZ refers only to tables
|
||||
preceding this table in the join order we're now considering), then
|
||||
assume that 25% of the rows will be filtered out by this condition.
|
||||
|
||||
This heuristic is supposed to force tables used in exprZ to be before
|
||||
this table in join order.
|
||||
If applicable, get a more accurate estimate.
|
||||
*/
|
||||
if (with_found_constraint)
|
||||
records-= records/4;
|
||||
|
||||
/*
|
||||
If applicable, get a more accurate estimate. Don't use the two
|
||||
heuristics at once.
|
||||
*/
|
||||
DBUG_ASSERT(s->table->opt_range_condition_rows <= s->found_records);
|
||||
if (s->table->opt_range_condition_rows != s->found_records)
|
||||
{
|
||||
*force_estimate= 1; // Don't call use_found_constraint()
|
||||
records= s->table->opt_range_condition_rows;
|
||||
}
|
||||
|
||||
dbl_records= (double)records;
|
||||
return dbl_records;
|
||||
}
|
||||
|
||||
/*
|
||||
Take into account that the table's WHERE clause has conditions on earlier
|
||||
tables that can reduce the number of accepted rows.
|
||||
|
||||
@param records Number of original rows (after selectivity)
|
||||
|
||||
If there is a filtering condition on the table (i.e. ref analyzer found
|
||||
at least one "table.keyXpartY= exprZ", where exprZ refers only to tables
|
||||
preceding this table in the join order we're now considering), then
|
||||
assume that 25% of the rows will be filtered out by this condition.
|
||||
|
||||
This heuristic is supposed to force tables used in exprZ to be before
|
||||
this table in join order.
|
||||
*/
|
||||
|
||||
inline double use_found_constraint(double records)
|
||||
{
|
||||
records-= records/4;
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Calculate the cost of reading a set of rows trough an index
|
||||
@@ -7702,6 +7723,92 @@ double adjust_quick_cost(double quick_cost, ha_rows records)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@brief
|
||||
Compute the fanout of hash join operation using EITS data
|
||||
*/
|
||||
|
||||
double hash_join_fanout(JOIN *join, JOIN_TAB *s, table_map remaining_tables,
|
||||
double rnd_records, KEYUSE *hj_start_key,
|
||||
bool *stats_found)
|
||||
{
|
||||
THD *thd= join->thd;
|
||||
/*
|
||||
Before doing the hash join, we will scan the table and apply the local part
|
||||
of the WHERE condition. This will produce rnd_records.
|
||||
|
||||
The EITS statistics describes the entire table. Calling
|
||||
|
||||
table->field[N]->get_avg_frequency()
|
||||
|
||||
produces average #rows in the table with some value.
|
||||
|
||||
What happens if we filter out rows so that rnd_records rows are left?
|
||||
Something between the two outcomes:
|
||||
A. filtering removes a fraction of rows for each value:
|
||||
avg_frequency=avg_frequency * condition_selectivity
|
||||
|
||||
B. filtering removes entire groups of rows with the same value, but
|
||||
the remaining groups remain of the same size.
|
||||
|
||||
We make pessimistic assumption and assume B.
|
||||
We also handle an edge case: if rnd_records is less than avg_frequency,
|
||||
assume we'll get rnd_records rows with the same value, and return
|
||||
rnd_records as the fanout estimate.
|
||||
*/
|
||||
double min_freq= rnd_records;
|
||||
|
||||
Json_writer_object trace_obj(thd, "hash_join_cardinality");
|
||||
/*
|
||||
There can be multiple KEYUSE referring to same or different columns
|
||||
|
||||
KEYUSE(tbl.col1 = ...)
|
||||
KEYUSE(tbl.col1 = ...)
|
||||
KEYUSE(tbl.col2 = ...)
|
||||
|
||||
Hash join code can use multiple columns: (col1, col2) for joining.
|
||||
We need n_distinct({col1, col2}).
|
||||
|
||||
EITS only has statistics on individual columns: n_distinct(col1),
|
||||
n_distinct(col2).
|
||||
|
||||
Our current solution is to be very conservative and use selectivity
|
||||
of one column with the lowest avg_frequency.
|
||||
|
||||
In the future, we should an approach that cautiosly takes into account
|
||||
multiple KEYUSEs either multiply by number of equalities or by sqrt
|
||||
of the second most selective equality.
|
||||
*/
|
||||
Json_writer_array trace_arr(thd, "hash_join_columns");
|
||||
for (KEYUSE *keyuse= hj_start_key;
|
||||
keyuse->table == s->table && is_hash_join_key_no(keyuse->key);
|
||||
keyuse++)
|
||||
{
|
||||
if (!(remaining_tables & keyuse->used_tables) &&
|
||||
(!keyuse->validity_ref || *keyuse->validity_ref) &&
|
||||
s->access_from_tables_is_allowed(keyuse->used_tables,
|
||||
join->sjm_lookup_tables))
|
||||
{
|
||||
Field *field= s->table->field[keyuse->keypart];
|
||||
if (is_eits_usable(field))
|
||||
{
|
||||
double freq= field->read_stats->get_avg_frequency();
|
||||
|
||||
Json_writer_object trace_field(thd);
|
||||
trace_field.add("field",field->field_name.str).
|
||||
add("avg_frequency", freq);
|
||||
if (freq < min_freq)
|
||||
min_freq= freq;
|
||||
*stats_found= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
trace_arr.end();
|
||||
trace_obj.add("rows", min_freq);
|
||||
return min_freq;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Find the best access path for an extension of a partial execution
|
||||
plan and add this path to the plan.
|
||||
@@ -8394,10 +8501,44 @@ best_access_path(JOIN *join,
|
||||
(!(s->table->map & join->outer_join) ||
|
||||
join->allowed_outer_join_with_cache)) // (2)
|
||||
{
|
||||
double join_sel= 0.1;
|
||||
double fanout;
|
||||
double join_sel;
|
||||
bool stats_found= 0, force_estimate= 0;
|
||||
Json_writer_object trace_access_hash(thd);
|
||||
trace_access_hash.add("type", "hash");
|
||||
trace_access_hash.add("index", "hj-key");
|
||||
/* Estimate the cost of the hash join access to the table */
|
||||
double rnd_records= matching_candidates_in_table(s, found_constraint,
|
||||
use_cond_selectivity);
|
||||
double rnd_records= apply_selectivity_for_table(s, use_cond_selectivity,
|
||||
&force_estimate);
|
||||
|
||||
DBUG_ASSERT(hj_start_key);
|
||||
if (optimizer_flag(thd, OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY))
|
||||
{
|
||||
/*
|
||||
Starting from this point, rnd_records should not be used anymore.
|
||||
Use "fanout" for an estimate of # matching records.
|
||||
*/
|
||||
fanout= hash_join_fanout(join, s, remaining_tables, rnd_records,
|
||||
hj_start_key, &stats_found);
|
||||
join_sel= 1.0; // Don't do the "10% heuristic"
|
||||
}
|
||||
if (!stats_found)
|
||||
{
|
||||
/*
|
||||
No OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY or no field statistics
|
||||
found.
|
||||
|
||||
Take into account if there is non constant constraints used with
|
||||
earlier tables in the where expression.
|
||||
If yes, this will set fanout to rnd_records/4.
|
||||
We estimate that there will be HASH_FANOUT (10%)
|
||||
hash matches / row.
|
||||
*/
|
||||
if (found_constraint && !force_estimate)
|
||||
rnd_records= use_found_constraint(rnd_records);
|
||||
fanout= rnd_records;
|
||||
join_sel= 0.1;
|
||||
}
|
||||
|
||||
tmp= s->quick ? s->quick->read_time : s->scan_time();
|
||||
double cmp_time= (s->records - rnd_records)/TIME_FOR_COMPARE;
|
||||
@@ -8409,20 +8550,37 @@ best_access_path(JOIN *join,
|
||||
record_count /
|
||||
(double) thd->variables.join_buff_size));
|
||||
tmp= COST_MULT(tmp, refills);
|
||||
best_time= COST_ADD(tmp,
|
||||
COST_MULT((record_count*join_sel) / TIME_FOR_COMPARE,
|
||||
rnd_records));
|
||||
|
||||
// Add cost of reading/writing the join buffer
|
||||
if (optimizer_flag(thd, OPTIMIZER_SWITCH_HASH_JOIN_CARDINALITY))
|
||||
{
|
||||
/* Set it to be 1/10th of TIME_FOR_COMPARE */
|
||||
double row_copy_cost= 1.0 / (10*TIME_FOR_COMPARE);
|
||||
double join_buffer_operations=
|
||||
COST_ADD(
|
||||
COST_MULT(record_count, row_copy_cost),
|
||||
COST_MULT(record_count, fanout * (idx - join->const_tables))
|
||||
);
|
||||
double jbuf_use_cost= row_copy_cost * join_buffer_operations;
|
||||
trace_access_hash.add("jbuf_use_cost", jbuf_use_cost);
|
||||
tmp= COST_ADD(tmp, jbuf_use_cost);
|
||||
}
|
||||
|
||||
double where_cost= COST_MULT((fanout*join_sel) / TIME_FOR_COMPARE,
|
||||
record_count);
|
||||
trace_access_hash.add("extra_cond_check_cost", where_cost);
|
||||
|
||||
best_time= COST_ADD(tmp, where_cost);
|
||||
|
||||
best= tmp;
|
||||
records= rnd_records;
|
||||
records= fanout;
|
||||
best_key= hj_start_key;
|
||||
best_ref_depends_map= 0;
|
||||
best_uses_jbuf= TRUE;
|
||||
best_filter= 0;
|
||||
best_type= JT_HASH;
|
||||
Json_writer_object trace_access_hash(thd);
|
||||
trace_access_hash.add("type", "hash");
|
||||
trace_access_hash.add("index", "hj-key");
|
||||
trace_access_hash.add("rnd_records", rnd_records);
|
||||
trace_access_hash.add("records", records);
|
||||
trace_access_hash.add("cost", best);
|
||||
trace_access_hash.add("chosen", true);
|
||||
}
|
||||
@@ -8474,9 +8632,13 @@ best_access_path(JOIN *join,
|
||||
!(s->table->force_index && best_key && !s->quick) && // (4)
|
||||
!(best_key && s->table->pos_in_table_list->jtbm_subselect)) // (5)
|
||||
{ // Check full join
|
||||
double rnd_records= matching_candidates_in_table(s, found_constraint,
|
||||
use_cond_selectivity);
|
||||
|
||||
bool force_estimate= 0;
|
||||
double rnd_records= apply_selectivity_for_table(s,
|
||||
use_cond_selectivity,
|
||||
&force_estimate);
|
||||
rnd_records= ((found_constraint && !force_estimate) ?
|
||||
use_found_constraint(rnd_records) :
|
||||
rnd_records);
|
||||
/*
|
||||
Range optimizer never proposes a RANGE if it isn't better
|
||||
than FULL: so if RANGE is present, it's always preferred to FULL.
|
||||
@@ -9693,7 +9855,7 @@ double table_multi_eq_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
|
||||
with previous tables.
|
||||
|
||||
For quick selects and full table scans, selectivity of COND(this_table)
|
||||
is accounted for in matching_candidates_in_table(). Here, we only count
|
||||
is accounted for in apply_selectivity_for_table(). Here, we only count
|
||||
selectivity of COND(this_table, previous_tables).
|
||||
|
||||
For other access methods, we need to calculate selectivity of the whole
|
||||
@@ -9895,7 +10057,7 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
|
||||
/*
|
||||
The table is accessed with full table scan, or quick select.
|
||||
Selectivity of COND(table) is already accounted for in
|
||||
matching_candidates_in_table().
|
||||
apply_selectivity_for_table().
|
||||
*/
|
||||
sel= 1;
|
||||
}
|
||||
@@ -24900,7 +25062,7 @@ JOIN_TAB::remove_duplicates()
|
||||
if (!(sortorder= (SORT_FIELD*) my_malloc(PSI_INSTRUMENT_ME,
|
||||
(fields->elements+1) *
|
||||
sizeof(SORT_FIELD),
|
||||
MYF(MY_WME))))
|
||||
MYF(MY_WME | MY_ZEROFILL))))
|
||||
DBUG_RETURN(TRUE);
|
||||
|
||||
/* Calculate how many saved fields there is in list */
|
||||
@@ -24919,7 +25081,6 @@ JOIN_TAB::remove_duplicates()
|
||||
else
|
||||
{
|
||||
/* Item is not stored in temporary table, remember it */
|
||||
sorder->field= 0; // Safety, not used
|
||||
sorder->item= item;
|
||||
/* Calculate sorder->length */
|
||||
item->type_handler()->sort_length(thd, item, sorder);
|
||||
@@ -28733,7 +28894,7 @@ void st_select_lex::print_item_list(THD *thd, String *str,
|
||||
outer_select() can not be used here because it is for name resolution
|
||||
and will return NULL at any end of name resolution chain (view/derived)
|
||||
*/
|
||||
bool top_level= (get_master() == &thd->lex->unit);
|
||||
bool top_level= is_query_topmost(thd);
|
||||
List_iterator_fast<Item> it(item_list);
|
||||
Item *item;
|
||||
while ((item= it++))
|
||||
@@ -28840,7 +29001,7 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type)
|
||||
return;
|
||||
}
|
||||
|
||||
bool top_level= (get_master() == &thd->lex->unit);
|
||||
bool top_level= is_query_topmost(thd);
|
||||
enum explainable_cmd_type sel_type= SELECT_CMD;
|
||||
if (top_level)
|
||||
sel_type= get_explainable_cmd_type(thd);
|
||||
|
Reference in New Issue
Block a user