/** Memory for each table in the intermediate buffer is allocated in separatechunks.Thesechunksareconsideredtobeconcatenatedto
represent one flat array of rows. */ struct i_s_mem_chunk_t {
ulint offset; /*!< offset, in number of rows */
ulint rows_allocd; /*!< the size of this chunk, in number
of rows */ void* base; /*!< start of the chunk */
};
/** This represents one table's cache. */ struct i_s_table_cache_t {
ulint rows_used; /*!< number of used rows */
ulint rows_allocd; /*!< number of allocated rows */
ulint row_size; /*!< size of a single row */
i_s_mem_chunk_t chunks[MEM_CHUNKS_IN_TABLE_CACHE]; /*!< array of memorychunksthatstoresthe
rows */
};
/** This structure describes the intermediate buffer */ struct trx_i_s_cache_t {
srw_lock rw_lock; /*!< read-write lock protecting this */
Atomic_relaxed<ulonglong> last_read; /*!< last time the cache was read;
measured in nanoseconds */
i_s_table_cache_t innodb_trx; /*!< innodb_trx table */
i_s_table_cache_t innodb_locks; /*!< innodb_locks table */
i_s_table_cache_t innodb_lock_waits;/*!< innodb_lock_waits table */ /** the hash table size is LOCKS_HASH_CELLS_NUM * sizeof(void*) bytes */ #define LOCKS_HASH_CELLS_NUM 10000
hash_table_t locks_hash; /*!< hash table used to eliminate duplicateentriesinthe
innodb_locks table */ /** Initial size of the cache storage */ #define CACHE_STORAGE_INITIAL_SIZE 1024 /** Number of hash cells in the cache storage */ #define CACHE_STORAGE_HASH_CELLS 2048
ha_storage_t* storage; /*!< storage for external volatile datathatmaybecomeunavailable whenwerelease
lock_sys.latch */
ulint mem_allocd; /*!< the amount of memory
allocated with mem_alloc*() */ bool is_truncated; /*!< this is true if the memory limitwashitandthusthedata
in the cache is truncated */
/** Adds an element. @paramlockelementtobeadded @paramheap_norecordlockheapnumber,or0xFFFFfortablelock @returntheexistingoraddedlock
@retval nullptr if memory cannot be allocated */
i_s_locks_row_t *add(const lock_t &lock, uint16_t heap_no) noexcept;
};
/** This is the intermediate buffer where data needed to fill the INFORMATIONSCHEMAtablesisfetchedandlaterretrievedbytheC++
code in handler/i_s.cc. */ static trx_i_s_cache_t cache;
/** @return the heap number of a record lock
@retval 0xFFFF for table locks */ static uint16_t wait_lock_get_heap_no(const lock_t *lock)
{ return !lock->is_table()
? static_cast<uint16_t>(lock_rec_find_set_bit(lock))
: uint16_t{0xFFFF};
}
/*******************************************************************//**
Initializes the members of a table cache. */ static void
table_cache_init( /*=============*/
i_s_table_cache_t* table_cache, /*!< out: table cache */
size_t row_size) /*!< in: the size of a
row */
{
ulint i;
/* the memory is actually allocated in
table_cache_create_empty_row() */ if (table_cache->chunks[i].base) {
ut_free(table_cache->chunks[i].base);
table_cache->chunks[i].base = NULL;
}
}
}
/*******************************************************************//**
Returns an empty row from a table cache. The row is allocated if no more
empty rows are available. The number of used rows is incremented. If the memory limit is hit then NULL is returned and nothing is
allocated.
@return empty row, or NULL if out of memory */ static void*
table_cache_create_empty_row( /*=========================*/
i_s_table_cache_t* table_cache, /*!< in/out: table cache */
trx_i_s_cache_t* cache) /*!< in/out: cache to record howmanybytesare
allocated */
{
ulint i; void* row;
/* return the first empty row in the newly allocated
chunk */
row = chunk->base;
} else {
char* chunk_start;
ulint offset;
/* there is an empty row, no need to allocate new
chunks */
/* find the first chunk that contains allocated but
empty/unused rows */ for (i = 0; i < MEM_CHUNKS_IN_TABLE_CACHE; i++) {
if (table_cache->chunks[i].offset
+ table_cache->chunks[i].rows_allocd
> table_cache->rows_used) {
break;
}
}
/* i == MEM_CHUNKS_IN_TABLE_CACHE means that all chunks arefull,but table_cache->rows_used!=table_cache->rows_allocdmeans exactlytheopposite-thereareallocatedbut
empty/unused rows :-X */
ut_a(i < MEM_CHUNKS_IN_TABLE_CACHE);
#ifdef UNIV_DEBUG /*******************************************************************//**
Validates a row in the locks cache.
@returnTRUEif valid */ static
ibool
i_s_locks_row_validate( /*===================*/ const i_s_locks_row_t* row) /*!< in: row to validate */
{
ut_ad(row->lock_mode);
ut_ad(row->lock_table != NULL);
ut_ad(row->lock_table_id != 0);
if (!row->lock_index) { /* table lock */
ut_ad(!row->lock_data);
ut_ad(row->lock_page == page_id_t(0, 0));
ut_ad(!row->lock_rec);
} else { /* record lock */ /* row->lock_data == NULL if buf_page_try_get() == NULL */
}
return(TRUE);
} #endif/* UNIV_DEBUG */
/*******************************************************************//**
Fills i_s_trx_row_t object. If memory can not be allocated then FALSE is returned.
@returnFALSEif allocation fails */ static
ibool
fill_trx_row( /*=========*/
i_s_trx_row_t* row, /*!< out: result object
that's filled */ const trx_t* trx, /*!< in: transaction to
get data from */ const i_s_locks_row_t* requested_lock_row,/*!< in: pointer to the correspondingrowin innodb_locksiftrxis waitingorNULLiftrx
is not waiting */
trx_i_s_cache_t* cache) /*!< in/out: cache into whichtocopyvolatile
strings */
{ constchar* s;
/*******************************************************************//**
Format the nth field of "rec"and put it in "buf". The result is always
NUL-terminated. Returns the number of bytes that were written to "buf"
(including the terminating NUL).
@return end of the result */ static
ulint
put_nth_field( /*==========*/ char* buf, /*!< out: buffer */
ulint buf_size,/*!< in: buffer size in bytes */
ulint n, /*!< in: number of field */ const dict_index_t* index, /*!< in: index */ const rec_t* rec, /*!< in: record */ const rec_offs* offsets)/*!< in: record offsets, returned
by rec_get_offsets() */
{ const byte* data;
ulint data_len;
dict_field_t* dict_field;
ulint ret;
ut_ad(rec_offs_validate(rec, NULL, offsets));
if (buf_size == 0) {
return(0);
}
ret = 0;
if (n > 0) { /* we must append ", " before the actual data */
if (buf_size < 3) {
buf[0] = '\0'; return(1);
}
memcpy(buf, ", ", 3);
buf += 2;
buf_size -= 2;
ret += 2;
}
/* now buf_size >= 1 */
data = rec_get_nth_field(rec, offsets, n, &data_len);
dict_field = dict_index_get_nth_field(index, n);
ret += row_raw_format((constchar*) data, data_len,
dict_field, buf, buf_size);
return(ret);
}
/*******************************************************************//**
Fills the "lock_data" member of i_s_locks_row_t object. If memory can not be allocated then FALSE is returned.
@returnFALSEif allocation fails */ static
ibool
fill_lock_data( /*===========*/ constchar** lock_data,/*!< out: "lock_data" to fill */ const lock_t* lock, /*!< in: lock used to find the data */
ulint heap_no,/*!< in: rec num used to find the data */
trx_i_s_cache_t* cache) /*!< in/out: cache where to store
volatile data */
{
ut_a(!lock->is_table());
/* this means that rec_get_offsets() has created a new heapandhasstoredoffsetsinit;checkthatthisis
really the case and free the heap */
ut_a(offsets != offsets_onstack);
mem_heap_free(heap);
}
mtr_commit(&mtr);
if (*lock_data == NULL) {
return(FALSE);
}
return(TRUE);
}
/** @return the table of a lock */ staticconst dict_table_t *lock_get_table(const lock_t &lock)
{ if (lock.is_table()) return lock.un_member.tab_lock.table;
ut_ad(lock.index->is_primary() || !dict_index_is_online_ddl(lock.index)); return lock.index->table;
}
/*******************************************************************//**
Fills i_s_locks_row_t object. Returns its first argument. If memory can not be allocated then FALSE is returned.
@returnfalseif allocation fails */ staticbool fill_locks_row(
i_s_locks_row_t* row, /*!< out: result object that's filled */ const lock_t* lock, /*!< in: lock to get data from */
uint16_t heap_no,/*!< in: lock's record number or0ifthelock
is a table lock */
trx_i_s_cache_t* cache) /*!< in/out: cache into which to copy
volatile strings */
{
row->lock_trx_id = lock->trx->id; constbool is_gap_lock = lock->is_gap();
ut_ad(!is_gap_lock || !lock->is_table()); switch (lock->mode()) { case LOCK_S:
row->lock_mode = uint8_t(1 + is_gap_lock); break; case LOCK_X:
row->lock_mode = uint8_t(3 + is_gap_lock); break; case LOCK_IS:
row->lock_mode = uint8_t(5 + is_gap_lock); break; case LOCK_IX:
row->lock_mode = uint8_t(7 + is_gap_lock); break; case LOCK_AUTO_INC:
row->lock_mode = 9; break; default:
ut_ad("unknown lock mode" == 0);
row->lock_mode = 0;
}
/*******************************************************************//**
Calculates a hash fold for a lock. For a record lock the fold is
calculated from 4 elements, which uniquely identify a lock at a given
point in time: transaction id, space id, page number, record number. For a table lock the fold is table's id.
@return fold */ static
ulint
fold_lock( /*======*/ const lock_t& lock, /*!< in: lock object to fold */
uint16_t heap_no)/*!< in: lock's record number or0xFFFFifthelock
is a table lock */
{
ut_ad((heap_no == 0xFFFF) == lock.is_table()); if (heap_no == 0xFFFF) return ulint(lock.un_member.tab_lock.table->id); char buf[8 + 8];
memcpy(buf, &lock.trx->id, 8);
memcpy(buf + 8, &lock.un_member.rec_lock.page_id, 8); return my_crc32c(heap_no, buf, sizeof buf);
}
/*******************************************************************//**
Adds new element to the locks cache, enlarging it if necessary.
Returns a pointer to the added row. If the row is already present then
no row is added and a pointer to the existing row is returned. If row can not be allocated then NULL is returned.
@return row */
i_s_locks_row_t *
trx_i_s_cache_t::add(const lock_t &lock, uint16_t heap_no) noexcept
{
ut_ad(lock.is_table() == (heap_no == 0xFFFF));
i_s_locks_row_t** after= reinterpret_cast<i_s_locks_row_t**>
(&locks_hash.cell_get(fold_lock(lock, heap_no))->node); while (i_s_locks_row_t *row= *after)
{
ut_ad(i_s_locks_row_validate(row)); if (row->lock_trx_id == lock.trx->id &&
(heap_no == 0xFFFF
? row->lock_table_id == lock.un_member.tab_lock.table->id
: (row->lock_rec == heap_no &&
row->lock_page == lock.un_member.rec_lock.page_id))) return row;
after= &row->next;
}
i_s_locks_row_t *dst_row= static_cast<i_s_locks_row_t*>
(table_cache_create_empty_row(&innodb_locks, this)); if (dst_row)
{ if (!fill_locks_row(dst_row, &lock, heap_no, this))
{
innodb_locks.rows_used--;
dst_row= nullptr;
} else
{
*after= dst_row;
ut_ad(i_s_locks_row_validate(dst_row));
}
}
return dst_row;
}
/*******************************************************************//**
Adds new pair of locks to the lock waits cache. If memory can not be allocated then FALSE is returned.
@returnFALSEif allocation fails */ static
ibool
add_lock_wait_to_cache( /*===================*/
trx_i_s_cache_t* cache, /*!< in/out: cache */ const i_s_locks_row_t* requested_lock_row,/*!< in: pointer to the relevantrequestedlock
row in innodb_locks */ const i_s_locks_row_t* blocking_lock_row)/*!< in: pointer to the relevantblockinglock
row in innodb_locks */
{
i_s_lock_waits_row_t* dst_row;
/*******************************************************************//**
Adds transaction's relevant (important) locks to cache. If the transaction is waiting, then the wait lock is added to
innodb_locks and a pointer to the added row is returned in
requested_lock_row, otherwise requested_lock_row is set to NULL. If rows can not be allocated then FALSE is returned and the value of
requested_lock_row is undefined.
@returnFALSEif allocation fails */ static
ibool
add_trx_relevant_locks_to_cache( /*============================*/
trx_i_s_cache_t* cache, /*!< in/out: cache */ const trx_t* trx, /*!< in: transaction */
i_s_locks_row_t** requested_lock_row)/*!< out: pointer to the requestedlockrow,orNULLor
undefined */
{
lock_sys.assert_locked();
/* If transaction is waiting we add the wait lock and all locks
from another transactions that are blocking the wait lock. */ if (const lock_t *wait_lock = trx->lock.wait_lock) {
/* add the lock that is
blocking wait_lock */
blocking_lock_row = cache->add(*curr_lock,
heap_no);
/* memory could not be allocated */ if (blocking_lock_row == NULL) {
return(FALSE);
}
/* add the relation between both locks
to innodb_lock_waits */ if (!add_lock_wait_to_cache(
cache, *requested_lock_row,
blocking_lock_row)) {
/* memory could not be allocated */ return(FALSE);
}
}
}
} else {
*requested_lock_row = NULL;
}
return(TRUE);
}
/** The minimum time that a cache must not be updated after it has been readforthelasttime;measuredinnanoseconds.Weusethistechnique toensurethatSELECTswhichjoinseveralINFORMATIONSCHEMAtablesread
the same version of the cache. */ #define CACHE_MIN_IDLE_TIME_NS 100000000/* 0.1 sec */
/*******************************************************************//**
Checks if the cache can safely be updated.
@return whether the cache can be updated */ staticbool can_cache_be_updated(trx_i_s_cache_t* cache)
{ /* cache->last_read is only updated when a shared rw lock on the wholecacheisbeingheld(seetrx_i_s_cache_end_read())and wearecurrentlyholdinganexclusiverwlockonthecache. Soitisnotpossibleforlast_readtobeupdatedwhileweare
reading it. */ return my_interval_timer() - cache->last_read > CACHE_MIN_IDLE_TIME_NS;
}
/*******************************************************************//**
Declare a cache empty, preparing it to be filled up. Not all resources
are freed because they can be reused. */ static void
trx_i_s_cache_clear( /*================*/
trx_i_s_cache_t* cache) /*!< out: cache to clear */
{
cache->innodb_trx.rows_used = 0;
cache->innodb_locks.rows_used = 0;
cache->innodb_lock_waits.rows_used = 0;
staticvoid fetch_data_into_cache(trx_i_s_cache_t *cache)
{ /* these are protected by cache->rw_lock.wr_lock() */
trx_i_s_cache_clear(cache); /* this flag may be set by fetch_data_into_cache_low() below */
cache->is_truncated= false;
LockMutexGuard g{SRW_LOCK_CALL};
/* Capture the state of transactions */
trx_sys.trx_list.for_each([cache](trx_t &trx) { if (!cache->is_truncated && trx.state != TRX_STATE_NOT_STARTED &&
&trx != (purge_sys.query ? purge_sys.query->trx : nullptr))
{
trx.mutex_lock(); if (trx.is_started())
fetch_data_into_cache_low(cache, &trx);
trx.mutex_unlock();
}
});
}
/*******************************************************************//**
Update the transactions cache if it has not been read for some time.
@retval false when fetched or cached
@retval trueif fetched but the cache was truncated */ bool trx_i_s_possibly_fetch_data_into_cache()
{ bool is_truncated= false;
cache.rw_lock.wr_lock(SRW_LOCK_CALL); if (can_cache_be_updated(&cache))
{ /* We need to read trx_sys and record/table lock queues */
fetch_data_into_cache(&cache); /* update cache last read time */
cache.last_read= my_interval_timer();
is_truncated= cache.is_truncated;
}
cache.rw_lock.wr_unlock(); return is_truncated;
}
void trx_i_s_cache_init()
{ /* The latching is done in the following order: acquiretrx_i_s_cache_t::rw_lock,rwlock acquireexclusivelock_sys.latch releaseexclusivelock_sys.latch releasetrx_i_s_cache_t::rw_lock acquiretrx_i_s_cache_t::rw_lock,rdlock
release trx_i_s_cache_t::rw_lock */
/*******************************************************************//**
Crafts a lock id string from a i_s_locks_row_t object. Returns its
second argument. This function aborts if there is not enough space in
lock_id. Be sure to provide at least TRX_I_S_LOCK_ID_MAX_LEN + 1if you
want to be 100% sure that it will not abort.
@return resulting lock id */ char*
trx_i_s_create_lock_id( /*===================*/ const i_s_locks_row_t* row, /*!< in: innodb_locks row */ char* lock_id,/*!< out: resulting lock_id */
ulint lock_id_size)/*!< in: size of the lock id
buffer */
{ int res_len;
/* please adjust TRX_I_S_LOCK_ID_MAX_LEN if you change this */
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.