Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions mysql-test/main/opt_context_load_stats_basic.result
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].name');
select * from t1 where a > 10;
a b
Warnings:
Warning 4253 Failed to parse saved optimizer context: "name" element not present at offset 1387.
Warning 4253 Failed to parse saved optimizer context: "name" element not present at offset 1409.
set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].ddl');
select * from t1 where a > 10;
a b
Expand All @@ -354,12 +354,12 @@ set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat
select * from t1 where a > 10;
a b
Warnings:
Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1380.
Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1402.
set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].file_stat_records');
select * from t1 where a > 10;
a b
Warnings:
Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1380.
Warning 4253 Failed to parse saved optimizer context: "file_stat_records" element not present at offset 1402.
set @opt_context=json_remove(@saved_opt_context_1, '$.list_contexts[0].indexes[0].index_name');
select * from t1 where a > 10;
a b
Expand Down
122 changes: 117 additions & 5 deletions sql/opt_context_store_replay.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ class records_in_range_call_record : public Sql_alloc
ha_rows records;
};

class count_agg : public Sql_alloc
{
public:
ulong call_number;
longlong count;
};

/*
structure to store all the index range records,
and the cost for reading indexes, pertaining to a table
Expand All @@ -152,6 +159,7 @@ class table_context_for_store : public Sql_alloc
List<cost_index_read_call_record> irc_list;
List<records_in_range_call_record> rir_list;
List<char> const_tbl_ins_stmt_list;
List<count_agg> count_agg_list;
};

namespace Show
Expand Down Expand Up @@ -182,6 +190,8 @@ static bool parse_range_cost_estimate(MEM_ROOT*, json_engine_t *je,
static int parse_records_in_range_context(MEM_ROOT *mem_root, json_engine_t *je,
String *err_buf,
records_in_range_call_record *rir_ctx);
static int parse_count_agg_context(MEM_ROOT *mem_root, json_engine_t *je,
String *err_buf, count_agg *out);

static char *strdup_root(MEM_ROOT *root, const String *str)
{
Expand Down Expand Up @@ -299,6 +309,20 @@ void dump_records_in_range_calls(List<records_in_range_call_record> *rir_list,
}
}

static void dump_count_agg_calls(List<count_agg> *count_agg_list,
Json_writer *ctx_writer)
{
Json_writer_array list_cnt_agg_wrapper(ctx_writer, "list_count_agg");
List_iterator li(*count_agg_list);

while (count_agg *cnt_agg= li++)
{
Json_writer_object obj(ctx_writer);
obj.add("call_number", cnt_agg->call_number);
obj.add("count", cnt_agg->count);
}
}

static
void dump_recorded_table_calls(THD *thd, uchar *tbl_name, size_t tbl_name_len,
Json_writer *ctx_writer)
Expand All @@ -312,6 +336,7 @@ void dump_recorded_table_calls(THD *thd, uchar *tbl_name, size_t tbl_name_len,
dump_mrr_info_calls(&table_context->mrr_list, ctx_writer);
dump_index_read_calls(&table_context->irc_list, ctx_writer);
dump_records_in_range_calls(&table_context->rir_list, ctx_writer);
dump_count_agg_calls(&table_context->count_agg_list, ctx_writer);
}

/*
Expand Down Expand Up @@ -794,6 +819,23 @@ Optimizer_context_recorder::~Optimizer_context_recorder()
my_hash_free(&tbl_ctx_hash);
}

void Optimizer_context_recorder::record_count_agg(TABLE *tbl, longlong count)
{
table_context_for_store *table_ctx=
get_table_context(tbl->pos_in_table_list);

if (unlikely(!table_ctx))
return; // OOM

auto *count_agg_ctx= new (mem_root) count_agg;
if (unlikely(!count_agg_ctx))
return; // OOM

count_agg_ctx->call_number= ++count_agg_counter;
count_agg_ctx->count= count;
table_ctx->count_agg_list.push_back(count_agg_ctx, mem_root);
}
Comment on lines +822 to +837
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If get_table_context or the allocation of count_agg_ctx fails (e.g., due to OOM), record_count_agg returns early without incrementing count_agg_counter. However, during replay, infuse_count_agg increments count_agg_counter unconditionally. This will cause the counters to get out of sync for any subsequent count aggregates. Incrementing count_agg_counter at the very beginning of record_count_agg ensures that the counters remain synchronized even if recording fails for a specific call.

void Optimizer_context_recorder::record_count_agg(TABLE *tbl, longlong count)
{
  count_agg_counter++;
  table_context_for_store *table_ctx=
      get_table_context(tbl->pos_in_table_list);

  if (unlikely(!table_ctx))
    return; // OOM

  auto *count_agg_ctx= new (mem_root) count_agg;
  if (unlikely(!count_agg_ctx))
    return; // OOM

  count_agg_ctx->call_number= count_agg_counter;
  count_agg_ctx->count= count;
  table_ctx->count_agg_list.push_back(count_agg_ctx, mem_root);
}


bool Optimizer_context_recorder::has_records()
{
return tbl_ctx_hash.records > 0;
Expand Down Expand Up @@ -1004,6 +1046,7 @@ class table_context_for_replay : public Sql_alloc
List<Multi_range_read_const_call_record> ranges_list;
List<cost_index_read_call_record> irc_list;
List<records_in_range_call_record> rir_list;
List<count_agg> count_agg_list;
};

/*
Expand Down Expand Up @@ -1211,7 +1254,8 @@ static int parse_table_context(MEM_ROOT *mem_root, json_engine_t *je,
Read_named_member array[]= {
{"name", Read_string(mem_root, &table_ctx->name), false},
{"file_stat_records",
Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(&table_ctx->file_stat_records),
Read_non_neg_integer<ha_rows, ULONGLONG_MAX>(
&table_ctx->file_stat_records),
false},
{"read_cost_io", Read_double(&table_ctx->read_cost_io), false},
{"read_cost_cpu", Read_double(&table_ctx->read_cost_cpu), false},
Expand All @@ -1231,6 +1275,10 @@ static int parse_table_context(MEM_ROOT *mem_root, json_engine_t *je,
Read_array_into_list<records_in_range_call_record>(
mem_root, &table_ctx->rir_list, parse_records_in_range_context),
true},
{"list_count_agg",
Read_array_into_list<count_agg>(mem_root, &table_ctx->count_agg_list,
parse_count_agg_context),
true},
{NULL, Read_double(NULL), true}};

return parse_context_obj_from_json_array(je, err_buf, err_msg, array);
Expand Down Expand Up @@ -1412,6 +1460,34 @@ static int parse_records_in_range_context(MEM_ROOT *mem_root,
return parse_context_obj_from_json_array(je, err_buf, err_msg, array);
}

/*
Parses the count_agg information for reading count_agg
JSON structure of the optimizer context.
To be specific, single array element of list_count_agg
is parsed in this method.
Refer to the file opt_context_schema.inc, and
the description at the start of this file.

@return
0 OK
1 Parse Error
-1 EOF
*/
static int parse_count_agg_context(MEM_ROOT *mem_root, json_engine_t *je,
String *err_buf, count_agg *out)
{
const char *err_msg= "Expected an object in the count_agg array";

Read_named_member array[]= {
{"call_number",
Read_non_neg_integer<ulong, ULONG_MAX>(&out->call_number), false},
{"count", Read_non_neg_integer<longlong, LONGLONG_MAX>(&out->count),
false},
{NULL, Read_double(NULL), true}};

return parse_context_obj_from_json_array(je, err_buf, err_msg, array);
}

Optimizer_context_replay::Optimizer_context_replay(THD *thd_arg) : thd(thd_arg)
{
parse(); // TODO: error handling?
Expand Down Expand Up @@ -1625,10 +1701,10 @@ bool Optimizer_context_replay::infuse_index_read_cost(const TABLE *tbl,
}
}

String warn_msg;
String warn_msg(256);
warn_msg.append(tbl_name);
warn_msg.append(STRING_WITH_LEN(" with key_number:"));
warn_msg.append(keynr);
warn_msg.q_append(keynr);
warn_msg.append(STRING_WITH_LEN(", records:"));
warn_msg.q_append_int64(records);
warn_msg.append(STRING_WITH_LEN(", eq_ref:"));
Expand Down Expand Up @@ -1747,10 +1823,10 @@ bool Optimizer_context_replay::infuse_records_in_range(
}
}

String warn_msg;
String warn_msg(256);
warn_msg.append(tbl_name);
warn_msg.append(STRING_WITH_LEN(" with key_number:"));
warn_msg.append(keynr);
warn_msg.q_append(keynr);
warn_msg.append(STRING_WITH_LEN(" with min_key:"));
warn_msg.append(min_key);
warn_msg.append(STRING_WITH_LEN(" with max_key:"));
Expand All @@ -1763,6 +1839,42 @@ bool Optimizer_context_replay::infuse_records_in_range(
return true;
}

bool Optimizer_context_replay::infuse_count_agg(const TABLE *tbl,
longlong *count)
{
if (!has_records() || !is_base_table(tbl->pos_in_table_list))
return true;

String tbl_name;
append_full_table_name(tbl->pos_in_table_list, &tbl_name);
count_agg_counter++;

if (table_context_for_replay *tbl_ctx=
find_table_context(tbl_name.c_ptr_safe()))
{
List_iterator<count_agg> iter(tbl_ctx->count_agg_list);
while (count_agg *rec= iter++)
{
if (count_agg_counter == rec->call_number)
{
*count= rec->count;
return false;
}
}
}

String warn_msg(256);
warn_msg.append(tbl_name);
warn_msg.append(STRING_WITH_LEN(" with count_agg call_number: "));
warn_msg.q_append_int64(count_agg_counter);
push_warning_printf(
thd, Sql_condition::WARN_LEVEL_WARN,
ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED,
ER_THD(thd, ER_JSON_OPTIMIZER_REPLAY_CONTEXT_MATCH_FAILED),
warn_msg.c_ptr_safe(), "list_count_agg");
return true;
}

/*
@brief
restore the saved stats for the tables, and indexes that were
Expand Down
11 changes: 10 additions & 1 deletion sql/opt_context_store_replay.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class Optimizer_context_recorder
/* use table->record[0] */
record_table_row(tbl, 0);
}

void record_count_agg(TABLE *tbl, longlong count);
bool has_records();
table_context_for_store *search(uchar *tbl_name, size_t tbl_name_len);

Expand All @@ -88,6 +88,10 @@ class Optimizer_context_recorder
table_context_for_store *get_table_context(const TABLE_LIST *tbl);
static const uchar *get_tbl_ctx_key(const void *entry_, size_t *length,
my_bool flags);
/*
counter that tracks count_agg calls
*/
ulong count_agg_counter= 0;
};


Expand Down Expand Up @@ -145,6 +149,7 @@ class Optimizer_context_replay
bool infuse_records_in_range(const TABLE *tbl, const KEY_PART_INFO *key_part,
uint keynr, const key_range *min_range,
const key_range *max_range, ha_rows *records);
bool infuse_count_agg(const TABLE *tbl, longlong *count);

private:
bool infuse_table_rows(const TABLE *tbl, ha_rows *rows);
Expand All @@ -164,6 +169,10 @@ class Optimizer_context_replay
void store_range_contexts(const TABLE *tbl, const char *idx_name,
List<Multi_range_read_const_call_record> *list);
table_context_for_replay *find_table_context(const char *name);
/*
counter that tracks count_agg calls
*/
ulong count_agg_counter= 0;
#ifndef DBUG_OFF
void dbug_print_read_stats();
#endif
Expand Down
22 changes: 21 additions & 1 deletion sql/opt_sum.cc
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,28 @@ int opt_sum_query(THD *thd,
}
is_exact_count= 1; // count is now exact
}
((Item_sum_count*) item)->make_const((longlong) count);
Item_sum_count *cnt_item= static_cast<Item_sum_count *>(item);
cnt_item->make_const((longlong) count);
recalc_const_item= 1;
Item *expr= item_sum->get_arg(0);
if (expr && ((expr->used_tables() & OUTER_REF_TABLE_BIT) == 0) &&
expr->real_item()->type() == Item::FIELD_ITEM)
{
Item_field *item_field= (Item_field *) (expr->real_item());
TABLE *table= item_field->field->table;
if (Optimizer_context_replay *replay= thd->opt_ctx_replay)
{
longlong tmp_count;
if (!replay->infuse_count_agg(table, &tmp_count))
{
count= tmp_count;
}
}
if (Optimizer_context_recorder *rec= thd->opt_ctx_recorder)
{
rec->record_count_agg(table, count);
}
}
}
else
const_result= 0;
Expand Down
Loading