mirror of
https://github.com/MariaDB/server.git
synced 2025-09-05 09:02:06 +02:00

dict_table_open_on_id(): Simplify the logic. dict_stats: A helper for acquiring MDL and opening the tables mysql.innodb_table_stats and mysql.innodb_index_stats. innodb_ft_aux_table_validate(): Contiguously hold dict_sys.latch while accessing the table that we open with dict_table_open_on_name(). lock_table_children(): Do not hold a table reference while invoking dict_acquire_mdl_shared<false>(), which may temporarily release and reacquire the shared dict_sys.latch that we are holding. With these changes, no caller of dict_acquire_mdl_shared<false> should be holding a table reference. All remaining calls to dict_table_open_on_name(dict_locked=false) except the one in fts_lock_table() and possibly in the DDL recovery predicate innodb_check_version() should be protected by MDL, but there currently is no assertion that would enforce this. Reviewed by: Debarun Banerjee
366 lines
11 KiB
C++
366 lines
11 KiB
C++
/*****************************************************************************
|
|
|
|
Copyright (c) 2016, 2022, MariaDB Corporation.
|
|
|
|
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 the Free Software
|
|
Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along with
|
|
this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
|
|
|
|
*****************************************************************************/
|
|
|
|
/**************************************************//**
|
|
@file dict/dict0defrag_bg.cc
|
|
Defragmentation routines.
|
|
|
|
Created 25/08/2016 Jan Lindström
|
|
*******************************************************/
|
|
|
|
#include "dict0dict.h"
|
|
#include "dict0stats.h"
|
|
#include "dict0stats_bg.h"
|
|
#include "dict0defrag_bg.h"
|
|
#include "btr0btr.h"
|
|
#include "srv0start.h"
|
|
#include "trx0trx.h"
|
|
#include "lock0lock.h"
|
|
#include "row0mysql.h"
|
|
|
|
static mysql_mutex_t defrag_pool_mutex;
|
|
|
|
/** Iterator type for iterating over the elements of objects of type
|
|
defrag_pool_t. */
|
|
typedef defrag_pool_t::iterator defrag_pool_iterator_t;
|
|
|
|
/** Pool where we store information on which tables are to be processed
|
|
by background defragmentation. */
|
|
defrag_pool_t defrag_pool;
|
|
|
|
|
|
/*****************************************************************//**
|
|
Initialize the defrag pool, called once during thread initialization. */
|
|
void
|
|
dict_defrag_pool_init(void)
|
|
/*=======================*/
|
|
{
|
|
ut_ad(!srv_read_only_mode);
|
|
mysql_mutex_init(0, &defrag_pool_mutex, nullptr);
|
|
}
|
|
|
|
/*****************************************************************//**
|
|
Free the resources occupied by the defrag pool, called once during
|
|
thread de-initialization. */
|
|
void
|
|
dict_defrag_pool_deinit(void)
|
|
/*=========================*/
|
|
{
|
|
ut_ad(!srv_read_only_mode);
|
|
|
|
mysql_mutex_destroy(&defrag_pool_mutex);
|
|
}
|
|
|
|
/*****************************************************************//**
|
|
Get an index from the auto defrag pool. The returned index id is removed
|
|
from the pool.
|
|
@return true if the pool was non-empty and "id" was set, false otherwise */
|
|
static
|
|
bool
|
|
dict_stats_defrag_pool_get(
|
|
/*=======================*/
|
|
table_id_t* table_id, /*!< out: table id, or unmodified if
|
|
list is empty */
|
|
index_id_t* index_id) /*!< out: index id, or unmodified if
|
|
list is empty */
|
|
{
|
|
ut_ad(!srv_read_only_mode);
|
|
|
|
mysql_mutex_lock(&defrag_pool_mutex);
|
|
|
|
if (defrag_pool.empty()) {
|
|
mysql_mutex_unlock(&defrag_pool_mutex);
|
|
return(false);
|
|
}
|
|
|
|
defrag_pool_item_t& item = defrag_pool.back();
|
|
*table_id = item.table_id;
|
|
*index_id = item.index_id;
|
|
|
|
defrag_pool.pop_back();
|
|
|
|
mysql_mutex_unlock(&defrag_pool_mutex);
|
|
|
|
return(true);
|
|
}
|
|
|
|
/*****************************************************************//**
|
|
Add an index in a table to the defrag pool, which is processed by the
|
|
background stats gathering thread. Only the table id and index id are
|
|
added to the list, so the table can be closed after being enqueued and
|
|
it will be opened when needed. If the table or index does not exist later
|
|
(has been DROPped), then it will be removed from the pool and skipped. */
|
|
void
|
|
dict_stats_defrag_pool_add(
|
|
/*=======================*/
|
|
const dict_index_t* index) /*!< in: table to add */
|
|
{
|
|
defrag_pool_item_t item;
|
|
|
|
ut_ad(!srv_read_only_mode);
|
|
|
|
mysql_mutex_lock(&defrag_pool_mutex);
|
|
|
|
/* quit if already in the list */
|
|
for (defrag_pool_iterator_t iter = defrag_pool.begin();
|
|
iter != defrag_pool.end();
|
|
++iter) {
|
|
if ((*iter).table_id == index->table->id
|
|
&& (*iter).index_id == index->id) {
|
|
mysql_mutex_unlock(&defrag_pool_mutex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
item.table_id = index->table->id;
|
|
item.index_id = index->id;
|
|
defrag_pool.push_back(item);
|
|
if (defrag_pool.size() == 1) {
|
|
/* Kick off dict stats optimizer work */
|
|
dict_stats_schedule_now();
|
|
}
|
|
mysql_mutex_unlock(&defrag_pool_mutex);
|
|
}
|
|
|
|
/*****************************************************************//**
|
|
Delete a given index from the auto defrag pool. */
|
|
void
|
|
dict_stats_defrag_pool_del(
|
|
/*=======================*/
|
|
const dict_table_t* table, /*!<in: if given, remove
|
|
all entries for the table */
|
|
const dict_index_t* index) /*!< in: if given, remove this index */
|
|
{
|
|
ut_a((table && !index) || (!table && index));
|
|
ut_ad(!srv_read_only_mode);
|
|
ut_ad(dict_sys.frozen());
|
|
|
|
mysql_mutex_lock(&defrag_pool_mutex);
|
|
|
|
defrag_pool_iterator_t iter = defrag_pool.begin();
|
|
while (iter != defrag_pool.end()) {
|
|
if ((table && (*iter).table_id == table->id)
|
|
|| (index
|
|
&& (*iter).table_id == index->table->id
|
|
&& (*iter).index_id == index->id)) {
|
|
/* erase() invalidates the iterator */
|
|
iter = defrag_pool.erase(iter);
|
|
if (index)
|
|
break;
|
|
} else {
|
|
iter++;
|
|
}
|
|
}
|
|
|
|
mysql_mutex_unlock(&defrag_pool_mutex);
|
|
}
|
|
|
|
/*****************************************************************//**
|
|
Get the first index that has been added for updating persistent defrag
|
|
stats and eventually save its stats. */
|
|
static void dict_stats_process_entry_from_defrag_pool(THD *thd)
|
|
{
|
|
table_id_t table_id;
|
|
index_id_t index_id;
|
|
|
|
ut_ad(!srv_read_only_mode);
|
|
|
|
/* pop the first index from the auto defrag pool */
|
|
if (!dict_stats_defrag_pool_get(&table_id, &index_id))
|
|
/* no index in defrag pool */
|
|
return;
|
|
|
|
/* If the table is no longer cached, we've already lost the in
|
|
memory stats so there's nothing really to write to disk. */
|
|
MDL_ticket *mdl= nullptr;
|
|
if (dict_table_t *table=
|
|
dict_table_open_on_id(table_id, false, DICT_TABLE_OP_OPEN_ONLY_IF_CACHED,
|
|
thd, &mdl))
|
|
{
|
|
if (dict_index_t *index= !table->corrupted
|
|
? dict_table_find_index_on_id(table, index_id) : nullptr)
|
|
if (index->is_btree())
|
|
dict_stats_save_defrag_stats(index);
|
|
dict_table_close(table, false, thd, mdl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Get the first index that has been added for updating persistent defrag
|
|
stats and eventually save its stats. */
|
|
void dict_defrag_process_entries_from_defrag_pool(THD *thd)
|
|
{
|
|
while (!defrag_pool.empty())
|
|
dict_stats_process_entry_from_defrag_pool(thd);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Save defragmentation result.
|
|
@return DB_SUCCESS or error code */
|
|
dberr_t dict_stats_save_defrag_summary(dict_index_t *index, THD *thd)
|
|
{
|
|
if (index->is_ibuf())
|
|
return DB_SUCCESS;
|
|
|
|
dict_stats stats;
|
|
if (stats.open(thd))
|
|
return DB_STATS_DO_NOT_EXIST;
|
|
trx_t *trx= trx_create();
|
|
trx->mysql_thd= thd;
|
|
trx_start_internal(trx);
|
|
dberr_t ret= trx->read_only
|
|
? DB_READ_ONLY
|
|
: lock_table_for_trx(stats.table(), trx, LOCK_X);
|
|
if (ret == DB_SUCCESS)
|
|
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
|
|
row_mysql_lock_data_dictionary(trx);
|
|
if (ret == DB_SUCCESS)
|
|
ret= dict_stats_save_index_stat(index, time(nullptr), "n_pages_freed",
|
|
index->stat_defrag_n_pages_freed,
|
|
nullptr,
|
|
"Number of pages freed during"
|
|
" last defragmentation run.",
|
|
trx);
|
|
if (ret == DB_SUCCESS)
|
|
trx->commit();
|
|
else
|
|
trx->rollback();
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
trx->free();
|
|
stats.close();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**************************************************************//**
|
|
Gets the number of reserved and used pages in a B-tree.
|
|
@return number of pages reserved, or ULINT_UNDEFINED if the index
|
|
is unavailable */
|
|
static
|
|
ulint
|
|
btr_get_size_and_reserved(
|
|
dict_index_t* index, /*!< in: index */
|
|
ulint flag, /*!< in: BTR_N_LEAF_PAGES or BTR_TOTAL_SIZE */
|
|
ulint* used, /*!< out: number of pages used (<= reserved) */
|
|
mtr_t* mtr) /*!< in/out: mini-transaction where index
|
|
is s-latched */
|
|
{
|
|
ulint dummy;
|
|
|
|
ut_ad(mtr->memo_contains(index->lock, MTR_MEMO_SX_LOCK));
|
|
ut_a(flag == BTR_N_LEAF_PAGES || flag == BTR_TOTAL_SIZE);
|
|
|
|
if (index->page == FIL_NULL
|
|
|| dict_index_is_online_ddl(index)
|
|
|| !index->is_committed()
|
|
|| !index->table->space) {
|
|
return(ULINT_UNDEFINED);
|
|
}
|
|
|
|
dberr_t err;
|
|
buf_block_t* root = btr_root_block_get(index, RW_SX_LATCH, mtr, &err);
|
|
*used = 0;
|
|
if (!root) {
|
|
return ULINT_UNDEFINED;
|
|
}
|
|
|
|
mtr->x_lock_space(index->table->space);
|
|
|
|
ulint n = fseg_n_reserved_pages(*root, PAGE_HEADER + PAGE_BTR_SEG_LEAF
|
|
+ root->page.frame, used, mtr);
|
|
if (flag == BTR_TOTAL_SIZE) {
|
|
n += fseg_n_reserved_pages(*root,
|
|
PAGE_HEADER + PAGE_BTR_SEG_TOP
|
|
+ root->page.frame, &dummy, mtr);
|
|
*used += dummy;
|
|
}
|
|
|
|
return(n);
|
|
}
|
|
|
|
/*********************************************************************//**
|
|
Save defragmentation stats for a given index.
|
|
@return DB_SUCCESS or error code */
|
|
dberr_t
|
|
dict_stats_save_defrag_stats(
|
|
/*============================*/
|
|
dict_index_t* index) /*!< in: index */
|
|
{
|
|
if (index->is_ibuf())
|
|
return DB_SUCCESS;
|
|
if (!index->is_readable())
|
|
return dict_stats_report_error(index->table, true);
|
|
|
|
const time_t now= time(nullptr);
|
|
mtr_t mtr;
|
|
ulint n_leaf_pages;
|
|
mtr.start();
|
|
mtr_sx_lock_index(index, &mtr);
|
|
ulint n_leaf_reserved= btr_get_size_and_reserved(index, BTR_N_LEAF_PAGES,
|
|
&n_leaf_pages, &mtr);
|
|
mtr.commit();
|
|
|
|
if (n_leaf_reserved == ULINT_UNDEFINED)
|
|
return DB_SUCCESS;
|
|
|
|
THD *const thd= current_thd;
|
|
dict_stats stats;
|
|
if (stats.open(thd))
|
|
return DB_STATS_DO_NOT_EXIST;
|
|
trx_t *trx= trx_create();
|
|
trx->mysql_thd= thd;
|
|
trx_start_internal(trx);
|
|
dberr_t ret= trx->read_only
|
|
? DB_READ_ONLY
|
|
: lock_table_for_trx(stats.table(), trx, LOCK_X);
|
|
if (ret == DB_SUCCESS)
|
|
ret= lock_table_for_trx(stats.index(), trx, LOCK_X);
|
|
|
|
row_mysql_lock_data_dictionary(trx);
|
|
|
|
if (ret == DB_SUCCESS)
|
|
ret= dict_stats_save_index_stat(index, now, "n_page_split",
|
|
index->stat_defrag_n_page_split, nullptr,
|
|
"Number of new page splits on leaves"
|
|
" since last defragmentation.", trx);
|
|
|
|
if (ret == DB_SUCCESS)
|
|
ret= dict_stats_save_index_stat(index, now, "n_leaf_pages_defrag",
|
|
n_leaf_pages, nullptr,
|
|
"Number of leaf pages when"
|
|
" this stat is saved to disk", trx);
|
|
|
|
if (ret == DB_SUCCESS)
|
|
ret= dict_stats_save_index_stat(index, now, "n_leaf_pages_reserved",
|
|
n_leaf_reserved, nullptr,
|
|
"Number of pages reserved for"
|
|
" this index leaves"
|
|
" when this stat is saved to disk", trx);
|
|
|
|
if (ret == DB_SUCCESS)
|
|
trx->commit();
|
|
else
|
|
trx->rollback();
|
|
|
|
row_mysql_unlock_data_dictionary(trx);
|
|
trx->free();
|
|
stats.close();
|
|
|
|
return ret;
|
|
}
|