1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-08 11:22:35 +03:00

MDEV-22535 TABLE::initialize_quick_structures() takes 0.5% in oltp_read_only

Fixed by:
- Make all quick_* variable allocated according to real number keys instead
  of MAX_KEY
- Store all the quick* items in separated allocated structure (OPT_RANGE)
- Ensure we don't access any quick* variable without first checking
  opt_range_keys.is_set().  Thanks to this, we don't need any
  pre-initialization of quick* variables anymore.

Some renames was done to use the new structure:
table->quick_keys                -> table->opt_range_keys
table->quick_rows[X]             -> table->opt_range[X].rows
table->quick_key_parts[X]        -> table->opt_range[X].key_parts
table->quick_costs[X]            -> table->opt_range[X].cost
table->quick_index_only_costs[X] -> table->opt_range[X].index_only_cost
table->quick_n_ranges[X]         -> table->opt_range[X].ranges
table->quick_condition_rows      -> table->opt_range_condition_rows

This patch should both decrease memory needed for TABLE objects
(3528 -> 984 + keyinfo) and increase performance, thanks to less
initializations per query, and more localized memory, thanks to the
opt_range structure.
This commit is contained in:
Monty
2020-06-26 01:47:33 +03:00
parent 5cbb18cb44
commit 6cee9b1953
8 changed files with 181 additions and 146 deletions

View File

@@ -2599,10 +2599,10 @@ static int fill_used_fields_bitmap(PARAM *param)
In the table struct the following information is updated: In the table struct the following information is updated:
quick_keys - Which keys can be used quick_keys - Which keys can be used
quick_rows - How many rows the key matches quick_rows - How many rows the key matches
quick_condition_rows - E(# rows that will satisfy the table condition) opt_range_condition_rows - E(# rows that will satisfy the table condition)
IMPLEMENTATION IMPLEMENTATION
quick_condition_rows value is obtained as follows: opt_range_condition_rows value is obtained as follows:
It is a minimum of E(#output rows) for all considered table access It is a minimum of E(#output rows) for all considered table access
methods (range and index_merge accesses over various indexes). methods (range and index_merge accesses over various indexes).
@@ -2626,7 +2626,7 @@ static int fill_used_fields_bitmap(PARAM *param)
which is currently produced. which is currently produced.
TODO TODO
* Change the value returned in quick_condition_rows from a pessimistic * Change the value returned in opt_range_condition_rows from a pessimistic
estimate to true E(#rows that satisfy table condition). estimate to true E(#rows that satisfy table condition).
(we can re-use some of E(#rows) calcuation code from (we can re-use some of E(#rows) calcuation code from
index_merge/intersection for this) index_merge/intersection for this)
@@ -2957,7 +2957,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
{ {
best_trp= intersect_trp; best_trp= intersect_trp;
best_read_time= best_trp->read_cost; best_read_time= best_trp->read_cost;
set_if_smaller(param.table->quick_condition_rows, set_if_smaller(param.table->opt_range_condition_rows,
intersect_trp->records); intersect_trp->records);
} }
} }
@@ -2977,7 +2977,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
{ {
new_conj_trp= get_best_disjunct_quick(&param, imerge, best_read_time); new_conj_trp= get_best_disjunct_quick(&param, imerge, best_read_time);
if (new_conj_trp) if (new_conj_trp)
set_if_smaller(param.table->quick_condition_rows, set_if_smaller(param.table->opt_range_condition_rows,
new_conj_trp->records); new_conj_trp->records);
if (new_conj_trp && if (new_conj_trp &&
(!best_conj_trp || (!best_conj_trp ||
@@ -3004,7 +3004,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
restore_nonrange_trees(&param, tree, backup_keys); restore_nonrange_trees(&param, tree, backup_keys);
if ((group_trp= get_best_group_min_max(&param, tree, read_time))) if ((group_trp= get_best_group_min_max(&param, tree, read_time)))
{ {
param.table->quick_condition_rows= MY_MIN(group_trp->records, param.table->opt_range_condition_rows= MY_MIN(group_trp->records,
head->stat_records()); head->stat_records());
Json_writer_object grp_summary(thd, "best_group_range_summary"); Json_writer_object grp_summary(thd, "best_group_range_summary");
@@ -3340,8 +3340,8 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond)
for (keynr= 0; keynr < table->s->keys; keynr++) for (keynr= 0; keynr < table->s->keys; keynr++)
{ {
if (table->quick_keys.is_set(keynr)) if (table->opt_range_keys.is_set(keynr))
set_if_bigger(max_quick_key_parts, table->quick_key_parts[keynr]); set_if_bigger(max_quick_key_parts, table->opt_range[keynr].key_parts);
} }
/* /*
@@ -3353,13 +3353,13 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond)
{ {
for (keynr= 0; keynr < table->s->keys; keynr++) for (keynr= 0; keynr < table->s->keys; keynr++)
{ {
if (table->quick_keys.is_set(keynr) && if (table->opt_range_keys.is_set(keynr) &&
table->quick_key_parts[keynr] == quick_key_parts) table->opt_range[keynr].key_parts == quick_key_parts)
{ {
uint i; uint i;
uint used_key_parts= table->quick_key_parts[keynr]; uint used_key_parts= table->opt_range[keynr].key_parts;
double quick_cond_selectivity= table->quick_rows[keynr] / double quick_cond_selectivity= (table->opt_range[keynr].rows /
table_records; table_records);
KEY *key_info= table->key_info + keynr; KEY *key_info= table->key_info + keynr;
KEY_PART_INFO* key_part= key_info->key_part; KEY_PART_INFO* key_part= key_info->key_part;
/* /*
@@ -5777,7 +5777,7 @@ bool prepare_search_best_index_intersect(PARAM *param,
continue; continue;
} }
cost= table->quick_index_only_costs[(*index_scan)->keynr]; cost= table->opt_range[(*index_scan)->keynr].index_only_cost;
idx_scan.add("cost", cost); idx_scan.add("cost", cost);
@@ -7188,7 +7188,7 @@ TRP_ROR_INTERSECT *get_best_ror_intersect(const PARAM *param, SEL_TREE *tree,
ha_rows best_rows = double2rows(intersect_best->out_rows); ha_rows best_rows = double2rows(intersect_best->out_rows);
if (!best_rows) if (!best_rows)
best_rows= 1; best_rows= 1;
set_if_smaller(param->table->quick_condition_rows, best_rows); set_if_smaller(param->table->opt_range_condition_rows, best_rows);
trp->records= best_rows; trp->records= best_rows;
trp->index_scan_costs= intersect_best->index_scan_costs; trp->index_scan_costs= intersect_best->index_scan_costs;
trp->cpk_scan= cpk_scan; trp->cpk_scan= cpk_scan;
@@ -7357,7 +7357,7 @@ TRP_ROR_INTERSECT *get_best_covering_ror_intersect(PARAM *param,
trp->read_cost= total_cost; trp->read_cost= total_cost;
trp->records= records; trp->records= records;
trp->cpk_scan= NULL; trp->cpk_scan= NULL;
set_if_smaller(param->table->quick_condition_rows, records); set_if_smaller(param->table->opt_range_condition_rows, records);
DBUG_PRINT("info", DBUG_PRINT("info",
("Returning covering ROR-intersect plan: cost %g, records %lu", ("Returning covering ROR-intersect plan: cost %g, records %lu",
@@ -11083,11 +11083,11 @@ void SEL_ARG::test_use_count(SEL_ARG *root)
about range scan we've evaluated. about range scan we've evaluated.
mrr_flags INOUT MRR access flags mrr_flags INOUT MRR access flags
cost OUT Scan cost cost OUT Scan cost
is_ror_scan is set to reflect if the key scan is a ROR (see
is_key_scan_ror function for more info)
NOTES NOTES
param->is_ror_scan is set to reflect if the key scan is a ROR (see param->table->opt_range*, param->range_count (and maybe others) are
is_key_scan_ror function for more info)
param->table->quick_*, param->range_count (and maybe others) are
updated with data of given key scan, see quick_range_seq_next for details. updated with data of given key scan, see quick_range_seq_next for details.
RETURN RETURN
@@ -11157,6 +11157,7 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only,
if (param->table->pos_in_table_list->is_non_derived()) if (param->table->pos_in_table_list->is_non_derived())
rows= file->multi_range_read_info_const(keynr, &seq_if, (void*)&seq, 0, rows= file->multi_range_read_info_const(keynr, &seq_if, (void*)&seq, 0,
bufsize, mrr_flags, cost); bufsize, mrr_flags, cost);
param->quick_rows[keynr]= rows;
if (rows != HA_POS_ERROR) if (rows != HA_POS_ERROR)
{ {
ha_rows table_records= param->table->stat_records(); ha_rows table_records= param->table->stat_records();
@@ -11164,30 +11165,30 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only,
{ {
/* /*
For any index the total number of records within all ranges For any index the total number of records within all ranges
cannot be be bigger than the number of records in the table cannot be be bigger than the number of records in the table.
This check is needed as sometimes that table statistics or range
estimates may be slightly out of sync.
*/ */
rows= table_records; rows= table_records;
set_if_bigger(rows, 1); set_if_bigger(rows, 1);
param->quick_rows[keynr]= rows;
} }
param->quick_rows[keynr]= rows;
param->possible_keys.set_bit(keynr); param->possible_keys.set_bit(keynr);
if (update_tbl_stats) if (update_tbl_stats)
{ {
param->table->quick_keys.set_bit(keynr); param->table->opt_range_keys.set_bit(keynr);
param->table->quick_key_parts[keynr]= param->max_key_parts; param->table->opt_range[keynr].key_parts= param->max_key_parts;
param->table->quick_n_ranges[keynr]= param->range_count; param->table->opt_range[keynr].ranges= param->range_count;
param->table->quick_condition_rows= param->table->opt_range_condition_rows=
MY_MIN(param->table->quick_condition_rows, rows); MY_MIN(param->table->opt_range_condition_rows, rows);
param->table->quick_rows[keynr]= rows; param->table->opt_range[keynr].rows= rows;
param->table->quick_costs[keynr]= cost->total_cost(); param->table->opt_range[keynr].cost= cost->total_cost();
if (param->table->file->is_clustering_key(keynr)) if (param->table->file->is_clustering_key(keynr))
param->table->quick_index_only_costs[keynr]= 0; param->table->opt_range[keynr].index_only_cost= 0;
else else
param->table->quick_index_only_costs[keynr]= cost->index_only_cost(); param->table->opt_range[keynr].index_only_cost= cost->index_only_cost();
} }
} }
else
param->quick_rows[keynr]= HA_POS_ERROR;
/* Figure out if the key scan is ROR (returns rows in ROWID order) or not */ /* Figure out if the key scan is ROR (returns rows in ROWID order) or not */
enum ha_key_alg key_alg= param->table->key_info[seq.real_keyno].algorithm; enum ha_key_alg key_alg= param->table->key_info[seq.real_keyno].algorithm;

View File

@@ -2500,7 +2500,7 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
double rows= 1.0; double rows= 1.0;
while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END) while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
rows= COST_MULT(rows, rows= COST_MULT(rows,
join->map2table[tableno]->table->quick_condition_rows); join->map2table[tableno]->table->opt_range_condition_rows);
sjm->rows= MY_MIN(sjm->rows, rows); sjm->rows= MY_MIN(sjm->rows, rows);
} }
memcpy((uchar*) sjm->positions, memcpy((uchar*) sjm->positions,

View File

@@ -110,10 +110,12 @@ Range_rowid_filter_cost_info::set_adjusted_gain_param(double access_cost_factor)
void Range_rowid_filter_cost_info::init(Rowid_filter_container_type cont_type, void Range_rowid_filter_cost_info::init(Rowid_filter_container_type cont_type,
TABLE *tab, uint idx) TABLE *tab, uint idx)
{ {
DBUG_ASSERT(tab->opt_range_keys.is_set(idx));
container_type= cont_type; container_type= cont_type;
table= tab; table= tab;
key_no= idx; key_no= idx;
est_elements= (ulonglong) (table->quick_rows[key_no]); est_elements= (ulonglong) table->opt_range[key_no].rows;
b= build_cost(container_type); b= build_cost(container_type);
selectivity= est_elements/((double) table->stat_records()); selectivity= est_elements/((double) table->stat_records());
a= avg_access_and_eval_gain_per_row(container_type); a= avg_access_and_eval_gain_per_row(container_type);
@@ -134,8 +136,9 @@ double
Range_rowid_filter_cost_info::build_cost(Rowid_filter_container_type cont_type) Range_rowid_filter_cost_info::build_cost(Rowid_filter_container_type cont_type)
{ {
double cost= 0; double cost= 0;
DBUG_ASSERT(table->opt_range_keys.is_set(key_no));
cost+= table->quick_index_only_costs[key_no]; cost+= table->opt_range[key_no].index_only_cost;
switch (cont_type) { switch (cont_type) {
@@ -345,7 +348,7 @@ void TABLE::init_cost_info_for_usable_range_rowid_filters(THD *thd)
uint key_no; uint key_no;
key_map usable_range_filter_keys; key_map usable_range_filter_keys;
usable_range_filter_keys.clear_all(); usable_range_filter_keys.clear_all();
key_map::Iterator it(quick_keys); key_map::Iterator it(opt_range_keys);
/* /*
From all indexes that can be used for range accesses select only such that From all indexes that can be used for range accesses select only such that
@@ -359,7 +362,7 @@ void TABLE::init_cost_info_for_usable_range_rowid_filters(THD *thd)
continue; continue;
if (file->is_clustering_key(key_no)) // !2 if (file->is_clustering_key(key_no)) // !2
continue; continue;
if (quick_rows[key_no] > if (opt_range[key_no].rows >
get_max_range_rowid_filter_elems_for_table(thd, this, get_max_range_rowid_filter_elems_for_table(thd, this,
SORTED_ARRAY_CONTAINER)) // !3 SORTED_ARRAY_CONTAINER)) // !3
continue; continue;

View File

@@ -492,7 +492,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
set_statistics_for_table(thd, table); set_statistics_for_table(thd, table);
table->covering_keys.clear_all(); table->covering_keys.clear_all();
table->quick_keys.clear_all(); // Can't use 'only index' table->opt_range_keys.clear_all();
select=make_select(table, 0, 0, conds, (SORT_INFO*) 0, 0, &error); select=make_select(table, 0, 0, conds, (SORT_INFO*) 0, 0, &error);
if (unlikely(error)) if (unlikely(error))
@@ -518,7 +518,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
} }
/* If running in safe sql mode, don't allow updates without keys */ /* If running in safe sql mode, don't allow updates without keys */
if (table->quick_keys.is_clear_all()) if (table->opt_range_keys.is_clear_all())
{ {
thd->set_status_no_index_used(); thd->set_status_no_index_used();
if (safe_update && !using_limit) if (safe_update && !using_limit)

View File

@@ -4897,7 +4897,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
table->file->print_error(error, MYF(0)); table->file->print_error(error, MYF(0));
goto error; goto error;
} }
table->quick_keys.clear_all(); table->opt_range_keys.clear_all();
table->intersect_keys.clear_all(); table->intersect_keys.clear_all();
table->reginfo.join_tab=s; table->reginfo.join_tab=s;
table->reginfo.not_exists_optimize=0; table->reginfo.not_exists_optimize=0;
@@ -4909,7 +4909,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
s->dependent= tables->dep_tables; s->dependent= tables->dep_tables;
if (tables->schema_table) if (tables->schema_table)
table->file->stats.records= table->used_stat_records= 2; table->file->stats.records= table->used_stat_records= 2;
table->quick_condition_rows= table->stat_records(); table->opt_range_condition_rows= table->stat_records();
s->on_expr_ref= &tables->on_expr; s->on_expr_ref= &tables->on_expr;
if (*s->on_expr_ref) if (*s->on_expr_ref)
@@ -5376,7 +5376,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
get_delayed_table_estimates(s->table, &s->records, &s->read_time, get_delayed_table_estimates(s->table, &s->records, &s->read_time,
&s->startup_cost); &s->startup_cost);
s->found_records= s->records; s->found_records= s->records;
table->quick_condition_rows=s->records; table->opt_range_condition_rows=s->records;
} }
else else
s->scan_time(); s->scan_time();
@@ -7217,8 +7217,8 @@ double matching_candidates_in_table(JOIN_TAB *s, bool with_found_constraint,
If applicable, get a more accurate estimate. Don't use the two If applicable, get a more accurate estimate. Don't use the two
heuristics at once. heuristics at once.
*/ */
if (s->table->quick_condition_rows != s->found_records) if (s->table->opt_range_condition_rows != s->found_records)
records= s->table->quick_condition_rows; records= s->table->opt_range_condition_rows;
dbl_records= (double)records; dbl_records= (double)records;
return dbl_records; return dbl_records;
@@ -7506,8 +7506,8 @@ best_access_path(JOIN *join,
type= JT_EQ_REF; type= JT_EQ_REF;
trace_access_idx.add("access_type", join_type_str[type]) trace_access_idx.add("access_type", join_type_str[type])
.add("index", keyinfo->name); .add("index", keyinfo->name);
if (!found_ref && table->quick_keys.is_set(key)) if (!found_ref && table->opt_range_keys.is_set(key))
tmp= adjust_quick_cost(table->quick_costs[key], 1); tmp= adjust_quick_cost(table->opt_range[key].cost, 1);
else else
tmp= table->file->avg_io_cost(); tmp= table->file->avg_io_cost();
tmp*= prev_record_reads(join_positions, idx, found_ref); tmp*= prev_record_reads(join_positions, idx, found_ref);
@@ -7537,12 +7537,12 @@ best_access_path(JOIN *join,
quick_cond is equivalent to ref_const_cond (if it was an quick_cond is equivalent to ref_const_cond (if it was an
empty interval we wouldn't have got here). empty interval we wouldn't have got here).
*/ */
if (table->quick_keys.is_set(key)) if (table->opt_range_keys.is_set(key))
{ {
records= (double) table->quick_rows[key]; records= (double) table->opt_range[key].rows;
trace_access_idx.add("used_range_estimates", true); trace_access_idx.add("used_range_estimates", true);
tmp= adjust_quick_cost(table->quick_costs[key], tmp= adjust_quick_cost(table->opt_range[key].cost,
table->quick_rows[key]); table->opt_range[key].rows);
goto got_cost; goto got_cost;
} }
else else
@@ -7575,19 +7575,19 @@ best_access_path(JOIN *join,
can make an adjustment is a special case of the criteria used can make an adjustment is a special case of the criteria used
in ReuseRangeEstimateForRef-3. in ReuseRangeEstimateForRef-3.
*/ */
if (table->quick_keys.is_set(key) && if (table->opt_range_keys.is_set(key) &&
(const_part & (const_part &
(((key_part_map)1 << table->quick_key_parts[key])-1)) == (((key_part_map)1 << table->opt_range[key].key_parts)-1)) ==
(((key_part_map)1 << table->quick_key_parts[key])-1) && (((key_part_map)1 << table->opt_range[key].key_parts)-1) &&
table->quick_n_ranges[key] == 1 && table->opt_range[key].ranges == 1 &&
records > (double) table->quick_rows[key]) records > (double) table->opt_range[key].rows)
{ {
records= (double) table->quick_rows[key]; records= (double) table->opt_range[key].rows;
trace_access_idx.add("used_range_estimates", true); trace_access_idx.add("used_range_estimates", true);
} }
else else
{ {
if (table->quick_keys.is_set(key)) if (table->opt_range_keys.is_set(key))
{ {
trace_access_idx.add("used_range_estimates",false) trace_access_idx.add("used_range_estimates",false)
.add("cause", .add("cause",
@@ -7661,13 +7661,13 @@ best_access_path(JOIN *join,
(C3) "range optimizer used (have ref_or_null?2:1) intervals" (C3) "range optimizer used (have ref_or_null?2:1) intervals"
*/ */
if (table->quick_keys.is_set(key) && !found_ref && //(C1) if (table->opt_range_keys.is_set(key) && !found_ref && //(C1)
table->quick_key_parts[key] == max_key_part && //(C2) table->opt_range[key].key_parts == max_key_part && //(C2)
table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part)) //(C3) table->opt_range[key].ranges == 1 + MY_TEST(ref_or_null_part)) //(C3)
{ {
records= (double) table->quick_rows[key]; records= (double) table->opt_range[key].rows;
tmp= adjust_quick_cost(table->quick_costs[key], tmp= adjust_quick_cost(table->opt_range[key].cost,
table->quick_rows[key]); table->opt_range[key].rows);
trace_access_idx.add("used_range_estimates", true); trace_access_idx.add("used_range_estimates", true);
goto got_cost2; goto got_cost2;
} }
@@ -7692,15 +7692,16 @@ best_access_path(JOIN *join,
cheaper in some cases ? cheaper in some cases ?
TODO: figure this out and adjust the plan choice if needed. TODO: figure this out and adjust the plan choice if needed.
*/ */
if (table->quick_keys.is_set(key)) if (table->opt_range_keys.is_set(key))
{ {
if (table->quick_key_parts[key] >= max_key_part) // (2) if (table->opt_range[key].key_parts >= max_key_part) // (2)
{ {
double rows= (double) table->opt_range[key].rows;
if (!found_ref && // (1) if (!found_ref && // (1)
records < (double) table->quick_rows[key]) // (3) records < rows) // (3)
{ {
trace_access_idx.add("used_range_estimates", true); trace_access_idx.add("used_range_estimates", true);
records= (double) table->quick_rows[key]; records= rows;
} }
} }
else /* (table->quick_key_parts[key] < max_key_part) */ else /* (table->quick_key_parts[key] < max_key_part) */
@@ -7766,15 +7767,16 @@ best_access_path(JOIN *join,
optimizer is the same as in ReuseRangeEstimateForRef-3, optimizer is the same as in ReuseRangeEstimateForRef-3,
applied to first table->quick_key_parts[key] key parts. applied to first table->quick_key_parts[key] key parts.
*/ */
if (table->quick_keys.is_set(key) && if (table->opt_range_keys.is_set(key) &&
table->quick_key_parts[key] <= max_key_part && table->opt_range[key].key_parts <= max_key_part &&
const_part & const_part &
((key_part_map)1 << table->quick_key_parts[key]) && ((key_part_map)1 << table->opt_range[key].key_parts) &&
table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part & table->opt_range[key].ranges == (1 +
const_part) && MY_TEST(ref_or_null_part &
records > (double) table->quick_rows[key]) const_part)) &&
records > (double) table->opt_range[key].rows)
{ {
records= (double) table->quick_rows[key]; records= (double) table->opt_range[key].rows;
} }
} }
@@ -7813,7 +7815,7 @@ best_access_path(JOIN *join,
tmp-= filter->get_adjusted_gain(rows) - filter->get_cmp_gain(rows); tmp-= filter->get_adjusted_gain(rows) - filter->get_cmp_gain(rows);
DBUG_ASSERT(tmp >= 0); DBUG_ASSERT(tmp >= 0);
trace_access_idx.add("rowid_filter_key", trace_access_idx.add("rowid_filter_key",
s->table->key_info[filter->key_no].name); table->key_info[filter->key_no].name);
} }
} }
trace_access_idx.add("rows", records).add("cost", tmp); trace_access_idx.add("rows", records).add("cost", tmp);
@@ -7928,7 +7930,7 @@ best_access_path(JOIN *join,
if ((records >= s->found_records || best > s->read_time) && // (1) if ((records >= s->found_records || best > s->read_time) && // (1)
!(best_key && best_key->key == MAX_KEY) && // (2) !(best_key && best_key->key == MAX_KEY) && // (2)
!(s->quick && best_key && s->quick->index == best_key->key && // (2) !(s->quick && best_key && s->quick->index == best_key->key && // (2)
best_max_key_part >= s->table->quick_key_parts[best_key->key]) &&// (2) best_max_key_part >= s->table->opt_range[best_key->key].key_parts) &&// (2)
!((s->table->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX) && // (3) !((s->table->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX) && // (3)
! s->table->covering_keys.is_clear_all() && best_key && !s->quick) &&// (3) ! s->table->covering_keys.is_clear_all() && best_key && !s->quick) &&// (3)
!(s->table->force_index && best_key && !s->quick) && // (4) !(s->table->force_index && best_key && !s->quick) && // (4)
@@ -9193,10 +9195,11 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
/* /*
Check if we have a prefix of key=const that matches a quick select. Check if we have a prefix of key=const that matches a quick select.
*/ */
if (!is_hash_join_key_no(key)) if (!is_hash_join_key_no(key) && table->opt_range_keys.is_set(key))
{ {
key_part_map quick_key_map= (key_part_map(1) << table->quick_key_parts[key]) - 1; key_part_map quick_key_map= (key_part_map(1) <<
if (table->quick_rows[key] && table->opt_range[key].key_parts) - 1;
if (table->opt_range[key].rows &&
!(quick_key_map & ~table->const_key_parts[key])) !(quick_key_map & ~table->const_key_parts[key]))
{ {
/* /*
@@ -9223,7 +9226,7 @@ double table_cond_selectivity(JOIN *join, uint idx, JOIN_TAB *s,
However if sel becomes greater than 2 then with high probability However if sel becomes greater than 2 then with high probability
something went wrong. something went wrong.
*/ */
sel /= (double)table->quick_rows[key] / (double) table->stat_records(); sel /= (double)table->opt_range[key].rows / (double) table->stat_records();
set_if_smaller(sel, 1.0); set_if_smaller(sel, 1.0);
used_range_selectivity= true; used_range_selectivity= true;
} }
@@ -11721,7 +11724,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
{ {
sel->needed_reg=tab->needed_reg; sel->needed_reg=tab->needed_reg;
} }
sel->quick_keys= tab->table->quick_keys; sel->quick_keys= tab->table->opt_range_keys;
if (!sel->quick_keys.is_subset(tab->checked_keys) || if (!sel->quick_keys.is_subset(tab->checked_keys) ||
!sel->needed_reg.is_subset(tab->checked_keys)) !sel->needed_reg.is_subset(tab->checked_keys))
{ {
@@ -13483,14 +13486,14 @@ double JOIN_TAB::scan_time()
get_delayed_table_estimates(table, &records, &read_time, get_delayed_table_estimates(table, &records, &read_time,
&startup_cost); &startup_cost);
found_records= records; found_records= records;
table->quick_condition_rows= records; table->opt_range_condition_rows= records;
} }
else else
{ {
found_records= records= table->stat_records(); found_records= records= table->stat_records();
read_time= table->file->scan_time(); read_time= table->file->scan_time();
/* /*
table->quick_condition_rows has already been set to table->opt_range_condition_rows has already been set to
table->file->stats.records table->file->stats.records
*/ */
} }
@@ -18262,6 +18265,7 @@ TABLE *Create_tmp_table::start(THD *thd,
char *tmpname,path[FN_REFLEN]; char *tmpname,path[FN_REFLEN];
Field **reg_field; Field **reg_field;
uint *blob_field; uint *blob_field;
key_part_map *const_key_parts;
/* Treat sum functions as normal ones when loose index scan is used. */ /* Treat sum functions as normal ones when loose index scan is used. */
m_save_sum_fields|= param->precomputed_group_by; m_save_sum_fields|= param->precomputed_group_by;
DBUG_ENTER("Create_tmp_table::start"); DBUG_ENTER("Create_tmp_table::start");
@@ -18358,6 +18362,7 @@ TABLE *Create_tmp_table::start(THD *thd,
&m_group_buff, (m_group && ! m_using_unique_constraint ? &m_group_buff, (m_group && ! m_using_unique_constraint ?
param->group_length : 0), param->group_length : 0),
&m_bitmaps, bitmap_buffer_size(field_count)*6, &m_bitmaps, bitmap_buffer_size(field_count)*6,
&const_key_parts, sizeof(*const_key_parts),
NullS)) NullS))
{ {
DBUG_RETURN(NULL); /* purecov: inspected */ DBUG_RETURN(NULL); /* purecov: inspected */
@@ -18375,12 +18380,15 @@ TABLE *Create_tmp_table::start(THD *thd,
bzero((char*) reg_field, sizeof(Field*) * (field_count+1)); bzero((char*) reg_field, sizeof(Field*) * (field_count+1));
bzero((char*) m_default_field, sizeof(Field*) * (field_count)); bzero((char*) m_default_field, sizeof(Field*) * (field_count));
bzero((char*) m_from_field, sizeof(Field*) * field_count); bzero((char*) m_from_field, sizeof(Field*) * field_count);
/* const_key_parts is used in sort_and_filter_keyuse */
bzero((char*) const_key_parts, sizeof(*const_key_parts));
table->mem_root= own_root; table->mem_root= own_root;
mem_root_save= thd->mem_root; mem_root_save= thd->mem_root;
thd->mem_root= &table->mem_root; thd->mem_root= &table->mem_root;
table->field=reg_field; table->field=reg_field;
table->const_key_parts= const_key_parts;
table->alias.set(table_alias->str, table_alias->length, table_alias_charset); table->alias.set(table_alias->str, table_alias->length, table_alias_charset);
table->reginfo.lock_type=TL_WRITE; /* Will be updated */ table->reginfo.lock_type=TL_WRITE; /* Will be updated */
@@ -23403,9 +23411,9 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
Otherwise, construct a ref access (todo: it's not clear what is the Otherwise, construct a ref access (todo: it's not clear what is the
win in using ref access when we could use quick select also?) win in using ref access when we could use quick select also?)
*/ */
if ((table->quick_keys.is_set(new_ref_key) && if ((table->opt_range_keys.is_set(new_ref_key) &&
table->quick_key_parts[new_ref_key] > ref_key_parts) || table->opt_range[new_ref_key].key_parts > ref_key_parts) ||
!(tab->ref.key >= 0)) !(tab->ref.key >= 0))
{ {
/* /*
The range optimizer constructed QUICK_RANGE for ref_key, and The range optimizer constructed QUICK_RANGE for ref_key, and
@@ -23508,7 +23516,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
goto use_filesort; goto use_filesort;
if (select && // psergey: why doesn't this use a quick? if (select && // psergey: why doesn't this use a quick?
table->quick_keys.is_set(best_key) && best_key != ref_key) table->opt_range_keys.is_set(best_key) && best_key != ref_key)
{ {
key_map tmp_map; key_map tmp_map;
tmp_map.clear_all(); // Force the creation of quick select tmp_map.clear_all(); // Force the creation of quick select
@@ -28064,14 +28072,14 @@ static bool get_range_limit_read_cost(const JOIN_TAB *tab,
We need to adjust the estimates if we had a quick select (or ref(const)) on We need to adjust the estimates if we had a quick select (or ref(const)) on
index keynr. index keynr.
*/ */
if (table->quick_keys.is_set(keynr)) if (table->opt_range_keys.is_set(keynr))
{ {
/* /*
Start from quick select's rows and cost. These are always cheaper than Start from quick select's rows and cost. These are always cheaper than
full index scan/cost. full index scan/cost.
*/ */
double best_rows= (double)table->quick_rows[keynr]; double best_rows= (double) table->opt_range[keynr].rows;
double best_cost= (double)table->quick_costs[keynr]; double best_cost= (double) table->opt_range[keynr].cost;
/* /*
Check if ref(const) access was possible on this index. Check if ref(const) access was possible on this index.
@@ -28098,8 +28106,8 @@ static bool get_range_limit_read_cost(const JOIN_TAB *tab,
2. ref(const) uses fewer key parts, becasue there is a 2. ref(const) uses fewer key parts, becasue there is a
range_cond(key_part+1). range_cond(key_part+1).
*/ */
if (kp == table->quick_key_parts[keynr]) if (kp == table->opt_range[keynr].key_parts)
ref_rows= table->quick_rows[keynr]; ref_rows= table->opt_range[keynr].rows;
else else
ref_rows= (ha_rows) table->key_info[keynr].actual_rec_per_key(kp-1); ref_rows= (ha_rows) table->key_info[keynr].actual_rec_per_key(kp-1);
@@ -28185,7 +28193,7 @@ static bool get_range_limit_read_cost(const JOIN_TAB *tab,
value for further use in QUICK_SELECT_DESC value for further use in QUICK_SELECT_DESC
@note @note
This function takes into account table->quick_condition_rows statistic This function takes into account table->opt_range_condition_rows statistic
(that is calculated by the make_join_statistics function). (that is calculated by the make_join_statistics function).
However, single table procedures such as mysql_update() and mysql_delete() However, single table procedures such as mysql_update() and mysql_delete()
never call make_join_statistics, so they have to update it manually never call make_join_statistics, so they have to update it manually
@@ -28220,7 +28228,7 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
double fanout= 1; double fanout= 1;
ha_rows table_records= table->stat_records(); ha_rows table_records= table->stat_records();
bool group= join && join->group && order == join->group_list; bool group= join && join->group && order == join->group_list;
ha_rows refkey_rows_estimate= table->quick_condition_rows; ha_rows refkey_rows_estimate= table->opt_range_condition_rows;
const bool has_limit= (select_limit_arg != HA_POS_ERROR); const bool has_limit= (select_limit_arg != HA_POS_ERROR);
THD* thd= join ? join->thd : table->in_use; THD* thd= join ? join->thd : table->in_use;
@@ -28274,7 +28282,7 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
trace_cheaper_ordering.add("read_time", read_time); trace_cheaper_ordering.add("read_time", read_time);
/* /*
Calculate the selectivity of the ref_key for REF_ACCESS. For Calculate the selectivity of the ref_key for REF_ACCESS. For
RANGE_ACCESS we use table->quick_condition_rows. RANGE_ACCESS we use table->opt_range_condition_rows.
*/ */
if (ref_key >= 0 && ref_key != MAX_KEY && tab->type == JT_REF) if (ref_key >= 0 && ref_key != MAX_KEY && tab->type == JT_REF)
{ {
@@ -28285,9 +28293,9 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
*/ */
if (tab->ref.const_ref_part_map == if (tab->ref.const_ref_part_map ==
make_prev_keypart_map(tab->ref.key_parts) && make_prev_keypart_map(tab->ref.key_parts) &&
table->quick_keys.is_set(ref_key) && table->opt_range_keys.is_set(ref_key) &&
table->quick_key_parts[ref_key] == tab->ref.key_parts) table->opt_range[ref_key].key_parts == tab->ref.key_parts)
refkey_rows_estimate= table->quick_rows[ref_key]; refkey_rows_estimate= table->opt_range[ref_key].rows;
else else
{ {
const KEY *ref_keyinfo= table->key_info + ref_key; const KEY *ref_keyinfo= table->key_info + ref_key;
@@ -28501,8 +28509,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
possible_key.add("cause", "ref estimates better"); possible_key.add("cause", "ref estimates better");
continue; continue;
} }
if (table->quick_keys.is_set(nr)) if (table->opt_range_keys.is_set(nr))
quick_records= table->quick_rows[nr]; quick_records= table->opt_range[nr].rows;
possible_key.add("records", quick_records); possible_key.add("records", quick_records);
if (best_key < 0 || if (best_key < 0 ||
(select_limit <= MY_MIN(quick_records,best_records) ? (select_limit <= MY_MIN(quick_records,best_records) ?
@@ -28597,7 +28605,7 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
@note @note
Side effects: Side effects:
- may deallocate or deallocate and replace select->quick; - may deallocate or deallocate and replace select->quick;
- may set table->quick_condition_rows and table->quick_rows[...] - may set table->opt_range_condition_rows and table->quick_rows[...]
to table->file->stats.records. to table->file->stats.records.
*/ */
@@ -28662,10 +28670,10 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select,
{ // check if some index scan & LIMIT is more efficient than filesort { // check if some index scan & LIMIT is more efficient than filesort
/* /*
Update quick_condition_rows since single table UPDATE/DELETE procedures Update opt_range_condition_rows since single table UPDATE/DELETE procedures
don't call make_join_statistics() and leave this variable uninitialized. don't call make_join_statistics() and leave this variable uninitialized.
*/ */
table->quick_condition_rows= table->stat_records(); table->opt_range_condition_rows= table->stat_records();
int key, direction; int key, direction;
if (test_if_cheaper_ordering(NULL, order, table, if (test_if_cheaper_ordering(NULL, order, table,

View File

@@ -443,7 +443,7 @@ int mysql_update(THD *thd,
/* Calculate "table->covering_keys" based on the WHERE */ /* Calculate "table->covering_keys" based on the WHERE */
table->covering_keys= table->s->keys_in_use; table->covering_keys= table->s->keys_in_use;
table->quick_keys.clear_all(); table->opt_range_keys.clear_all();
query_plan.select_lex= thd->lex->first_select_lex(); query_plan.select_lex= thd->lex->first_select_lex();
query_plan.table= table; query_plan.table= table;
@@ -577,7 +577,7 @@ int mysql_update(THD *thd,
} }
/* If running in safe sql mode, don't allow updates without keys */ /* If running in safe sql mode, don't allow updates without keys */
if (table->quick_keys.is_clear_all()) if (table->opt_range_keys.is_clear_all())
{ {
thd->set_status_no_index_used(); thd->set_status_no_index_used();
if (safe_update && !using_limit) if (safe_update && !using_limit)

View File

@@ -3976,6 +3976,15 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share,
sizeof(Field*))))) sizeof(Field*)))))
goto err; /* purecov: inspected */ goto err; /* purecov: inspected */
/* Allocate storage for range optimizer */
if (!multi_alloc_root(&outparam->mem_root,
&outparam->opt_range,
share->keys * sizeof(TABLE::OPT_RANGE),
&outparam->const_key_parts,
share->keys * sizeof(key_part_map),
NullS))
goto err;
outparam->field= field_ptr; outparam->field= field_ptr;
record= (uchar*) outparam->record[0]-1; /* Fieldstart = 1 */ record= (uchar*) outparam->record[0]-1; /* Fieldstart = 1 */
@@ -5383,9 +5392,9 @@ void TABLE::init(THD *thd, TABLE_LIST *tl)
range_rowid_filter_cost_info_ptr= NULL; range_rowid_filter_cost_info_ptr= NULL;
range_rowid_filter_cost_info= NULL; range_rowid_filter_cost_info= NULL;
vers_write= s->versioned; vers_write= s->versioned;
quick_condition_rows=0; opt_range_condition_rows=0;
no_cache= false; no_cache= false;
initialize_quick_structures(); initialize_opt_range_structures();
#ifdef HAVE_REPLICATION #ifdef HAVE_REPLICATION
/* used in RBR Triggers */ /* used in RBR Triggers */
master_had_triggers= 0; master_had_triggers= 0;
@@ -7773,12 +7782,28 @@ void TABLE::restore_blob_values(String *blob_storage)
bool TABLE::alloc_keys(uint key_count) bool TABLE::alloc_keys(uint key_count)
{ {
key_info= (KEY*) alloc_root(&mem_root, sizeof(KEY)*(s->keys+key_count)); KEY *new_key_info;
key_part_map *new_const_key_parts;
DBUG_ASSERT(s->tmp_table == INTERNAL_TMP_TABLE);
if (!multi_alloc_root(&mem_root,
&new_key_info, sizeof(*key_info)*(s->keys+key_count),
&new_const_key_parts,
sizeof(*new_const_key_parts)*(s->keys+key_count),
NullS))
return TRUE;
if (s->keys) if (s->keys)
memmove(key_info, s->key_info, sizeof(KEY)*s->keys); {
s->key_info= key_info; memmove(new_key_info, s->key_info, sizeof(*key_info) * s->keys);
memmove(new_const_key_parts, const_key_parts,
s->keys * sizeof(const_key_parts));
}
s->key_info= key_info= new_key_info;
const_key_parts= new_const_key_parts;
bzero((char*) (const_key_parts + s->keys),
sizeof(*const_key_parts) * key_count);
max_keys= s->keys+key_count; max_keys= s->keys+key_count;
return !(key_info); return FALSE;
} }
@@ -9898,20 +9923,18 @@ bool TABLE::export_structure(THD *thd, Row_definition_list *defs)
/* /*
@brief @brief
Initialize all the quick structures that are used to stored the Initialize all the opt_range structures that are used to stored the
estimates when the range optimizer is run. estimates when the range optimizer is run.
@details As these are initialized by the range optimizer for all index
This is specifically needed when we read the TABLE structure from the marked in opt_range_keys, we only mark the memory as undefined
table cache. There can be some garbage data from previous queries to be able to find wrong usage of data with valgrind or MSAN.
that need to be reset here.
*/ */
void TABLE::initialize_quick_structures() void TABLE::initialize_opt_range_structures()
{ {
bzero(quick_rows, sizeof(quick_rows)); TRASH_ALLOC(&opt_range_keys, sizeof(opt_range_keys));
bzero(quick_key_parts, sizeof(quick_key_parts)); TRASH_ALLOC(opt_range, s->keys * sizeof(*opt_range));
bzero(quick_costs, sizeof(quick_costs)); TRASH_ALLOC(const_key_parts, s->keys * sizeof(*const_key_parts));
bzero(quick_n_ranges, sizeof(quick_n_ranges));
} }
/* /*

View File

@@ -1257,8 +1257,7 @@ public:
Map of keys that can be used to retrieve all data from this table Map of keys that can be used to retrieve all data from this table
needed by the query without reading the row. needed by the query without reading the row.
*/ */
key_map covering_keys; key_map covering_keys, intersect_keys;
key_map quick_keys, intersect_keys;
/* /*
A set of keys that can be used in the query that references this A set of keys that can be used in the query that references this
table. table.
@@ -1340,28 +1339,29 @@ public:
/* The estimate of the number of records in the table used by optimizer */ /* The estimate of the number of records in the table used by optimizer */
ha_rows used_stat_records; ha_rows used_stat_records;
key_map opt_range_keys;
/* /*
For each key that has quick_keys.is_set(key) == TRUE: estimate of #records The following structure is filled for each key that has
and max #key parts that range access would use. opt_range_keys.is_set(key) == TRUE
*/ */
ha_rows quick_rows[MAX_KEY]; struct OPT_RANGE
uint quick_key_parts[MAX_KEY]; {
uint key_parts;
double quick_costs[MAX_KEY]; uint ranges;
ha_rows rows;
double cost;
/*
If there is a range access by i-th index then the cost of
index only access for it is stored in index_only_costs[i]
*/
double index_only_cost;
} *opt_range;
/* /*
If there is a range access by i-th index then the cost of Bitmaps of key parts that =const for the duration of join execution. If
index only access for it is stored in quick_index_only_costs[i] we're in a subquery, then the constant may be different across subquery
re-executions.
*/ */
double quick_index_only_costs[MAX_KEY]; key_part_map *const_key_parts;
/*
Bitmaps of key parts that =const for the duration of join execution. If
we're in a subquery, then the constant may be different across subquery
re-executions.
*/
key_part_map const_key_parts[MAX_KEY];
uint quick_n_ranges[MAX_KEY];
/* /*
Estimate of number of records that satisfy SARGable part of the table Estimate of number of records that satisfy SARGable part of the table
@@ -1371,7 +1371,7 @@ public:
that will pass the table condition (condition that depends on fields of that will pass the table condition (condition that depends on fields of
this table and constants) this table and constants)
*/ */
ha_rows quick_condition_rows; ha_rows opt_range_condition_rows;
double cond_selectivity; double cond_selectivity;
List<st_cond_statistic> *cond_selectivity_sampling_explain; List<st_cond_statistic> *cond_selectivity_sampling_explain;
@@ -1637,7 +1637,7 @@ public:
bool is_filled_at_execution(); bool is_filled_at_execution();
bool update_const_key_parts(COND *conds); bool update_const_key_parts(COND *conds);
void initialize_quick_structures(); void initialize_opt_range_structures();
my_ptrdiff_t default_values_offset() const my_ptrdiff_t default_values_offset() const
{ return (my_ptrdiff_t) (s->default_values - record[0]); } { return (my_ptrdiff_t) (s->default_values - record[0]); }