mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Thread safe statistics loading
Previously multiple threads were allowed to load statistics concurrently. There were no known problems caused by this. But given amount of data races in this code, it'd happen sooner or later. To avoid scalability bottleneck, statistics loading is protected by per-TABLE_SHARE atomic variable. Whenever statistics were loaded by preceding statement (hot-path), a scalable load-acquire check is performed. Whenever statistics have to be loaded anew, mutual exclusion for loaders is established by atomic variable. If statistics are being loaded concurrently, statement waits until load is completed. TABLE_STATISTICS_CB::stats_can_be_read and TABLE_STATISTICS_CB::stats_is_read are replaced with a tri state atomic variable. Part of MDEV-19061 - table_share used for reading statistical tables is not protected
This commit is contained in:
@ -2204,27 +2204,13 @@ static int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share)
|
|||||||
|
|
||||||
DBUG_ENTER("alloc_statistics_for_table_share");
|
DBUG_ENTER("alloc_statistics_for_table_share");
|
||||||
|
|
||||||
DEBUG_SYNC(thd, "statistics_mem_alloc_start1");
|
|
||||||
DEBUG_SYNC(thd, "statistics_mem_alloc_start2");
|
|
||||||
|
|
||||||
mysql_mutex_lock(&table_share->LOCK_share);
|
|
||||||
|
|
||||||
if (stats_cb->stats_can_be_read)
|
|
||||||
{
|
|
||||||
mysql_mutex_unlock(&table_share->LOCK_share);
|
|
||||||
DBUG_RETURN(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Table_statistics *table_stats= stats_cb->table_stats;
|
Table_statistics *table_stats= stats_cb->table_stats;
|
||||||
if (!table_stats)
|
if (!table_stats)
|
||||||
{
|
{
|
||||||
table_stats= (Table_statistics *) alloc_root(&stats_cb->mem_root,
|
table_stats= (Table_statistics *) alloc_root(&stats_cb->mem_root,
|
||||||
sizeof(Table_statistics));
|
sizeof(Table_statistics));
|
||||||
if (!table_stats)
|
if (!table_stats)
|
||||||
{
|
|
||||||
mysql_mutex_unlock(&table_share->LOCK_share);
|
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
|
||||||
memset(table_stats, 0, sizeof(Table_statistics));
|
memset(table_stats, 0, sizeof(Table_statistics));
|
||||||
stats_cb->table_stats= table_stats;
|
stats_cb->table_stats= table_stats;
|
||||||
}
|
}
|
||||||
@ -2290,13 +2276,7 @@ static int alloc_statistics_for_table_share(THD* thd, TABLE_SHARE *table_share)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DBUG_RETURN(column_stats && index_stats && idx_avg_frequency ? 0 : 1);
|
||||||
if (column_stats && index_stats && idx_avg_frequency)
|
|
||||||
stats_cb->stats_can_be_read= TRUE;
|
|
||||||
|
|
||||||
mysql_mutex_unlock(&table_share->LOCK_share);
|
|
||||||
|
|
||||||
DBUG_RETURN(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2913,11 +2893,22 @@ int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables)
|
|||||||
Field **field_ptr;
|
Field **field_ptr;
|
||||||
KEY *key_info, *key_info_end;
|
KEY *key_info, *key_info_end;
|
||||||
TABLE_SHARE *table_share= table->s;
|
TABLE_SHARE *table_share= table->s;
|
||||||
Table_statistics *read_stats= table_share->stats_cb.table_stats;
|
|
||||||
|
|
||||||
DBUG_ENTER("read_statistics_for_table");
|
DBUG_ENTER("read_statistics_for_table");
|
||||||
|
DEBUG_SYNC(thd, "statistics_mem_alloc_start1");
|
||||||
|
DEBUG_SYNC(thd, "statistics_mem_alloc_start2");
|
||||||
|
|
||||||
|
if (!table_share->stats_cb.start_stats_load())
|
||||||
|
DBUG_RETURN(table_share->stats_cb.stats_are_ready() ? 0 : 1);
|
||||||
|
|
||||||
|
if (alloc_statistics_for_table_share(thd, table_share))
|
||||||
|
{
|
||||||
|
table_share->stats_cb.abort_stats_load();
|
||||||
|
DBUG_RETURN(1);
|
||||||
|
}
|
||||||
|
|
||||||
/* Read statistics from the statistical table table_stats */
|
/* Read statistics from the statistical table table_stats */
|
||||||
|
Table_statistics *read_stats= table_share->stats_cb.table_stats;
|
||||||
stat_table= stat_tables[TABLE_STAT].table;
|
stat_table= stat_tables[TABLE_STAT].table;
|
||||||
Table_stat table_stat(stat_table, table);
|
Table_stat table_stat(stat_table, table);
|
||||||
table_stat.set_key_fields();
|
table_stat.set_key_fields();
|
||||||
@ -2995,9 +2986,7 @@ int read_statistics_for_table(THD *thd, TABLE *table, TABLE_LIST *stat_tables)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
table_share->stats_cb.end_stats_load();
|
||||||
table->stats_is_read= TRUE;
|
|
||||||
|
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3146,6 +3135,23 @@ int read_statistics_for_tables_if_needed(THD *thd, TABLE_LIST *tables)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void dump_stats_from_share_to_table(TABLE *table)
|
||||||
|
{
|
||||||
|
TABLE_SHARE *table_share= table->s;
|
||||||
|
KEY *key_info= table_share->key_info;
|
||||||
|
KEY *key_info_end= key_info + table_share->keys;
|
||||||
|
KEY *table_key_info= table->key_info;
|
||||||
|
for ( ; key_info < key_info_end; key_info++, table_key_info++)
|
||||||
|
table_key_info->read_stats= key_info->read_stats;
|
||||||
|
|
||||||
|
Field **field_ptr= table_share->field;
|
||||||
|
Field **table_field_ptr= table->field;
|
||||||
|
for ( ; *field_ptr; field_ptr++, table_field_ptr++)
|
||||||
|
(*table_field_ptr)->read_stats= (*field_ptr)->read_stats;
|
||||||
|
table->stats_is_read= true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int read_statistics_for_tables(THD *thd, TABLE_LIST *tables)
|
int read_statistics_for_tables(THD *thd, TABLE_LIST *tables)
|
||||||
{
|
{
|
||||||
TABLE_LIST stat_tables[STATISTICS_TABLES];
|
TABLE_LIST stat_tables[STATISTICS_TABLES];
|
||||||
@ -3167,30 +3173,17 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables)
|
|||||||
{
|
{
|
||||||
if (table_share->table_category == TABLE_CATEGORY_USER)
|
if (table_share->table_category == TABLE_CATEGORY_USER)
|
||||||
{
|
{
|
||||||
if (table_share->stats_cb.stats_can_be_read ||
|
if (table_share->stats_cb.stats_are_ready())
|
||||||
!alloc_statistics_for_table_share(thd, table_share))
|
|
||||||
{
|
{
|
||||||
if (table_share->stats_cb.stats_can_be_read)
|
if (!tl->table->stats_is_read)
|
||||||
{
|
dump_stats_from_share_to_table(tl->table);
|
||||||
KEY *key_info= table_share->key_info;
|
tl->table->histograms_are_read=
|
||||||
KEY *key_info_end= key_info + table_share->keys;
|
table_share->stats_cb.histograms_are_read;
|
||||||
KEY *table_key_info= tl->table->key_info;
|
if (table_share->stats_cb.histograms_are_read ||
|
||||||
for ( ; key_info < key_info_end; key_info++, table_key_info++)
|
thd->variables.optimizer_use_condition_selectivity <= 3)
|
||||||
table_key_info->read_stats= key_info->read_stats;
|
continue;
|
||||||
Field **field_ptr= table_share->field;
|
|
||||||
Field **table_field_ptr= tl->table->field;
|
|
||||||
for ( ; *field_ptr; field_ptr++, table_field_ptr++)
|
|
||||||
(*table_field_ptr)->read_stats= (*field_ptr)->read_stats;
|
|
||||||
tl->table->stats_is_read= table_share->stats_cb.stats_is_read;
|
|
||||||
tl->table->histograms_are_read=
|
|
||||||
table_share->stats_cb.histograms_are_read;
|
|
||||||
|
|
||||||
if (!tl->table->stats_is_read ||
|
|
||||||
(!table_share->stats_cb.histograms_are_read &&
|
|
||||||
thd->variables.optimizer_use_condition_selectivity > 3))
|
|
||||||
statistics_for_tables_is_needed= true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
statistics_for_tables_is_needed= true;
|
||||||
}
|
}
|
||||||
else if (is_stat_table(tl->db, tl->alias))
|
else if (is_stat_table(tl->db, tl->alias))
|
||||||
found_stat_table= true;
|
found_stat_table= true;
|
||||||
@ -3217,16 +3210,14 @@ int read_statistics_for_tables(THD *thd, TABLE_LIST *tables)
|
|||||||
table_share->tmp_table == NO_TMP_TABLE &&
|
table_share->tmp_table == NO_TMP_TABLE &&
|
||||||
table_share->table_category == TABLE_CATEGORY_USER)
|
table_share->table_category == TABLE_CATEGORY_USER)
|
||||||
{
|
{
|
||||||
if (table_share->stats_cb.stats_can_be_read &&
|
if (!tl->table->stats_is_read)
|
||||||
!table_share->stats_cb.stats_is_read)
|
|
||||||
{
|
{
|
||||||
(void) read_statistics_for_table(thd, tl->table, stat_tables);
|
if (!read_statistics_for_table(thd, tl->table, stat_tables))
|
||||||
table_share->stats_cb.stats_is_read= TRUE;
|
dump_stats_from_share_to_table(tl->table);
|
||||||
|
else
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (table_share->stats_cb.stats_is_read)
|
|
||||||
tl->table->stats_is_read= TRUE;
|
|
||||||
if (thd->variables.optimizer_use_condition_selectivity > 3 &&
|
if (thd->variables.optimizer_use_condition_selectivity > 3 &&
|
||||||
table_share->stats_cb.stats_can_be_read &&
|
|
||||||
!table_share->stats_cb.histograms_are_read)
|
!table_share->stats_cb.histograms_are_read)
|
||||||
{
|
{
|
||||||
(void) read_histograms_for_table(thd, tl->table, stat_tables);
|
(void) read_histograms_for_table(thd, tl->table, stat_tables);
|
||||||
|
@ -416,8 +416,6 @@ void TABLE_SHARE::destroy()
|
|||||||
|
|
||||||
delete_stat_values_for_table_share(this);
|
delete_stat_values_for_table_share(this);
|
||||||
free_root(&stats_cb.mem_root, MYF(0));
|
free_root(&stats_cb.mem_root, MYF(0));
|
||||||
stats_cb.stats_can_be_read= FALSE;
|
|
||||||
stats_cb.stats_is_read= FALSE;
|
|
||||||
stats_cb.histograms_can_be_read= FALSE;
|
stats_cb.histograms_can_be_read= FALSE;
|
||||||
stats_cb.histograms_are_read= FALSE;
|
stats_cb.histograms_are_read= FALSE;
|
||||||
|
|
||||||
|
75
sql/table.h
75
sql/table.h
@ -594,15 +594,82 @@ enum open_frm_error {
|
|||||||
from persistent statistical tables
|
from persistent statistical tables
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct TABLE_STATISTICS_CB
|
class TABLE_STATISTICS_CB
|
||||||
{
|
{
|
||||||
|
class Statistics_state
|
||||||
|
{
|
||||||
|
enum state_codes
|
||||||
|
{
|
||||||
|
EMPTY, /** data is not loaded */
|
||||||
|
LOADING, /** data is being loaded in some connection */
|
||||||
|
READY /** data is loaded and available for use */
|
||||||
|
};
|
||||||
|
int32 state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** No state copy */
|
||||||
|
Statistics_state &operator=(const Statistics_state &) { return *this; }
|
||||||
|
|
||||||
|
/** Checks if data loading have been completed */
|
||||||
|
bool is_ready() const
|
||||||
|
{
|
||||||
|
return my_atomic_load32_explicit(const_cast<int32*>(&state),
|
||||||
|
MY_MEMORY_ORDER_ACQUIRE) == READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets mutual exclusion for data loading
|
||||||
|
|
||||||
|
If stats are in LOADING state, waits until state change.
|
||||||
|
|
||||||
|
@return
|
||||||
|
@retval true atomic EMPTY -> LOADING transfer completed, ok to load
|
||||||
|
@retval false stats are in READY state, no need to load
|
||||||
|
*/
|
||||||
|
bool start_load()
|
||||||
|
{
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
int32 expected= EMPTY;
|
||||||
|
if (my_atomic_cas32_weak_explicit(&state, &expected, LOADING,
|
||||||
|
MY_MEMORY_ORDER_RELAXED,
|
||||||
|
MY_MEMORY_ORDER_RELAXED))
|
||||||
|
return true;
|
||||||
|
if (expected == READY)
|
||||||
|
return false;
|
||||||
|
(void) LF_BACKOFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Marks data available for subsequent use */
|
||||||
|
void end_load()
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(my_atomic_load32_explicit(&state, MY_MEMORY_ORDER_RELAXED) ==
|
||||||
|
LOADING);
|
||||||
|
my_atomic_store32_explicit(&state, READY, MY_MEMORY_ORDER_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Restores empty state on error (e.g. OOM) */
|
||||||
|
void abort_load()
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(my_atomic_load32_explicit(&state, MY_MEMORY_ORDER_RELAXED) ==
|
||||||
|
LOADING);
|
||||||
|
my_atomic_store32_explicit(&state, EMPTY, MY_MEMORY_ORDER_RELAXED);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Statistics_state stats_state;
|
||||||
|
|
||||||
|
public:
|
||||||
MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */
|
MEM_ROOT mem_root; /* MEM_ROOT to allocate statistical data for the table */
|
||||||
Table_statistics *table_stats; /* Structure to access the statistical data */
|
Table_statistics *table_stats; /* Structure to access the statistical data */
|
||||||
bool stats_can_be_read; /* Memory for statistical data is allocated */
|
|
||||||
bool stats_is_read; /* Statistical data for table has been read
|
|
||||||
from statistical tables */
|
|
||||||
bool histograms_can_be_read;
|
bool histograms_can_be_read;
|
||||||
bool histograms_are_read;
|
bool histograms_are_read;
|
||||||
|
|
||||||
|
bool stats_are_ready() const { return stats_state.is_ready(); }
|
||||||
|
bool start_stats_load() { return stats_state.start_load(); }
|
||||||
|
void end_stats_load() { stats_state.end_load(); }
|
||||||
|
void abort_stats_load() { stats_state.abort_load(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user