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:
@@ -2599,10 +2599,10 @@ static int fill_used_fields_bitmap(PARAM *param)
|
||||
In the table struct the following information is updated:
|
||||
quick_keys - Which keys can be used
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
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).
|
||||
(we can re-use some of E(#rows) calcuation code from
|
||||
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_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);
|
||||
}
|
||||
}
|
||||
@@ -2977,7 +2977,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
|
||||
{
|
||||
new_conj_trp= get_best_disjunct_quick(¶m, imerge, best_read_time);
|
||||
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);
|
||||
if (new_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(¶m, tree, backup_keys);
|
||||
if ((group_trp= get_best_group_min_max(¶m, 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());
|
||||
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++)
|
||||
{
|
||||
if (table->quick_keys.is_set(keynr))
|
||||
set_if_bigger(max_quick_key_parts, table->quick_key_parts[keynr]);
|
||||
if (table->opt_range_keys.is_set(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++)
|
||||
{
|
||||
if (table->quick_keys.is_set(keynr) &&
|
||||
table->quick_key_parts[keynr] == quick_key_parts)
|
||||
if (table->opt_range_keys.is_set(keynr) &&
|
||||
table->opt_range[keynr].key_parts == quick_key_parts)
|
||||
{
|
||||
uint i;
|
||||
uint used_key_parts= table->quick_key_parts[keynr];
|
||||
double quick_cond_selectivity= table->quick_rows[keynr] /
|
||||
table_records;
|
||||
uint used_key_parts= table->opt_range[keynr].key_parts;
|
||||
double quick_cond_selectivity= (table->opt_range[keynr].rows /
|
||||
table_records);
|
||||
KEY *key_info= table->key_info + keynr;
|
||||
KEY_PART_INFO* key_part= key_info->key_part;
|
||||
/*
|
||||
@@ -5777,7 +5777,7 @@ bool prepare_search_best_index_intersect(PARAM *param,
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
if (!best_rows)
|
||||
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->index_scan_costs= intersect_best->index_scan_costs;
|
||||
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->records= records;
|
||||
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",
|
||||
("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.
|
||||
mrr_flags INOUT MRR access flags
|
||||
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
|
||||
param->is_ror_scan is set to reflect if the key scan is a ROR (see
|
||||
is_key_scan_ror function for more info)
|
||||
param->table->quick_*, param->range_count (and maybe others) are
|
||||
param->table->opt_range*, param->range_count (and maybe others) are
|
||||
updated with data of given key scan, see quick_range_seq_next for details.
|
||||
|
||||
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())
|
||||
rows= file->multi_range_read_info_const(keynr, &seq_if, (void*)&seq, 0,
|
||||
bufsize, mrr_flags, cost);
|
||||
param->quick_rows[keynr]= rows;
|
||||
if (rows != HA_POS_ERROR)
|
||||
{
|
||||
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
|
||||
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;
|
||||
set_if_bigger(rows, 1);
|
||||
}
|
||||
param->quick_rows[keynr]= rows;
|
||||
}
|
||||
param->possible_keys.set_bit(keynr);
|
||||
if (update_tbl_stats)
|
||||
{
|
||||
param->table->quick_keys.set_bit(keynr);
|
||||
param->table->quick_key_parts[keynr]= param->max_key_parts;
|
||||
param->table->quick_n_ranges[keynr]= param->range_count;
|
||||
param->table->quick_condition_rows=
|
||||
MY_MIN(param->table->quick_condition_rows, rows);
|
||||
param->table->quick_rows[keynr]= rows;
|
||||
param->table->quick_costs[keynr]= cost->total_cost();
|
||||
param->table->opt_range_keys.set_bit(keynr);
|
||||
param->table->opt_range[keynr].key_parts= param->max_key_parts;
|
||||
param->table->opt_range[keynr].ranges= param->range_count;
|
||||
param->table->opt_range_condition_rows=
|
||||
MY_MIN(param->table->opt_range_condition_rows, rows);
|
||||
param->table->opt_range[keynr].rows= rows;
|
||||
param->table->opt_range[keynr].cost= cost->total_cost();
|
||||
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
|
||||
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 */
|
||||
enum ha_key_alg key_alg= param->table->key_info[seq.real_keyno].algorithm;
|
||||
|
@@ -2500,7 +2500,7 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
|
||||
double rows= 1.0;
|
||||
while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
|
||||
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);
|
||||
}
|
||||
memcpy((uchar*) sjm->positions,
|
||||
|
@@ -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,
|
||||
TABLE *tab, uint idx)
|
||||
{
|
||||
DBUG_ASSERT(tab->opt_range_keys.is_set(idx));
|
||||
|
||||
container_type= cont_type;
|
||||
table= tab;
|
||||
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);
|
||||
selectivity= est_elements/((double) table->stat_records());
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
|
||||
@@ -345,7 +348,7 @@ void TABLE::init_cost_info_for_usable_range_rowid_filters(THD *thd)
|
||||
uint key_no;
|
||||
key_map usable_range_filter_keys;
|
||||
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
|
||||
@@ -359,7 +362,7 @@ void TABLE::init_cost_info_for_usable_range_rowid_filters(THD *thd)
|
||||
continue;
|
||||
if (file->is_clustering_key(key_no)) // !2
|
||||
continue;
|
||||
if (quick_rows[key_no] >
|
||||
if (opt_range[key_no].rows >
|
||||
get_max_range_rowid_filter_elems_for_table(thd, this,
|
||||
SORTED_ARRAY_CONTAINER)) // !3
|
||||
continue;
|
||||
|
@@ -492,7 +492,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
|
||||
set_statistics_for_table(thd, table);
|
||||
|
||||
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);
|
||||
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 (table->quick_keys.is_clear_all())
|
||||
if (table->opt_range_keys.is_clear_all())
|
||||
{
|
||||
thd->set_status_no_index_used();
|
||||
if (safe_update && !using_limit)
|
||||
|
@@ -4897,7 +4897,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
|
||||
table->file->print_error(error, MYF(0));
|
||||
goto error;
|
||||
}
|
||||
table->quick_keys.clear_all();
|
||||
table->opt_range_keys.clear_all();
|
||||
table->intersect_keys.clear_all();
|
||||
table->reginfo.join_tab=s;
|
||||
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;
|
||||
if (tables->schema_table)
|
||||
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;
|
||||
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,
|
||||
&s->startup_cost);
|
||||
s->found_records= s->records;
|
||||
table->quick_condition_rows=s->records;
|
||||
table->opt_range_condition_rows=s->records;
|
||||
}
|
||||
else
|
||||
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
|
||||
heuristics at once.
|
||||
*/
|
||||
if (s->table->quick_condition_rows != s->found_records)
|
||||
records= s->table->quick_condition_rows;
|
||||
if (s->table->opt_range_condition_rows != s->found_records)
|
||||
records= s->table->opt_range_condition_rows;
|
||||
|
||||
dbl_records= (double)records;
|
||||
return dbl_records;
|
||||
@@ -7506,8 +7506,8 @@ best_access_path(JOIN *join,
|
||||
type= JT_EQ_REF;
|
||||
trace_access_idx.add("access_type", join_type_str[type])
|
||||
.add("index", keyinfo->name);
|
||||
if (!found_ref && table->quick_keys.is_set(key))
|
||||
tmp= adjust_quick_cost(table->quick_costs[key], 1);
|
||||
if (!found_ref && table->opt_range_keys.is_set(key))
|
||||
tmp= adjust_quick_cost(table->opt_range[key].cost, 1);
|
||||
else
|
||||
tmp= table->file->avg_io_cost();
|
||||
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
|
||||
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);
|
||||
tmp= adjust_quick_cost(table->quick_costs[key],
|
||||
table->quick_rows[key]);
|
||||
tmp= adjust_quick_cost(table->opt_range[key].cost,
|
||||
table->opt_range[key].rows);
|
||||
goto got_cost;
|
||||
}
|
||||
else
|
||||
@@ -7575,19 +7575,19 @@ best_access_path(JOIN *join,
|
||||
can make an adjustment is a special case of the criteria used
|
||||
in ReuseRangeEstimateForRef-3.
|
||||
*/
|
||||
if (table->quick_keys.is_set(key) &&
|
||||
if (table->opt_range_keys.is_set(key) &&
|
||||
(const_part &
|
||||
(((key_part_map)1 << table->quick_key_parts[key])-1)) ==
|
||||
(((key_part_map)1 << table->quick_key_parts[key])-1) &&
|
||||
table->quick_n_ranges[key] == 1 &&
|
||||
records > (double) table->quick_rows[key])
|
||||
(((key_part_map)1 << table->opt_range[key].key_parts)-1)) ==
|
||||
(((key_part_map)1 << table->opt_range[key].key_parts)-1) &&
|
||||
table->opt_range[key].ranges == 1 &&
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (table->quick_keys.is_set(key))
|
||||
if (table->opt_range_keys.is_set(key))
|
||||
{
|
||||
trace_access_idx.add("used_range_estimates",false)
|
||||
.add("cause",
|
||||
@@ -7661,13 +7661,13 @@ best_access_path(JOIN *join,
|
||||
|
||||
(C3) "range optimizer used (have ref_or_null?2:1) intervals"
|
||||
*/
|
||||
if (table->quick_keys.is_set(key) && !found_ref && //(C1)
|
||||
table->quick_key_parts[key] == max_key_part && //(C2)
|
||||
table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part)) //(C3)
|
||||
if (table->opt_range_keys.is_set(key) && !found_ref && //(C1)
|
||||
table->opt_range[key].key_parts == max_key_part && //(C2)
|
||||
table->opt_range[key].ranges == 1 + MY_TEST(ref_or_null_part)) //(C3)
|
||||
{
|
||||
records= (double) table->quick_rows[key];
|
||||
tmp= adjust_quick_cost(table->quick_costs[key],
|
||||
table->quick_rows[key]);
|
||||
records= (double) table->opt_range[key].rows;
|
||||
tmp= adjust_quick_cost(table->opt_range[key].cost,
|
||||
table->opt_range[key].rows);
|
||||
trace_access_idx.add("used_range_estimates", true);
|
||||
goto got_cost2;
|
||||
}
|
||||
@@ -7692,15 +7692,16 @@ best_access_path(JOIN *join,
|
||||
cheaper in some cases ?
|
||||
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)
|
||||
records < (double) table->quick_rows[key]) // (3)
|
||||
records < rows) // (3)
|
||||
{
|
||||
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) */
|
||||
@@ -7766,15 +7767,16 @@ best_access_path(JOIN *join,
|
||||
optimizer is the same as in ReuseRangeEstimateForRef-3,
|
||||
applied to first table->quick_key_parts[key] key parts.
|
||||
*/
|
||||
if (table->quick_keys.is_set(key) &&
|
||||
table->quick_key_parts[key] <= max_key_part &&
|
||||
if (table->opt_range_keys.is_set(key) &&
|
||||
table->opt_range[key].key_parts <= max_key_part &&
|
||||
const_part &
|
||||
((key_part_map)1 << table->quick_key_parts[key]) &&
|
||||
table->quick_n_ranges[key] == 1 + MY_TEST(ref_or_null_part &
|
||||
const_part) &&
|
||||
records > (double) table->quick_rows[key])
|
||||
((key_part_map)1 << table->opt_range[key].key_parts) &&
|
||||
table->opt_range[key].ranges == (1 +
|
||||
MY_TEST(ref_or_null_part &
|
||||
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);
|
||||
DBUG_ASSERT(tmp >= 0);
|
||||
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);
|
||||
@@ -7928,7 +7930,7 @@ best_access_path(JOIN *join,
|
||||
if ((records >= s->found_records || best > s->read_time) && // (1)
|
||||
!(best_key && best_key->key == MAX_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->covering_keys.is_clear_all() && best_key && !s->quick) &&// (3)
|
||||
!(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.
|
||||
*/
|
||||
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;
|
||||
if (table->quick_rows[key] &&
|
||||
key_part_map quick_key_map= (key_part_map(1) <<
|
||||
table->opt_range[key].key_parts) - 1;
|
||||
if (table->opt_range[key].rows &&
|
||||
!(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
|
||||
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);
|
||||
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->quick_keys= tab->table->quick_keys;
|
||||
sel->quick_keys= tab->table->opt_range_keys;
|
||||
if (!sel->quick_keys.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,
|
||||
&startup_cost);
|
||||
found_records= records;
|
||||
table->quick_condition_rows= records;
|
||||
table->opt_range_condition_rows= records;
|
||||
}
|
||||
else
|
||||
{
|
||||
found_records= records= table->stat_records();
|
||||
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
|
||||
*/
|
||||
}
|
||||
@@ -18262,6 +18265,7 @@ TABLE *Create_tmp_table::start(THD *thd,
|
||||
char *tmpname,path[FN_REFLEN];
|
||||
Field **reg_field;
|
||||
uint *blob_field;
|
||||
key_part_map *const_key_parts;
|
||||
/* Treat sum functions as normal ones when loose index scan is used. */
|
||||
m_save_sum_fields|= param->precomputed_group_by;
|
||||
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 ?
|
||||
param->group_length : 0),
|
||||
&m_bitmaps, bitmap_buffer_size(field_count)*6,
|
||||
&const_key_parts, sizeof(*const_key_parts),
|
||||
NullS))
|
||||
{
|
||||
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*) m_default_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;
|
||||
mem_root_save= thd->mem_root;
|
||||
thd->mem_root= &table->mem_root;
|
||||
|
||||
table->field=reg_field;
|
||||
table->const_key_parts= const_key_parts;
|
||||
table->alias.set(table_alias->str, table_alias->length, table_alias_charset);
|
||||
|
||||
table->reginfo.lock_type=TL_WRITE; /* Will be updated */
|
||||
@@ -23403,8 +23411,8 @@ 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
|
||||
win in using ref access when we could use quick select also?)
|
||||
*/
|
||||
if ((table->quick_keys.is_set(new_ref_key) &&
|
||||
table->quick_key_parts[new_ref_key] > ref_key_parts) ||
|
||||
if ((table->opt_range_keys.is_set(new_ref_key) &&
|
||||
table->opt_range[new_ref_key].key_parts > ref_key_parts) ||
|
||||
!(tab->ref.key >= 0))
|
||||
{
|
||||
/*
|
||||
@@ -23508,7 +23516,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
|
||||
goto use_filesort;
|
||||
|
||||
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;
|
||||
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
|
||||
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
|
||||
full index scan/cost.
|
||||
*/
|
||||
double best_rows= (double)table->quick_rows[keynr];
|
||||
double best_cost= (double)table->quick_costs[keynr];
|
||||
double best_rows= (double) table->opt_range[keynr].rows;
|
||||
double best_cost= (double) table->opt_range[keynr].cost;
|
||||
|
||||
/*
|
||||
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
|
||||
range_cond(key_part+1).
|
||||
*/
|
||||
if (kp == table->quick_key_parts[keynr])
|
||||
ref_rows= table->quick_rows[keynr];
|
||||
if (kp == table->opt_range[keynr].key_parts)
|
||||
ref_rows= table->opt_range[keynr].rows;
|
||||
else
|
||||
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
|
||||
|
||||
@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).
|
||||
However, single table procedures such as mysql_update() and mysql_delete()
|
||||
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;
|
||||
ha_rows table_records= table->stat_records();
|
||||
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);
|
||||
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);
|
||||
/*
|
||||
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)
|
||||
{
|
||||
@@ -28285,9 +28293,9 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table,
|
||||
*/
|
||||
if (tab->ref.const_ref_part_map ==
|
||||
make_prev_keypart_map(tab->ref.key_parts) &&
|
||||
table->quick_keys.is_set(ref_key) &&
|
||||
table->quick_key_parts[ref_key] == tab->ref.key_parts)
|
||||
refkey_rows_estimate= table->quick_rows[ref_key];
|
||||
table->opt_range_keys.is_set(ref_key) &&
|
||||
table->opt_range[ref_key].key_parts == tab->ref.key_parts)
|
||||
refkey_rows_estimate= table->opt_range[ref_key].rows;
|
||||
else
|
||||
{
|
||||
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");
|
||||
continue;
|
||||
}
|
||||
if (table->quick_keys.is_set(nr))
|
||||
quick_records= table->quick_rows[nr];
|
||||
if (table->opt_range_keys.is_set(nr))
|
||||
quick_records= table->opt_range[nr].rows;
|
||||
possible_key.add("records", quick_records);
|
||||
if (best_key < 0 ||
|
||||
(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
|
||||
Side effects:
|
||||
- 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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
table->quick_condition_rows= table->stat_records();
|
||||
table->opt_range_condition_rows= table->stat_records();
|
||||
|
||||
int key, direction;
|
||||
if (test_if_cheaper_ordering(NULL, order, table,
|
||||
|
@@ -443,7 +443,7 @@ int mysql_update(THD *thd,
|
||||
|
||||
/* Calculate "table->covering_keys" based on the WHERE */
|
||||
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.table= table;
|
||||
@@ -577,7 +577,7 @@ int mysql_update(THD *thd,
|
||||
}
|
||||
|
||||
/* 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();
|
||||
if (safe_update && !using_limit)
|
||||
|
55
sql/table.cc
55
sql/table.cc
@@ -3976,6 +3976,15 @@ enum open_frm_error open_table_from_share(THD *thd, TABLE_SHARE *share,
|
||||
sizeof(Field*)))))
|
||||
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;
|
||||
|
||||
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= NULL;
|
||||
vers_write= s->versioned;
|
||||
quick_condition_rows=0;
|
||||
opt_range_condition_rows=0;
|
||||
no_cache= false;
|
||||
initialize_quick_structures();
|
||||
initialize_opt_range_structures();
|
||||
#ifdef HAVE_REPLICATION
|
||||
/* used in RBR Triggers */
|
||||
master_had_triggers= 0;
|
||||
@@ -7773,12 +7782,28 @@ void TABLE::restore_blob_values(String *blob_storage)
|
||||
|
||||
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)
|
||||
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;
|
||||
return !(key_info);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -9898,20 +9923,18 @@ bool TABLE::export_structure(THD *thd, Row_definition_list *defs)
|
||||
|
||||
/*
|
||||
@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.
|
||||
@details
|
||||
This is specifically needed when we read the TABLE structure from the
|
||||
table cache. There can be some garbage data from previous queries
|
||||
that need to be reset here.
|
||||
As these are initialized by the range optimizer for all index
|
||||
marked in opt_range_keys, we only mark the memory as undefined
|
||||
to be able to find wrong usage of data with valgrind or MSAN.
|
||||
*/
|
||||
|
||||
void TABLE::initialize_quick_structures()
|
||||
void TABLE::initialize_opt_range_structures()
|
||||
{
|
||||
bzero(quick_rows, sizeof(quick_rows));
|
||||
bzero(quick_key_parts, sizeof(quick_key_parts));
|
||||
bzero(quick_costs, sizeof(quick_costs));
|
||||
bzero(quick_n_ranges, sizeof(quick_n_ranges));
|
||||
TRASH_ALLOC(&opt_range_keys, sizeof(opt_range_keys));
|
||||
TRASH_ALLOC(opt_range, s->keys * sizeof(*opt_range));
|
||||
TRASH_ALLOC(const_key_parts, s->keys * sizeof(*const_key_parts));
|
||||
}
|
||||
|
||||
/*
|
||||
|
32
sql/table.h
32
sql/table.h
@@ -1257,8 +1257,7 @@ public:
|
||||
Map of keys that can be used to retrieve all data from this table
|
||||
needed by the query without reading the row.
|
||||
*/
|
||||
key_map covering_keys;
|
||||
key_map quick_keys, intersect_keys;
|
||||
key_map covering_keys, intersect_keys;
|
||||
/*
|
||||
A set of keys that can be used in the query that references this
|
||||
table.
|
||||
@@ -1340,28 +1339,29 @@ public:
|
||||
/* The estimate of the number of records in the table used by optimizer */
|
||||
ha_rows used_stat_records;
|
||||
|
||||
key_map opt_range_keys;
|
||||
/*
|
||||
For each key that has quick_keys.is_set(key) == TRUE: estimate of #records
|
||||
and max #key parts that range access would use.
|
||||
The following structure is filled for each key that has
|
||||
opt_range_keys.is_set(key) == TRUE
|
||||
*/
|
||||
ha_rows quick_rows[MAX_KEY];
|
||||
uint quick_key_parts[MAX_KEY];
|
||||
|
||||
double quick_costs[MAX_KEY];
|
||||
struct OPT_RANGE
|
||||
{
|
||||
uint key_parts;
|
||||
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 quick_index_only_costs[i]
|
||||
index only access for it is stored in index_only_costs[i]
|
||||
*/
|
||||
double quick_index_only_costs[MAX_KEY];
|
||||
|
||||
double index_only_cost;
|
||||
} *opt_range;
|
||||
/*
|
||||
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];
|
||||
key_part_map *const_key_parts;
|
||||
|
||||
/*
|
||||
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
|
||||
this table and constants)
|
||||
*/
|
||||
ha_rows quick_condition_rows;
|
||||
ha_rows opt_range_condition_rows;
|
||||
|
||||
double cond_selectivity;
|
||||
List<st_cond_statistic> *cond_selectivity_sampling_explain;
|
||||
@@ -1637,7 +1637,7 @@ public:
|
||||
bool is_filled_at_execution();
|
||||
|
||||
bool update_const_key_parts(COND *conds);
|
||||
void initialize_quick_structures();
|
||||
void initialize_opt_range_structures();
|
||||
|
||||
my_ptrdiff_t default_values_offset() const
|
||||
{ return (my_ptrdiff_t) (s->default_values - record[0]); }
|
||||
|
Reference in New Issue
Block a user