mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
replay loses infused statistics in stored functions
Every sub-statement (stored function, trigger) replaced thd->opt_ctx_replay, leaking the top-level one, so the top-level tables were never restored and kept key_info->read_stats pointing into the freed statement mem_root. Saved and infused statistics were allocated on whatever arena was active, e.g. the stored function execution arena, freed before the restore. * create the replay object only for a top-level statement, and let sub-statements use it; restore only at the end of the top-level one * allocate everything the replay object owns in its own MEM_ROOT * empty the saved list after restoring Assisted-by: Claude:claude-5.5-opus
This commit is contained in:
@@ -1396,4 +1396,45 @@ select json_extract(@opt_context, '$.tables[0].indexes[0].rec_per_key[0]');
|
||||
json_extract(@opt_context, '$.tables[0].indexes[0].rec_per_key[0]')
|
||||
"10"
|
||||
drop table t1;
|
||||
#
|
||||
# index statistics are restored after a replayed statement
|
||||
# that runs a stored function
|
||||
#
|
||||
create table t1 (a int, b int, key(a)) engine=innodb;
|
||||
insert into t1 select seq%10, seq from seq_1_to_100;
|
||||
create table t2 (a int, key(a)) engine=innodb;
|
||||
insert into t2 select seq%10 from seq_1_to_100;
|
||||
analyze table t1, t2 persistent for all;
|
||||
Table Op Msg_type Msg_text
|
||||
test.t1 analyze status Engine-independent statistics collected
|
||||
test.t1 analyze status OK
|
||||
test.t2 analyze status Engine-independent statistics collected
|
||||
test.t2 analyze status OK
|
||||
create function f1() returns int return (select count(*) from t2);
|
||||
select count(*) from t2;
|
||||
count(*)
|
||||
100
|
||||
set @opt_context=
|
||||
(select REGEXP_SUBSTR(
|
||||
context,
|
||||
'(?<=set @opt_context=\')([\n\r].*)*(?=\'\;#opt_context_ends)')
|
||||
from information_schema.optimizer_context);
|
||||
set @opt_context_t2= @opt_context;
|
||||
select count(*) from t1 where a=3;
|
||||
count(*)
|
||||
10
|
||||
set @opt_context=
|
||||
(select REGEXP_SUBSTR(
|
||||
context,
|
||||
'(?<=set @opt_context=\')([\n\r].*)*(?=\'\;#opt_context_ends)')
|
||||
from information_schema.optimizer_context);
|
||||
set @opt_context_rpk= json_array_append(json_replace(@opt_context,
|
||||
'$.tables[0].indexes[0].rec_per_key[0]', 77),
|
||||
'$.tables', json_extract(@opt_context_t2, '$.tables[0]'));
|
||||
set optimizer_replay_context='opt_context_rpk';
|
||||
select f1() from t1 where a=3 limit 1;
|
||||
f1()
|
||||
100
|
||||
drop function f1;
|
||||
drop table t1, t2;
|
||||
# End of 13.1 tests
|
||||
|
||||
@@ -420,4 +420,27 @@ select count(*) from t1 where a=3;
|
||||
select json_extract(@opt_context, '$.tables[0].indexes[0].rec_per_key[0]');
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # index statistics are restored after a replayed statement
|
||||
--echo # that runs a stored function
|
||||
--echo #
|
||||
create table t1 (a int, b int, key(a)) engine=innodb;
|
||||
insert into t1 select seq%10, seq from seq_1_to_100;
|
||||
create table t2 (a int, key(a)) engine=innodb;
|
||||
insert into t2 select seq%10 from seq_1_to_100;
|
||||
analyze table t1, t2 persistent for all;
|
||||
create function f1() returns int return (select count(*) from t2);
|
||||
select count(*) from t2;
|
||||
--source include/opt_context_save_in_var.inc
|
||||
set @opt_context_t2= @opt_context;
|
||||
select count(*) from t1 where a=3;
|
||||
--source include/opt_context_save_in_var.inc
|
||||
set @opt_context_rpk= json_array_append(json_replace(@opt_context,
|
||||
'$.tables[0].indexes[0].rec_per_key[0]', 77),
|
||||
'$.tables', json_extract(@opt_context_t2, '$.tables[0]'));
|
||||
set optimizer_replay_context='opt_context_rpk';
|
||||
select f1() from t1 where a=3 limit 1;
|
||||
drop function f1;
|
||||
drop table t1, t2;
|
||||
|
||||
--echo # End of 13.1 tests
|
||||
|
||||
@@ -1239,7 +1239,7 @@ static void append_table_or_view_name(const TABLE_LIST *tbl, String *buf)
|
||||
class index_context_for_replay : public Sql_alloc
|
||||
{
|
||||
public:
|
||||
index_context_for_replay() : rec_per_key(current_thd->mem_root) {}
|
||||
index_context_for_replay(MEM_ROOT *mem_root) : rec_per_key(mem_root) {}
|
||||
char *idx_name= NULL;
|
||||
Mem_root_dynamic_array<double> rec_per_key;
|
||||
static index_context_for_replay *parse(MEM_ROOT *mem_root,
|
||||
@@ -1447,7 +1447,7 @@ index_context_for_replay*
|
||||
index_context_for_replay::parse(MEM_ROOT *mem_root, json_engine_t *je,
|
||||
String *err_buf)
|
||||
{
|
||||
auto index_ctx= new (mem_root) index_context_for_replay;
|
||||
auto index_ctx= new (mem_root) index_context_for_replay(mem_root);
|
||||
if (!index_ctx)
|
||||
return NULL;
|
||||
|
||||
@@ -1480,7 +1480,7 @@ Multi_range_read_const_call_record *
|
||||
Multi_range_read_const_call_record::parse(MEM_ROOT *mem_root, json_engine_t *je,
|
||||
String *err_buf)
|
||||
{
|
||||
auto out= new Multi_range_read_const_call_record;
|
||||
auto out= new (mem_root) Multi_range_read_const_call_record;
|
||||
if (!out)
|
||||
return NULL;
|
||||
|
||||
@@ -1631,7 +1631,8 @@ records_in_range_call_record::parse(MEM_ROOT *mem_root,
|
||||
return out;
|
||||
}
|
||||
|
||||
Optimizer_context_replay::Optimizer_context_replay(THD *thd_arg) : thd(thd_arg)
|
||||
Optimizer_context_replay::Optimizer_context_replay(THD *thd_arg)
|
||||
: thd(thd_arg), mem_root(thd_arg->mem_root)
|
||||
{
|
||||
parse(); // TODO: error handling?
|
||||
}
|
||||
@@ -1648,6 +1649,17 @@ static bool sql_command_uses_opt_context(enum_sql_command sql_command)
|
||||
|
||||
void init_optimizer_context_replay_if_needed(THD *thd)
|
||||
{
|
||||
/*
|
||||
Sub-statements use the replay object of the top-level statement, which
|
||||
restores the infused statistics of all their tables and frees it.
|
||||
Statements of a stored procedure are not replayed.
|
||||
*/
|
||||
if (thd->spcont || thd->in_sub_stmt)
|
||||
return;
|
||||
|
||||
// Replay is cleaned up after query in THD::cleanup_after_query()
|
||||
DBUG_ASSERT(!thd->opt_ctx_replay);
|
||||
|
||||
/* If @@optimizer_replay_context is not empty, start the replay */
|
||||
if (thd->variables.optimizer_replay_context &&
|
||||
strlen(thd->variables.optimizer_replay_context) > 0 &&
|
||||
@@ -1912,14 +1924,14 @@ void Optimizer_context_replay::infuse_table_stats(TABLE *table)
|
||||
if (!has_records() || !is_base_table(table->pos_in_table_list))
|
||||
return;
|
||||
|
||||
Saved_table_stats *saved_ts= new Saved_table_stats();
|
||||
Saved_table_stats *saved_ts= new (mem_root) Saved_table_stats();
|
||||
|
||||
if (unlikely(!saved_ts))
|
||||
return; // OOM
|
||||
|
||||
saved_ts->table= table;
|
||||
|
||||
if (saved_table_stats.push_front(saved_ts))
|
||||
if (saved_table_stats.push_front(saved_ts, mem_root))
|
||||
return;
|
||||
|
||||
KEY *key_info, *key_info_end;
|
||||
@@ -1932,7 +1944,7 @@ void Optimizer_context_replay::infuse_table_stats(TABLE *table)
|
||||
if (!index_freq_list || !index_freq_list->size())
|
||||
continue;
|
||||
|
||||
Saved_index_stats *saved_is= new Saved_index_stats();
|
||||
Saved_index_stats *saved_is= new (mem_root) Saved_index_stats();
|
||||
|
||||
if (unlikely(!saved_is))
|
||||
return; // OOM
|
||||
@@ -1941,16 +1953,18 @@ void Optimizer_context_replay::infuse_table_stats(TABLE *table)
|
||||
Index_statistics *original_read_stats= key_info->read_stats;
|
||||
bool original_is_statistics_from_stat_tables=
|
||||
key_info->is_statistics_from_stat_tables;
|
||||
Index_statistics *new_read_stats= new Index_statistics();
|
||||
Index_statistics *new_read_stats= new (mem_root) Index_statistics();
|
||||
|
||||
if (unlikely(!new_read_stats))
|
||||
return; // OOM
|
||||
|
||||
size_t frequencies_size= sizeof(ulonglong) * num_key_parts;
|
||||
ulonglong *frequencies=
|
||||
(ulonglong *) thd->calloc(sizeof(ulonglong) * num_key_parts);
|
||||
(ulonglong *) alloc_root(mem_root, frequencies_size);
|
||||
|
||||
if (unlikely(!frequencies))
|
||||
return; // OOM
|
||||
bzero(frequencies, frequencies_size);
|
||||
|
||||
new_read_stats->init_avg_frequency(frequencies);
|
||||
key_info->read_stats= new_read_stats;
|
||||
@@ -1968,7 +1982,7 @@ void Optimizer_context_replay::infuse_table_stats(TABLE *table)
|
||||
saved_is->original_is_statistics_from_stat_tables=
|
||||
original_is_statistics_from_stat_tables;
|
||||
saved_is->original_read_stats= original_read_stats;
|
||||
saved_ts->saved_index_stats.push_back(saved_is);
|
||||
saved_ts->saved_index_stats.push_back(saved_is, mem_root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2042,6 +2056,7 @@ void Optimizer_context_replay::restore_modified_table_stats()
|
||||
key->read_stats= saved_is->original_read_stats;
|
||||
}
|
||||
}
|
||||
saved_table_stats.empty();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2072,7 +2087,7 @@ bool Optimizer_context_replay::parse()
|
||||
Read_named_member members[]=
|
||||
{
|
||||
{"tables",
|
||||
Read_object_array<table_context_for_replay>(thd->mem_root, &ctx_list,
|
||||
Read_object_array<table_context_for_replay>(mem_root, &ctx_list,
|
||||
table_context_for_replay::parse),
|
||||
false
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright (c) 2025, MariaDB
|
||||
Copyright (c) 2025, 2026, MariaDB plc
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -177,6 +177,11 @@ void init_optimizer_context_replay_if_needed(THD *thd);
|
||||
thd->opt_ctx_replay->infuse_XXX()
|
||||
|
||||
to get "infuse" the statistics records from the context.
|
||||
|
||||
- The object is created by a top-level statement and is used by its
|
||||
sub-statements (stored functions, triggers) too. Everything it allocates
|
||||
is on the mem_root of the top-level statement, which outlives the object,
|
||||
independently of the arena that is active when a statistic is infused.
|
||||
*/
|
||||
|
||||
class Optimizer_context_replay
|
||||
@@ -215,6 +220,7 @@ public:
|
||||
|
||||
private:
|
||||
THD *thd;
|
||||
MEM_ROOT *mem_root;
|
||||
/*
|
||||
counter that tracks infuse_range_stats() calls
|
||||
*/
|
||||
|
||||
@@ -5922,7 +5922,7 @@ finish:
|
||||
if (!thd->is_error() && !res)
|
||||
res= store_optimizer_context(thd);
|
||||
|
||||
if (thd->opt_ctx_replay)
|
||||
if (thd->opt_ctx_replay && !thd->spcont && !thd->in_sub_stmt)
|
||||
thd->opt_ctx_replay->restore_modified_table_stats();
|
||||
|
||||
if (res || thd->is_error())
|
||||
|
||||
Reference in New Issue
Block a user