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:
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(&param, 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(&param, tree, backup_keys);
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());
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;

View File

@@ -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,

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,
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;

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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));
}
/*

View File

@@ -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]); }