1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +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

@ -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;
}
@ -9513,9 +9516,9 @@ best_extension_by_limited_search(JOIN *join,
for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
{
table_map real_table_bit= s->table->map;
if ((remaining_tables & real_table_bit) &&
if ((remaining_tables & real_table_bit) &&
(allowed_tables & real_table_bit) &&
!(remaining_tables & s->dependent) &&
!(remaining_tables & s->dependent) &&
(!idx || !check_interleaving_with_nj(s)))
{
double current_record_count, current_read_time;
@ -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 */
@ -20596,7 +20604,7 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
}
join->thd->get_stmt_da()->reset_current_row_for_warning();
if (rc != NESTED_LOOP_NO_MORE_ROWS &&
if (rc != NESTED_LOOP_NO_MORE_ROWS &&
(rc= join_tab_execution_startup(join_tab)) < 0)
DBUG_RETURN(rc);
@ -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
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) ||
!(tab->ref.key >= 0))
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))
{
/*
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;
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,