mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
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
This commit is contained in:
@ -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;
|
|||||||
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
|
|||||||
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
|
|||||||
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 @@ dummy_empty:
|
|||||||
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 @@ invalid:
|
|||||||
|
|
||||||
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 @@ invalid:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************************************//**
|
/*********************************************************************//**
|
||||||
@ -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(
|
|||||||
|
|
||||||
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(
|
|||||||
|
|
||||||
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(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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(
|
|||||||
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
|
|||||||
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 @@ empty_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 @@ found_level:
|
|||||||
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(
|
|||||||
|
|
||||||
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(
|
|||||||
/*********************************************************************//**
|
/*********************************************************************//**
|
||||||
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(
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 @@ invalid_table_id:
|
|||||||
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 @@ done:
|
|||||||
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)
|
||||||
|
Reference in New Issue
Block a user