From 6cee9b1953b5c6c7fa8bf12a99f24a6927aa06e0 Mon Sep 17 00:00:00 2001 From: Monty Date: Fri, 26 Jun 2020 01:47:33 +0300 Subject: [PATCH] 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. --- sql/opt_range.cc | 65 ++++++++++---------- sql/opt_subselect.cc | 2 +- sql/rowid_filter.cc | 11 ++-- sql/sql_delete.cc | 4 +- sql/sql_select.cc | 142 +++++++++++++++++++++++-------------------- sql/sql_update.cc | 4 +- sql/table.cc | 55 ++++++++++++----- sql/table.h | 44 +++++++------- 8 files changed, 181 insertions(+), 146 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 1993dc265a0..c674730d230 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -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->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; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 1e0a5398f6a..62f5964cd3c 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -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, diff --git a/sql/rowid_filter.cc b/sql/rowid_filter.cc index d8d0890e1f9..4a3746d72db 100644 --- a/sql/rowid_filter.cc +++ b/sql/rowid_filter.cc @@ -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; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 5942efcd601..7280236e43f 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -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) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 552363f80c0..896e7fdf5e7 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -4897,7 +4897,7 @@ make_join_statistics(JOIN *join, 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 &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 &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, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 8cd3b9f0411..fccc2a426c4 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -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) diff --git a/sql/table.cc b/sql/table.cc index 7b7313d3ea1..124694de621 100644 --- a/sql/table.cc +++ b/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)); } /* diff --git a/sql/table.h b/sql/table.h index 43ef03e16df..94115c778bf 100644 --- a/sql/table.h +++ b/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]; - /* - 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] - */ - double quick_index_only_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 index_only_costs[i] + */ + 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. + 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 *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]); }