mysql5/mysql-5.7.27/sql/table_cache.cc

413 lines
9.9 KiB
C++

/* Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
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 St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "table_cache.h"
#include "sql_test.h" // lock_descriptions[]
/**
Container for all table cache instances in the system.
*/
Table_cache_manager table_cache_manager;
#ifdef HAVE_PSI_INTERFACE
PSI_mutex_key Table_cache::m_lock_key;
PSI_mutex_info Table_cache::m_mutex_keys[]= {
{ &m_lock_key, "LOCK_table_cache", 0}
};
#endif
extern "C" uchar *table_cache_key(const uchar *record,
size_t *length,
my_bool not_used MY_ATTRIBUTE((unused)))
{
TABLE_SHARE *share= ((Table_cache_element*)record)->get_share();
*length= share->table_cache_key.length;
return (uchar*) share->table_cache_key.str;
}
extern "C" void table_cache_free_entry(Table_cache_element *element)
{
delete element;
}
/**
Initialize instance of table cache.
@retval false - success.
@retval true - failure.
*/
bool Table_cache::init()
{
mysql_mutex_init(m_lock_key, &m_lock, MY_MUTEX_INIT_FAST);
m_unused_tables= NULL;
m_table_count= 0;
if (my_hash_init(&m_cache, &my_charset_bin,
table_cache_size_per_instance, 0, 0,
table_cache_key, (my_hash_free_key) table_cache_free_entry,
0,
PSI_INSTRUMENT_ME))
{
mysql_mutex_destroy(&m_lock);
return true;
}
return false;
}
/** Destroy instance of table cache. */
void Table_cache::destroy()
{
my_hash_free(&m_cache);
mysql_mutex_destroy(&m_lock);
}
/** Init P_S instrumentation key for mutex protecting Table_cache instance. */
void Table_cache::init_psi_keys()
{
#ifdef HAVE_PSI_INTERFACE
mysql_mutex_register("sql", m_mutex_keys, array_elements(m_mutex_keys));
#endif
}
#ifdef EXTRA_DEBUG
void Table_cache::check_unused()
{
uint count= 0;
if (m_unused_tables != NULL)
{
TABLE *cur_link= m_unused_tables;
TABLE *start_link= m_unused_tables;
do
{
if (cur_link != cur_link->next->prev || cur_link != cur_link->prev->next)
{
DBUG_PRINT("error",("Unused_links aren't linked properly"));
return;
}
} while (count++ < m_table_count &&
(cur_link= cur_link->next) != start_link);
if (cur_link != start_link)
DBUG_PRINT("error",("Unused_links aren't connected"));
}
for (uint idx= 0; idx < m_cache.records; idx++)
{
Table_cache_element *el=
(Table_cache_element*) my_hash_element(&m_cache, idx);
Table_cache_element::TABLE_list::Iterator it(el->free_tables);
TABLE *entry;
while ((entry= it++))
{
/* We must not have TABLEs in the free list that have their file closed. */
DBUG_ASSERT(entry->db_stat && entry->file);
/* Merge children should be detached from a merge parent */
DBUG_ASSERT(! entry->file->extra(HA_EXTRA_IS_ATTACHED_CHILDREN));
if (entry->in_use)
DBUG_PRINT("error",("Used table is in share's list of unused tables"));
count--;
}
it.init(el->used_tables);
while ((entry= it++))
{
if (!entry->in_use)
DBUG_PRINT("error",("Unused table is in share's list of used tables"));
}
}
if (count != 0)
DBUG_PRINT("error",("Unused_links doesn't match open_cache: diff: %d",
count));
}
#endif
/** Free all unused TABLE objects in the table cache. */
void Table_cache::free_all_unused_tables()
{
assert_owner();
while (m_unused_tables)
{
TABLE *table_to_free= m_unused_tables;
remove_table(table_to_free);
intern_close_table(table_to_free);
}
}
#ifndef DBUG_OFF
/**
Print debug information for the contents of the table cache.
*/
void Table_cache::print_tables()
{
uint unused= 0;
uint count=0;
compile_time_assert(TL_WRITE_ONLY+1 == array_elements(lock_descriptions));
for (uint idx= 0; idx < m_cache.records; idx++)
{
Table_cache_element *el=
(Table_cache_element*) my_hash_element(&m_cache, idx);
Table_cache_element::TABLE_list::Iterator it(el->used_tables);
TABLE *entry;
while ((entry= it++))
{
printf("%-14.14s %-32s%6ld%8u%6d %s\n",
entry->s->db.str, entry->s->table_name.str, entry->s->version,
entry->in_use->thread_id(), entry->db_stat ? 1 : 0,
lock_descriptions[(int)entry->reginfo.lock_type]);
}
it.init(el->free_tables);
while ((entry= it++))
{
unused++;
printf("%-14.14s %-32s%6ld%8ld%6d %s\n",
entry->s->db.str, entry->s->table_name.str, entry->s->version,
0L, entry->db_stat ? 1 : 0, "Not in use");
}
}
if (m_unused_tables != NULL)
{
TABLE *start_link= m_unused_tables;
TABLE *lnk= m_unused_tables;
do
{
if (lnk != lnk->next->prev || lnk != lnk->prev->next)
{
printf("unused_links isn't linked properly\n");
return;
}
} while (count++ < m_table_count && (lnk= lnk->next) != start_link);
if (lnk != start_link)
printf("Unused_links aren't connected\n");
}
if (count != unused)
printf("Unused_links (%d) doesn't match table_def_cache: %d\n", count,
unused);
}
#endif
/**
Initialize all instances of table cache to be used by server.
@retval false - success.
@retval true - failure.
*/
bool Table_cache_manager::init()
{
Table_cache::init_psi_keys();
for (uint i= 0; i < table_cache_instances; i++)
{
if (m_table_cache[i].init())
{
for (uint j= 0; j < i; j++)
m_table_cache[i].destroy();
return true;
}
}
return false;
}
/** Destroy all instances of table cache which were used by server. */
void Table_cache_manager::destroy()
{
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].destroy();
}
/**
Get total number of used and unused TABLE objects in all table caches.
@note Doesn't require acquisition of table cache locks if inexact number
of tables is acceptable.
*/
uint Table_cache_manager::cached_tables()
{
uint result= 0;
for (uint i= 0; i < table_cache_instances; i++)
result+= m_table_cache[i].cached_tables();
return result;
}
/**
Acquire locks on all instances of table cache and table definition
cache (i.e. LOCK_open).
*/
void Table_cache_manager::lock_all_and_tdc()
{
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].lock();
mysql_mutex_lock(&LOCK_open);
}
/**
Release locks on all instances of table cache and table definition
cache.
*/
void Table_cache_manager::unlock_all_and_tdc()
{
mysql_mutex_unlock(&LOCK_open);
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].unlock();
}
/**
Assert that caller owns locks on all instances of table cache.
*/
void Table_cache_manager::assert_owner_all()
{
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].assert_owner();
}
/**
Assert that caller owns locks on all instances of table cache
and table definition cache.
*/
void Table_cache_manager::assert_owner_all_and_tdc()
{
assert_owner_all();
mysql_mutex_assert_owner(&LOCK_open);
}
/**
Remove and free all or some (depending on parameter) TABLE objects
for the table from all table cache instances.
@param thd Thread context
@param remove_type Type of removal. @sa tdc_remove_table().
@param share TABLE_SHARE for the table to be removed.
@note Caller should own LOCK_open and locks on all table cache
instances.
*/
void Table_cache_manager::free_table(THD *thd,
enum_tdc_remove_table_type remove_type,
TABLE_SHARE *share)
{
Table_cache_element *cache_el[MAX_TABLE_CACHES];
assert_owner_all_and_tdc();
/*
Freeing last TABLE instance for the share will destroy the share
and corresponding TABLE_SHARE::cache_element[] array. To make
iteration over this array safe, even when share is destroyed in
the middle of iteration, we create copy of this array on the stack
and iterate over it.
*/
memcpy(&cache_el, share->cache_element,
table_cache_instances * sizeof(Table_cache_element *));
for (uint i= 0; i < table_cache_instances; i++)
{
if (cache_el[i])
{
Table_cache_element::TABLE_list::Iterator it(cache_el[i]->free_tables);
TABLE *table;
#ifndef DBUG_OFF
if (remove_type == TDC_RT_REMOVE_ALL)
DBUG_ASSERT(cache_el[i]->used_tables.is_empty());
else if (remove_type == TDC_RT_REMOVE_NOT_OWN ||
remove_type == TDC_RT_REMOVE_NOT_OWN_KEEP_SHARE)
{
Table_cache_element::TABLE_list::Iterator it2(cache_el[i]->used_tables);
while ((table= it2++))
{
if (table->in_use != thd)
DBUG_ASSERT(0);
}
}
#endif
while ((table= it++))
{
m_table_cache[i].remove_table(table);
intern_close_table(table);
}
}
}
}
/** Free all unused TABLE objects in all table cache instances. */
void Table_cache_manager::free_all_unused_tables()
{
assert_owner_all_and_tdc();
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].free_all_unused_tables();
}
#ifndef DBUG_OFF
/**
Print debug information for the contents of all table cache instances.
*/
void Table_cache_manager::print_tables()
{
puts("DB Table Version Thread Open Lock");
for (uint i= 0; i < table_cache_instances; i++)
m_table_cache[i].print_tables();
}
#endif