MDEV-28327 InnoDB persistent statistics fail to update after bulk insert

- Background statistics thread should keep the table in the
statistics queue itself when the table under bulk insert operation

dict_stats_analyze_index(): Set the maximum value for index_stats_t
if the table is in bulk operation

dict_stats_update(), dict_stats_update_transient_for_index(),
dict_stats_update_transient(): Returns DB_SUCCESS_LOCKED_REC
if the table under bulk insert operation

dict_stats_process_entry_from_recalc_pool(): Add the table
back to recalc pool if the table under bulk insert operation
parent 0f93d803
...@@ -182,3 +182,14 @@ CREATE TABLE t (i INT) ENGINE=InnoDB PARTITION BY HASH (i) PARTITIONS 2; ...@@ -182,3 +182,14 @@ CREATE TABLE t (i INT) ENGINE=InnoDB PARTITION BY HASH (i) PARTITIONS 2;
INSERT INTO t VALUES (0); INSERT INTO t VALUES (0);
INSERT INTO t VALUES (1),(0),(1); INSERT INTO t VALUES (1),(0),(1);
DROP TABLE t; DROP TABLE t;
#
# MDEV-28327 InnoDB persistent statistics fail to update
# after bulk insert
#
CREATE TABLE t1 (a INT PRIMARY KEY)ENGINE=InnoDB;
INSERT INTO t1 SELECT * FROM seq_1_to_4096;
# Wait till statistics update after bulk insert operation
SELECT n_rows FROM mysql.innodb_table_stats WHERE TABLE_NAME="t1";
n_rows
4096
DROP TABLE t1;
...@@ -193,3 +193,16 @@ CREATE TABLE t (i INT) ENGINE=InnoDB PARTITION BY HASH (i) PARTITIONS 2; ...@@ -193,3 +193,16 @@ CREATE TABLE t (i INT) ENGINE=InnoDB PARTITION BY HASH (i) PARTITIONS 2;
INSERT INTO t VALUES (0); INSERT INTO t VALUES (0);
INSERT INTO t VALUES (1),(0),(1); INSERT INTO t VALUES (1),(0),(1);
DROP TABLE t; DROP TABLE t;
--echo #
--echo # MDEV-28327 InnoDB persistent statistics fail to update
--echo # after bulk insert
--echo #
CREATE TABLE t1 (a INT PRIMARY KEY)ENGINE=InnoDB;
INSERT INTO t1 SELECT * FROM seq_1_to_4096;
--echo # Wait till statistics update after bulk insert operation
let $wait_condition= select n_rows > 100 from mysql.innodb_table_stats
where table_name="t1";
source include/wait_condition.inc;
SELECT n_rows FROM mysql.innodb_table_stats WHERE TABLE_NAME="t1";
DROP TABLE t1;
...@@ -1036,6 +1036,12 @@ struct index_field_stats_t ...@@ -1036,6 +1036,12 @@ struct index_field_stats_t
n_non_null_key_vals(n_non_null_key_vals) n_non_null_key_vals(n_non_null_key_vals)
{ {
} }
bool is_bulk_operation() const
{
return n_diff_key_vals == UINT64_MAX &&
n_sample_sizes == UINT64_MAX && n_non_null_key_vals == UINT64_MAX;
}
}; };
/*******************************************************************//** /*******************************************************************//**
...@@ -1377,13 +1383,16 @@ relatively quick and is used to calculate transient statistics that ...@@ -1377,13 +1383,16 @@ relatively quick and is used to calculate transient statistics that
are not saved on disk. This was the only way to calculate statistics are not saved on disk. This was the only way to calculate statistics
before the Persistent Statistics feature was introduced. before the Persistent Statistics feature was introduced.
This function doesn't update the defragmentation related stats. This function doesn't update the defragmentation related stats.
Only persistent statistics supports defragmentation stats. */ Only persistent statistics supports defragmentation stats.
@return error code
@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation */
static static
void dberr_t
dict_stats_update_transient_for_index( dict_stats_update_transient_for_index(
/*==================================*/ /*==================================*/
dict_index_t* index) /*!< in/out: index */ dict_index_t* index) /*!< in/out: index */
{ {
dberr_t err = DB_SUCCESS;
if (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO if (srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
&& (srv_force_recovery >= SRV_FORCE_NO_LOG_REDO && (srv_force_recovery >= SRV_FORCE_NO_LOG_REDO
|| !dict_index_is_clust(index))) { || !dict_index_is_clust(index))) {
...@@ -1397,6 +1406,7 @@ dict_stats_update_transient_for_index( ...@@ -1397,6 +1406,7 @@ dict_stats_update_transient_for_index(
index->table->stats_mutex_lock(); index->table->stats_mutex_lock();
dict_stats_empty_index(index, false); dict_stats_empty_index(index, false);
index->table->stats_mutex_unlock(); index->table->stats_mutex_unlock();
return err;
#if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG #if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG
} else if (ibuf_debug && !dict_index_is_clust(index)) { } else if (ibuf_debug && !dict_index_is_clust(index)) {
goto dummy_empty; goto dummy_empty;
...@@ -1420,6 +1430,7 @@ dict_stats_update_transient_for_index( ...@@ -1420,6 +1430,7 @@ dict_stats_update_transient_for_index(
const auto bulk_trx_id = index->table->bulk_trx_id; const auto bulk_trx_id = index->table->bulk_trx_id;
if (bulk_trx_id && trx_sys.find(nullptr, bulk_trx_id, false)) { if (bulk_trx_id && trx_sys.find(nullptr, bulk_trx_id, false)) {
err= DB_SUCCESS_LOCKED_REC;
goto invalid; goto invalid;
} }
...@@ -1461,6 +1472,8 @@ dict_stats_update_transient_for_index( ...@@ -1461,6 +1472,8 @@ dict_stats_update_transient_for_index(
} }
} }
} }
return err;
} }
/*********************************************************************//** /*********************************************************************//**
...@@ -1468,9 +1481,11 @@ Calculates new estimates for table and index statistics. This function ...@@ -1468,9 +1481,11 @@ Calculates new estimates for table and index statistics. This function
is relatively quick and is used to calculate transient statistics that is relatively quick and is used to calculate transient statistics that
are not saved on disk. are not saved on disk.
This was the only way to calculate statistics before the This was the only way to calculate statistics before the
Persistent Statistics feature was introduced. */ Persistent Statistics feature was introduced.
@return error code
@retval DB_SUCCESS_LOCKED REC if the table under bulk insert operation */
static static
void dberr_t
dict_stats_update_transient( dict_stats_update_transient(
/*========================*/ /*========================*/
dict_table_t* table) /*!< in/out: table */ dict_table_t* table) /*!< in/out: table */
...@@ -1479,6 +1494,7 @@ dict_stats_update_transient( ...@@ -1479,6 +1494,7 @@ dict_stats_update_transient(
dict_index_t* index; dict_index_t* index;
ulint sum_of_index_sizes = 0; ulint sum_of_index_sizes = 0;
dberr_t err = DB_SUCCESS;
/* Find out the sizes of the indexes and how many different values /* Find out the sizes of the indexes and how many different values
for the key they approximately have */ for the key they approximately have */
...@@ -1487,15 +1503,15 @@ dict_stats_update_transient( ...@@ -1487,15 +1503,15 @@ dict_stats_update_transient(
if (!table->space) { if (!table->space) {
/* Nothing to do. */ /* Nothing to do. */
empty_table:
dict_stats_empty_table(table, true); dict_stats_empty_table(table, true);
return; return err;
} else if (index == NULL) { } else if (index == NULL) {
/* Table definition is corrupt */ /* Table definition is corrupt */
ib::warn() << "Table " << table->name ib::warn() << "Table " << table->name
<< " has no indexes. Cannot calculate statistics."; << " has no indexes. Cannot calculate statistics.";
dict_stats_empty_table(table, true); goto empty_table;
return;
} }
for (; index != NULL; index = dict_table_get_next_index(index)) { for (; index != NULL; index = dict_table_get_next_index(index)) {
...@@ -1507,14 +1523,15 @@ dict_stats_update_transient( ...@@ -1507,14 +1523,15 @@ dict_stats_update_transient(
} }
if (dict_stats_should_ignore_index(index) if (dict_stats_should_ignore_index(index)
|| !index->is_readable()) { || !index->is_readable()
|| err == DB_SUCCESS_LOCKED_REC) {
index->table->stats_mutex_lock(); index->table->stats_mutex_lock();
dict_stats_empty_index(index, false); dict_stats_empty_index(index, false);
index->table->stats_mutex_unlock(); index->table->stats_mutex_unlock();
continue; continue;
} }
dict_stats_update_transient_for_index(index); err = dict_stats_update_transient_for_index(index);
sum_of_index_sizes += index->stat_index_size; sum_of_index_sizes += index->stat_index_size;
} }
...@@ -1538,6 +1555,8 @@ dict_stats_update_transient( ...@@ -1538,6 +1555,8 @@ dict_stats_update_transient(
table->stat_initialized = TRUE; table->stat_initialized = TRUE;
table->stats_mutex_unlock(); table->stats_mutex_unlock();
return err;
} }
/* @{ Pseudo code about the relation between the following functions /* @{ Pseudo code about the relation between the following functions
...@@ -2420,6 +2439,19 @@ struct index_stats_t ...@@ -2420,6 +2439,19 @@ struct index_stats_t
for (ulint i= 0; i < n_uniq; ++i) for (ulint i= 0; i < n_uniq; ++i)
stats.push_back(index_field_stats_t{0, 1, 0}); stats.push_back(index_field_stats_t{0, 1, 0});
} }
void set_bulk_operation()
{
memset((void*) &stats[0], 0xff, stats.size() * sizeof stats[0]);
}
bool is_bulk_operation() const
{
for (auto &s : stats)
if (!s.is_bulk_operation())
return false;
return true;
}
}; };
/** Set dict_index_t::stat_n_diff_key_vals[] and stat_n_sample_sizes[]. /** Set dict_index_t::stat_n_diff_key_vals[] and stat_n_sample_sizes[].
...@@ -2549,8 +2581,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index) ...@@ -2549,8 +2581,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
const auto bulk_trx_id = index->table->bulk_trx_id; const auto bulk_trx_id = index->table->bulk_trx_id;
if (bulk_trx_id && trx_sys.find(nullptr, bulk_trx_id, false)) { if (bulk_trx_id && trx_sys.find(nullptr, bulk_trx_id, false)) {
result.index_size = 1; result.set_bulk_operation();
result.n_leaf_pages = 1;
goto empty_index; goto empty_index;
} }
...@@ -2812,7 +2843,8 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index) ...@@ -2812,7 +2843,8 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
Calculates new estimates for table and index statistics. This function Calculates new estimates for table and index statistics. This function
is relatively slow and is used to calculate persistent statistics that is relatively slow and is used to calculate persistent statistics that
will be saved on disk. will be saved on disk.
@return DB_SUCCESS or error code */ @return DB_SUCCESS or error code
@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation */
static static
dberr_t dberr_t
dict_stats_update_persistent( dict_stats_update_persistent(
...@@ -2846,6 +2878,10 @@ dict_stats_update_persistent( ...@@ -2846,6 +2878,10 @@ dict_stats_update_persistent(
index_stats_t stats = dict_stats_analyze_index(index); index_stats_t stats = dict_stats_analyze_index(index);
if (stats.is_bulk_operation()) {
return DB_SUCCESS_LOCKED_REC;
}
table->stats_mutex_lock(); table->stats_mutex_lock();
index->stat_index_size = stats.index_size; index->stat_index_size = stats.index_size;
index->stat_n_leaf_pages = stats.n_leaf_pages; index->stat_n_leaf_pages = stats.n_leaf_pages;
...@@ -3841,7 +3877,8 @@ dict_stats_update_for_index( ...@@ -3841,7 +3877,8 @@ dict_stats_update_for_index(
/*********************************************************************//** /*********************************************************************//**
Calculates new estimates for table and index statistics. The statistics Calculates new estimates for table and index statistics. The statistics
are used in query optimization. are used in query optimization.
@return DB_SUCCESS or error code */ @return DB_SUCCESS or error code
@retval DB_SUCCESS_LOCKED_REC if the table under bulk insert operation */
dberr_t dberr_t
dict_stats_update( dict_stats_update(
/*==============*/ /*==============*/
...@@ -4054,9 +4091,7 @@ dict_stats_update( ...@@ -4054,9 +4091,7 @@ dict_stats_update(
} }
transient: transient:
dict_stats_update_transient(table); return dict_stats_update_transient(table);
return(DB_SUCCESS);
} }
/** Execute DELETE FROM mysql.innodb_table_stats /** Execute DELETE FROM mysql.innodb_table_stats
......
...@@ -339,8 +339,9 @@ static bool dict_stats_process_entry_from_recalc_pool(THD *thd) ...@@ -339,8 +339,9 @@ static bool dict_stats_process_entry_from_recalc_pool(THD *thd)
const bool update_now= const bool update_now=
difftime(time(nullptr), table->stats_last_recalc) >= MIN_RECALC_INTERVAL; difftime(time(nullptr), table->stats_last_recalc) >= MIN_RECALC_INTERVAL;
if (update_now) const dberr_t err= update_now
dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT); ? dict_stats_update(table, DICT_STATS_RECALC_PERSISTENT)
: DB_SUCCESS_LOCKED_REC;
dict_table_close(table, false, thd, mdl); dict_table_close(table, false, thd, mdl);
...@@ -361,7 +362,7 @@ static bool dict_stats_process_entry_from_recalc_pool(THD *thd) ...@@ -361,7 +362,7 @@ static bool dict_stats_process_entry_from_recalc_pool(THD *thd)
ut_ad(i->state == recalc::IN_PROGRESS); ut_ad(i->state == recalc::IN_PROGRESS);
recalc_pool.erase(i); recalc_pool.erase(i);
const bool reschedule= !update_now && recalc_pool.empty(); const bool reschedule= !update_now && recalc_pool.empty();
if (!update_now) if (err == DB_SUCCESS_LOCKED_REC)
recalc_pool.emplace_back(recalc{table_id, recalc::IDLE}); recalc_pool.emplace_back(recalc{table_id, recalc::IDLE});
mysql_mutex_unlock(&recalc_pool_mutex); mysql_mutex_unlock(&recalc_pool_mutex);
if (reschedule) if (reschedule)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment