polardbxengine/storage/innobase/handler/p_s.cc

1107 lines
35 KiB
C++

/*****************************************************************************
Copyright (c) 2016, 2018, 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, version 2.0, as published by the
Free Software Foundation.
This program is also distributed with certain software (including but not
limited to OpenSSL) that is licensed under separate terms, as designated in a
particular file or component or in included license documentation. The authors
of MySQL hereby grant you an additional permission to link the program and
your derivative works with the separately licensed software that they have
included with MySQL.
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, version 2.0,
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
*****************************************************************************/
/** @file handler/p_s.cc
InnoDB performance_schema tables interface to MySQL.
*******************************************************/
#include "storage/innobase/handler/p_s.h"
#include <stdlib.h>
#include <sys/types.h>
#include "lock0iter.h"
#include "lock0lock.h"
#include "sql_table.h"
#include "table.h"
#include "trx0i_s.h"
#include "trx0sys.h"
#include "my_io.h"
/**
@page PAGE_INNODB_PFS Innodb data lock instrumentation
Innodb Performance Schema data lock instrumentation
@section data_lock_iterators Data lock iterators
To provide content to the performance_schema.data_locks table,
innodb implements #Innodb_data_lock_iterator.
Likewise, table performance_schema.data_wait_locks
is populated with #Innodb_data_lock_wait_iterator.
Both these iterators need to return the data present
in the innodb engine memory,
which imply to take the proper mutex locks when inspecting it.
The structure to inspect here is the transaction list (#trx_sys)
How to implement this scan is critical for performances.
@subsection no_full_scan No full scan
Consider this implementation:
- Take all necessary locks
- Scan all the innodb internal locks
- Report all of them to the performance schema
- Release all the locks taken
This implementation materializes the entire table.
The benefits with this approach are:
- The materialized table is consistent
The problems with this approach are:
- The innodb engine is frozen for the entire duration,
for a time that is unpredictable.
- Memory consumption spikes, without bounds
- Materializing all rows upfront is incompatible with supporting an index
For example with N = 10,000 transactions,
a single scan reports all 10,000 transaction locks.
This alternative is rejected.
@subsection no_single_row_scan No single row scan
Consider this implementation:
- Take all necessary locks
- Resume the scan on innodb internal locks for 1 record
- Report this record to the performance schema
- Release all the locks taken
This implementation returns a row for a single transaction,
or even a single lock, at a time.
The benefits with this approach are:
- Memory consumption is well bounded, and low.
The problems with this approach are:
- Data reported can be very inconsistent.
- Implementing a restartable scan, on a very dynamic structure,
without holding any lock, is complex.
- Even assuming how to implement a scan is resolved,
looping N times to find element i, i+1, i+2 ... in a list
ends up having a complexity in O(N^2), consuming CPU.
For example with N = 10,000 transactions,
the trx_list would be scanned 10,000 times
to return 1 record each time.
The total number of operations on the list is 100 Millions.
This alternative is rejected.
@subsection restartable_batch_scan Restartable batch scan
What is implemented is:
- Take all necessary locks
- Resume the scan on innodb internal locks,
for a given record range
- Report all the records in the range to the performance schema
- Release all the locks taken
This is a compromise, with the following properties:
- Memory consumption is bounded,
by the number of records returned in each range.
- The duration of mutex locks on innodb structures is bounded
by the number of records in each range
- The data returned is not consistent,
but at least it is "consistent by chunks"
- The overall scan complexity is (N/RANGE)^2, where RANGE is the range size.
This is still technically O(N^2), but in practice should be reasonable.
For example with N = 10,000 transactions and RANGE = 256,
there are 40 batches at the trx list,
where each batch reports (up to) 256 trx, with the trx locks.
The total number of operations on the list is 400 thousands.
*/
static const char *g_engine = "INNODB";
static const size_t g_engine_length = 6;
inline trx_t *get_next_trx(const trx_t *trx, bool read_write) {
if (read_write) {
return (UT_LIST_GET_NEXT(trx_list, trx));
} else {
return (UT_LIST_GET_NEXT(mysql_trx_list, trx));
}
}
/** Pass of a given scan. */
enum scan_pass {
INIT_SCANNING,
/** Scan the RW trx list.
@sa trx_sys_t::rw_trx_list
*/
SCANNING_RW_TRX_LIST,
/** Scan the MySQL trx list.
@sa trx_t::mysql_trx_list
*/
SCANNING_MYSQL_TRX_LIST,
DONE_SCANNING
};
/** State of a given scan.
Scans are restartable, and done in multiple calls.
Overall, the code scans separately:
- the RW trx list
- the MySQL trx list
For each list, the scan is done by ranges of trx_id values.
Saving the current scan state allows to resume where the previous
scan ended.
*/
class Innodb_trx_scan_state {
public:
const trx_id_t SCAN_RANGE = 256;
Innodb_trx_scan_state()
: m_scan_pass(INIT_SCANNING),
m_start_trx_id_range(0),
m_end_trx_id_range(SCAN_RANGE),
m_next_trx_id_range(TRX_ID_MAX) {}
~Innodb_trx_scan_state() {}
scan_pass get_pass() { return m_scan_pass; }
/** Prepare the next scan.
When there are TRX after the current range,
compute the next range.
When there are no more TRX for this pass,
advance to the next pass.
*/
void prepare_next_scan() {
if (m_next_trx_id_range != TRX_ID_MAX) {
m_start_trx_id_range =
m_next_trx_id_range - (m_next_trx_id_range % SCAN_RANGE);
m_end_trx_id_range = m_start_trx_id_range + SCAN_RANGE;
m_next_trx_id_range = TRX_ID_MAX;
} else {
switch (m_scan_pass) {
case INIT_SCANNING:
m_scan_pass = SCANNING_RW_TRX_LIST;
m_start_trx_id_range = 0;
m_end_trx_id_range = SCAN_RANGE;
m_next_trx_id_range = TRX_ID_MAX;
break;
case SCANNING_RW_TRX_LIST:
m_scan_pass = SCANNING_MYSQL_TRX_LIST;
m_start_trx_id_range = 0;
m_end_trx_id_range = SCAN_RANGE;
m_next_trx_id_range = TRX_ID_MAX;
break;
case SCANNING_MYSQL_TRX_LIST:
m_scan_pass = DONE_SCANNING;
break;
case DONE_SCANNING:
default:
ut_error;
break;
}
}
}
/** Check if a transaction belongs to the current range.
As a side effect, compute the next range.
@param[in] trx_id Transaction id to evaluate
@return True if transaction is within range.
*/
bool trx_id_in_range(trx_id_t trx_id) {
ut_ad(trx_id < TRX_ID_MAX);
if ((m_start_trx_id_range <= trx_id) && (trx_id < m_end_trx_id_range)) {
return true;
}
if ((m_end_trx_id_range <= trx_id) && (trx_id < m_next_trx_id_range)) {
m_next_trx_id_range = trx_id;
}
return false;
}
private:
/** Current scan pass. */
scan_pass m_scan_pass;
/** Start of the current range. */
trx_id_t m_start_trx_id_range;
/** End of the current range. */
trx_id_t m_end_trx_id_range;
/** Next range. */
trx_id_t m_next_trx_id_range;
};
/** Inspect data locks for the innodb storage engine. */
class Innodb_data_lock_iterator : public PSI_engine_data_lock_iterator {
public:
Innodb_data_lock_iterator();
~Innodb_data_lock_iterator();
virtual bool scan(PSI_server_data_lock_container *container,
bool with_lock_data);
virtual bool fetch(PSI_server_data_lock_container *container,
const char *engine_lock_id, size_t engine_lock_id_length,
bool with_lock_data);
private:
/** Scan a trx list.
@param[in] container The container to fill
@param[in] with_lock_data True if column LOCK_DATA
needs to be populated.
@param[in] read_write True if the trx list is the RW list
@param[in] trx_list The trx list to scan
@returns The number of records found
*/
size_t scan_trx_list(PSI_server_data_lock_container *container,
bool with_lock_data, bool read_write,
trx_ut_list_t *trx_list);
/** Scan a given trx.
Either scan all the locks for a transaction,
or scan only records matching a given lock.
@param[in] container The container to fill
@param[in] with_lock_data True if column LOCK_DATA
needs to be populated.
@param[in] trx The trx to scan
@param[in] with_filter True if looking for a specific record
only.
@param[in] filter_lock_immutable_id Immutable id of lock_t we are looking
for
@param[in] filter_heap_id Heap id to look for, when filtering
@returns The number of records found
*/
size_t scan_trx(PSI_server_data_lock_container *container,
bool with_lock_data, const trx_t *trx, bool with_filter,
uint64_t filter_lock_immutable_id = 0,
ulint filter_heap_id = 0);
/** Current scan state. */
Innodb_trx_scan_state m_scan_state;
};
/** Inspect data lock waits for the innodb storage engine. */
class Innodb_data_lock_wait_iterator
: public PSI_engine_data_lock_wait_iterator {
public:
Innodb_data_lock_wait_iterator();
~Innodb_data_lock_wait_iterator();
virtual bool scan(PSI_server_data_lock_wait_container *container);
virtual bool fetch(PSI_server_data_lock_wait_container *container,
const char *requesting_engine_lock_id,
size_t requesting_engine_lock_id_length,
const char *blocking_engine_lock_id,
size_t blocking_engine_lock_id_length);
private:
/** Scan a given transaction list.
@param[in] container The container to fill
@param[in] read_write True if the transaction list is the RW list.
@param[in] trx_list The trx list to scan
@returns the number of records found.
*/
size_t scan_trx_list(PSI_server_data_lock_wait_container *container,
bool read_write, trx_ut_list_t *trx_list);
/** Scan a given transaction.
Either scan all the waits for a transaction,
or scan only records matching a given wait.
@param[in] container The container to fill
@param[in] trx The trx to scan
@param[in] with_filter True if looking for a given wait only.
@param[in] filter_requesting_lock_immutable_id Immutable id of lock_t for
the requesting lock, when filtering
@param[in] filter_blocking_lock_immutable_id Immutable id of lock_t
for the blocking lock, when filtering
@returns the number of records found.
*/
size_t scan_trx(PSI_server_data_lock_wait_container *container,
const trx_t *trx, bool with_filter,
uint64_t filter_requesting_lock_immutable_id = 0,
uint64_t filter_blocking_lock_immutable_id = 0);
/** Current scan state. */
Innodb_trx_scan_state m_scan_state;
};
/** Check if a transaction should be discarded.
Transactions present in any TRX LIST that have not started yet
are discarded, when inspecting data locks.
Transactions present in the MySQL TRX LIST,
that are writing data and have an id, are also discarded.
@param[in] trx Transaction to evaluate
@param[in] read_write True if trx is in the RW TRX list
@returns True if the trx should be discarded
*/
bool discard_trx(const trx_t *trx, bool read_write) {
if (!trx_is_started(trx)) {
return true;
}
if ((!read_write && trx->id != 0 && !trx->read_only)) {
return true;
}
return false;
}
/** Find a transaction in a TRX LIST.
@param[in] filter_trx_immutable_id The transaction immutable id
@param[in] read_write True for the RW TRX LIST
@param[in] trx_list The transaction list
@returns The transaction when found, or NULL
*/
static const trx_t *fetch_trx_in_trx_list(uint64_t filter_trx_immutable_id,
bool read_write,
trx_ut_list_t *trx_list) {
const trx_t *trx;
ut_ad(lock_mutex_own());
ut_ad(trx_sys_mutex_own());
for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
trx = get_next_trx(trx, read_write)) {
if (discard_trx(trx, read_write)) {
continue;
}
if (filter_trx_immutable_id == trx_immutable_id(trx)) {
return trx;
}
}
return NULL;
}
Innodb_data_lock_inspector::Innodb_data_lock_inspector() {}
Innodb_data_lock_inspector::~Innodb_data_lock_inspector() {}
PSI_engine_data_lock_iterator *
Innodb_data_lock_inspector::create_data_lock_iterator() {
return new Innodb_data_lock_iterator();
}
PSI_engine_data_lock_wait_iterator *
Innodb_data_lock_inspector::create_data_lock_wait_iterator() {
return new Innodb_data_lock_wait_iterator();
}
void Innodb_data_lock_inspector::destroy_data_lock_iterator(
PSI_engine_data_lock_iterator *it) {
delete it;
}
void Innodb_data_lock_inspector::destroy_data_lock_wait_iterator(
PSI_engine_data_lock_wait_iterator *it) {
delete it;
}
/** Convert an identifier.
Convert identifiers stored in innodb to the proper
character set, and allocate memory for them in the
performance schema container.
@param[in] container The container to fill
@param[in] str The identifier string
@param[in] length The identifier string length
@param[out] converted_length The length of the converted string
@returns A string in UTF8, allocated in the performance schema container.
*/
const char *convert_identifier(PSI_server_data_lock_container *container,
const char *str, size_t length,
size_t *converted_length) {
if (str == NULL) {
*converted_length = 0;
return NULL;
}
const char *result_string;
size_t result_length;
char buffer[FN_REFLEN];
uint err_cs = 0;
result_length = my_convert(buffer, sizeof(buffer), system_charset_info, str,
length, &my_charset_filename, &err_cs);
ut_ad(err_cs == 0);
result_string = container->cache_data(buffer, result_length);
*converted_length = result_length;
return result_string;
}
/** Parse a table path string.
Isolate the table schema, name, partition and sub partition
from a table path string.
Convert these strings and store them in the performance schema container.
@note String returned are not zero terminated.
@param[in] container The container to fill
@param[in] table_path The table path string
@param[in] table_path_length The table path string length
@param[out] table_schema The table schema
@param[out] table_schema_length The table schema length
@param[out] table_name The table name
@param[out] table_name_length The table name length
@param[out] partition_name Partition name
@param[out] partition_name_length Partition name length
@param[out] subpartition_name Sub partition name
@param[out] subpartition_name_length Sub partition name length
*/
void parse_table_path(PSI_server_data_lock_container *container,
const char *table_path, size_t table_path_length,
const char **table_schema, size_t *table_schema_length,
const char **table_name, size_t *table_name_length,
const char **partition_name,
size_t *partition_name_length,
const char **subpartition_name,
size_t *subpartition_name_length) {
const char *p1;
size_t s1;
const char *p2;
size_t s2;
const char *p3;
size_t s3;
const char *p4;
size_t s4;
parse_filename(table_path, table_path_length, &p1, &s1, &p2, &s2, &p3, &s3,
&p4, &s4);
*table_schema = convert_identifier(container, p1, s1, table_schema_length);
*table_name = convert_identifier(container, p2, s2, table_name_length);
*partition_name =
convert_identifier(container, p3, s3, partition_name_length);
*subpartition_name =
convert_identifier(container, p4, s4, subpartition_name_length);
}
/** Print a table lock id.
@param[in] lock The lock to print
@param[in,out] lock_id Printing buffer
@param[in] lock_id_size Printing buffer length
*/
void print_table_lock_id(const lock_t *lock, char *lock_id,
size_t lock_id_size) {
/* We try to be backward compatible with INFORMATION_SCHEMA so that one can
join with INFORMATION_SCHEMA.innodb_trx.trx_requested_lock_id column */
i_s_locks_row_t row;
fill_locks_row(&row, lock, ULINT_UNDEFINED);
trx_i_s_create_lock_id(&row, lock_id, lock_id_size);
}
/** Print a record lock id.
@param[in] lock The lock to print
@param[in] heap_no Lock heap number
@param[in,out] lock_id Printing buffer
@param[in] lock_id_size Printing buffer length
*/
void print_record_lock_id(const lock_t *lock, ulint heap_no, char *lock_id,
size_t lock_id_size) {
/* We try to be backward compatible with INFORMATION_SCHEMA so that one can
join with INFORMATION_SCHEMA.innodb_trx.trx_requested_lock_id column */
i_s_locks_row_t row;
fill_locks_row(&row, lock, heap_no);
trx_i_s_create_lock_id(&row, lock_id, lock_id_size);
}
/** Print a lock id.
@param[in] lock The lock to print
@param[in] heap_no Lock heap number
@param[in,out] lock_id Printing buffer
@param[in] lock_id_size Printing buffer length
*/
void print_lock_id(const lock_t *lock, ulint heap_no, char *lock_id,
size_t lock_id_size) {
switch (lock_get_type(lock)) {
case LOCK_TABLE:
print_table_lock_id(lock, lock_id, lock_id_size);
break;
case LOCK_REC:
print_record_lock_id(lock, heap_no, lock_id, lock_id_size);
break;
default:
ut_error;
}
}
/** Scan a lock id string and extract information necessary to find a row
by primary key.
@param[in] lock_id The lock id string to parse
@param[out] trx_immutable_id The immutable id of lock->trx
@param[out] lock_immutable_id The immutable id of lock
@param[out] heap_id The heap number found, for record locks
@returns The type of lock found.
@retval LOCK_TABLE Table lock
@retval LOCK_REC Record lock
@retval 0 Format error
*/
int scan_lock_id(const char *lock_id, uint64_t *trx_immutable_id,
uint64_t *lock_immutable_id, ulint *heap_id) {
i_s_locks_row_t row;
int lock_type = trx_i_s_parse_lock_id(lock_id, &row);
if (!lock_type) {
return 0;
}
*trx_immutable_id = row.lock_trx_immutable_id;
*lock_immutable_id = row.lock_immutable_id;
if (lock_type == LOCK_REC) {
*heap_id = row.lock_rec;
}
return lock_type;
}
Innodb_data_lock_iterator::Innodb_data_lock_iterator() {}
Innodb_data_lock_iterator::~Innodb_data_lock_iterator() {}
bool Innodb_data_lock_iterator::scan(PSI_server_data_lock_container *container,
bool with_lock_data) {
if (m_scan_state.get_pass() == INIT_SCANNING) {
if (!container->accept_engine(g_engine, g_engine_length)) {
return true;
}
m_scan_state.prepare_next_scan();
}
if (m_scan_state.get_pass() == DONE_SCANNING) {
return true;
}
lock_mutex_enter();
trx_sys_mutex_enter();
size_t found = 0;
while ((m_scan_state.get_pass() == SCANNING_RW_TRX_LIST) && (found == 0)) {
found =
scan_trx_list(container, with_lock_data, true, &trx_sys->rw_trx_list);
m_scan_state.prepare_next_scan();
}
while ((m_scan_state.get_pass() == SCANNING_MYSQL_TRX_LIST) && (found == 0)) {
found = scan_trx_list(container, with_lock_data, false,
&trx_sys->mysql_trx_list);
m_scan_state.prepare_next_scan();
}
trx_sys_mutex_exit();
lock_mutex_exit();
return false;
}
bool Innodb_data_lock_iterator::fetch(PSI_server_data_lock_container *container,
const char *engine_lock_id,
size_t engine_lock_id_length,
bool with_lock_data) {
int record_type;
uint64_t trx_immutable_id;
ulint heap_id;
uint64_t lock_immutable_id;
const trx_t *trx;
if (!container->accept_engine(g_engine, g_engine_length)) {
return true;
}
record_type = scan_lock_id(engine_lock_id, &trx_immutable_id,
&lock_immutable_id, &heap_id);
if (record_type == 0) {
return true;
}
lock_mutex_enter();
trx_sys_mutex_enter();
trx = fetch_trx_in_trx_list(trx_immutable_id, true, &trx_sys->rw_trx_list);
if (trx == NULL) {
trx = fetch_trx_in_trx_list(trx_immutable_id, false,
&trx_sys->mysql_trx_list);
}
if (trx != NULL) {
scan_trx(container, with_lock_data, trx, true, lock_immutable_id, heap_id);
}
trx_sys_mutex_exit();
lock_mutex_exit();
return true;
}
/** Scan a trx list.
@param[in] container The container to fill
@param[in] with_lock_data True if column LOCK_DATA
needs to be populated.
@param[in] read_write True if the trx list is the RW list
@param[in] trx_list The trx list to scan
@returns The number of records found
*/
size_t Innodb_data_lock_iterator::scan_trx_list(
PSI_server_data_lock_container *container, bool with_lock_data,
bool read_write, trx_ut_list_t *trx_list) {
const trx_t *trx;
trx_id_t trx_id;
size_t found = 0;
ut_ad(lock_mutex_own());
ut_ad(trx_sys_mutex_own());
for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
trx = get_next_trx(trx, read_write)) {
if (discard_trx(trx, read_write)) {
continue;
}
trx_id = trx_get_id_for_print(trx);
if (!m_scan_state.trx_id_in_range(trx_id)) {
continue;
}
found += scan_trx(container, with_lock_data, trx, false);
}
return found;
}
/** Scan a given trx.
Either scan all the locks for a transaction,
or scan only records matching a given lock.
@param[in] container The container to fill
@param[in] with_lock_data True if column LOCK_DATA
needs to be populated.
@param[in] trx The trx to scan
@param[in] with_filter True if looking for a specific
record only.
@param[in] filter_lock_immutable_id Immutable id of lock_t we are looking for
@param[in] filter_heap_id Heap id to look for, when filtering
@returns The number of records found
*/
size_t Innodb_data_lock_iterator::scan_trx(
PSI_server_data_lock_container *container, bool with_lock_data,
const trx_t *trx, bool with_filter, uint64_t filter_lock_immutable_id,
ulint filter_heap_id) {
assert_trx_nonlocking_or_in_list(trx);
size_t found = 0;
const lock_t *lock;
ulonglong trx_id;
ulonglong thread_id;
ulonglong event_id;
const char *table_path;
const char *table_schema;
size_t table_schema_length;
const char *table_name;
size_t table_name_length;
const char *partition_name;
size_t partition_name_length;
const char *subpartition_name;
size_t subpartition_name_length;
const char *index_name;
size_t index_name_length;
const void *identity;
const char *lock_mode_str;
const char *lock_type_str;
const char *lock_status_str = "GRANTED";
const char *lock_data_str;
char engine_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1];
size_t engine_lock_id_length;
ulint heap_no;
int record_type;
lock_t *wait_lock;
wait_lock = trx->lock.wait_lock;
trx_id = trx_get_id_for_print(trx);
if (!container->accept_transaction_id(trx_id)) {
return 0;
}
for (lock = lock_get_first_trx_locks(&trx->lock); lock != NULL;
lock = lock_get_next_trx_locks(lock)) {
record_type = lock_get_type(lock);
if (with_filter &&
filter_lock_immutable_id != lock_get_immutable_id(lock)) {
continue;
}
lock_get_psi_event(lock, &thread_id, &event_id);
if (!container->accept_thread_id_event_id(thread_id, event_id)) {
continue;
}
table_path = lock_get_table_name(lock).m_name;
parse_table_path(container, table_path, strlen(table_path), &table_schema,
&table_schema_length, &table_name, &table_name_length,
&partition_name, &partition_name_length,
&subpartition_name, &subpartition_name_length);
if (!container->accept_object(table_schema, table_schema_length, table_name,
table_name_length, partition_name,
partition_name_length, subpartition_name,
subpartition_name_length)) {
continue;
}
identity = lock;
lock_mode_str = lock_get_mode_str(lock);
lock_type_str = lock_get_type_str(lock);
if (lock == wait_lock) {
lock_status_str = "WAITING";
}
switch (record_type) {
case LOCK_TABLE:
print_table_lock_id(lock, engine_lock_id, sizeof(engine_lock_id));
engine_lock_id_length = strlen(engine_lock_id);
if (container->accept_lock_id(engine_lock_id, engine_lock_id_length)) {
container->add_lock_row(
g_engine, g_engine_length, engine_lock_id, engine_lock_id_length,
trx_id, thread_id, event_id, table_schema, table_schema_length,
table_name, table_name_length, partition_name,
partition_name_length, subpartition_name,
subpartition_name_length, NULL, 0, identity, lock_mode_str,
lock_type_str, lock_status_str, NULL);
found++;
}
break;
case LOCK_REC:
index_name = lock_rec_get_index_name(lock);
index_name_length = strlen(index_name);
heap_no = lock_rec_find_set_bit(lock);
while (heap_no != ULINT_UNDEFINED) {
if (!with_filter || (heap_no == filter_heap_id)) {
print_record_lock_id(lock, heap_no, engine_lock_id,
sizeof(engine_lock_id));
engine_lock_id_length = strlen(engine_lock_id);
if (container->accept_lock_id(engine_lock_id,
engine_lock_id_length)) {
if (with_lock_data) {
p_s_fill_lock_data(&lock_data_str, lock, heap_no, container);
} else {
lock_data_str = NULL;
}
container->add_lock_row(
g_engine, g_engine_length, engine_lock_id,
engine_lock_id_length, trx_id, thread_id, event_id,
table_schema, table_schema_length, table_name,
table_name_length, partition_name, partition_name_length,
subpartition_name, subpartition_name_length, index_name,
index_name_length, identity, lock_mode_str, lock_type_str,
lock_status_str, lock_data_str);
found++;
}
}
heap_no = lock_rec_find_next_set_bit(lock, heap_no);
}
break;
default:
ut_error;
}
}
return found;
}
Innodb_data_lock_wait_iterator::Innodb_data_lock_wait_iterator()
{}
Innodb_data_lock_wait_iterator::~Innodb_data_lock_wait_iterator() {}
bool Innodb_data_lock_wait_iterator::scan(
PSI_server_data_lock_wait_container *container) {
if (m_scan_state.get_pass() == INIT_SCANNING) {
if (!container->accept_engine(g_engine, g_engine_length)) {
return true;
}
m_scan_state.prepare_next_scan();
}
if (m_scan_state.get_pass() == DONE_SCANNING) {
return true;
}
lock_mutex_enter();
trx_sys_mutex_enter();
size_t found = 0;
while ((m_scan_state.get_pass() == SCANNING_RW_TRX_LIST) && (found == 0)) {
found = scan_trx_list(container, true, &trx_sys->rw_trx_list);
m_scan_state.prepare_next_scan();
}
while ((m_scan_state.get_pass() == SCANNING_MYSQL_TRX_LIST) && (found == 0)) {
found = scan_trx_list(container, false, &trx_sys->mysql_trx_list);
m_scan_state.prepare_next_scan();
}
trx_sys_mutex_exit();
lock_mutex_exit();
return false;
}
bool Innodb_data_lock_wait_iterator::fetch(
PSI_server_data_lock_wait_container *container,
const char *requesting_engine_lock_id,
size_t requesting_engine_lock_id_length,
const char *blocking_engine_lock_id,
size_t blocking_engine_lock_id_length) {
int requesting_record_type;
uint64_t requesting_trx_immutable_id;
ulint requesting_heap_id;
uint64_t requesting_lock_immutable_id;
int blocking_record_type;
uint64_t blocking_trx_immutable_id;
ulint blocking_heap_id;
uint64_t blocking_lock_immutable_id;
const trx_t *trx;
if (!container->accept_engine(g_engine, g_engine_length)) {
return true;
}
requesting_record_type =
scan_lock_id(requesting_engine_lock_id, &requesting_trx_immutable_id,
&requesting_lock_immutable_id, &requesting_heap_id);
if (requesting_record_type == 0) {
return true;
}
blocking_record_type =
scan_lock_id(blocking_engine_lock_id, &blocking_trx_immutable_id,
&blocking_lock_immutable_id, &blocking_heap_id);
if (blocking_record_type == 0) {
return true;
}
lock_mutex_enter();
trx_sys_mutex_enter();
trx = fetch_trx_in_trx_list(requesting_trx_immutable_id, true,
&trx_sys->rw_trx_list);
if (trx == NULL) {
trx = fetch_trx_in_trx_list(requesting_trx_immutable_id, false,
&trx_sys->mysql_trx_list);
}
if (trx != NULL) {
scan_trx(container, trx, true, requesting_lock_immutable_id,
blocking_lock_immutable_id);
}
trx_sys_mutex_exit();
lock_mutex_exit();
return true;
}
/** Scan a given transaction list.
@param[in] container The container to fill
@param[in] read_write True if the transaction list is the RW list.
@param[in] trx_list The trx list to scan
@returns the number of records found.
*/
size_t Innodb_data_lock_wait_iterator::scan_trx_list(
PSI_server_data_lock_wait_container *container, bool read_write,
trx_ut_list_t *trx_list) {
const trx_t *trx;
trx_id_t trx_id;
size_t found = 0;
ut_ad(lock_mutex_own());
ut_ad(trx_sys_mutex_own());
for (trx = UT_LIST_GET_FIRST(*trx_list); trx != NULL;
trx = get_next_trx(trx, read_write)) {
if (discard_trx(trx, read_write)) {
continue;
}
trx_id = trx_get_id_for_print(trx);
if (!m_scan_state.trx_id_in_range(trx_id)) {
continue;
}
found += scan_trx(container, trx, false);
}
return found;
}
/** Scan a given transaction.
Either scan all the waits for a transaction,
or scan only records matching a given wait.
@param[in] container The container to fill
@param[in] trx The trx to scan
@param[in] with_filter True if looking for a given wait only.
@param[in] filter_requesting_lock_immutable_id Immutable id of
lock_t for the requesting lock, when filtering
@param[in] filter_blocking_lock_immutable_id Immutable idof
lock_t for the blocking lock, when filtering
@returns the number of records found.
*/
size_t Innodb_data_lock_wait_iterator::scan_trx(
PSI_server_data_lock_wait_container *container, const trx_t *trx,
bool with_filter, uint64_t filter_requesting_lock_immutable_id,
uint64_t filter_blocking_lock_immutable_id) {
assert_trx_nonlocking_or_in_list(trx);
if (trx->lock.que_state != TRX_QUE_LOCK_WAIT) {
return 0;
}
ulonglong requesting_trx_id;
ulonglong requesting_thread_id;
ulonglong requesting_event_id;
const void *requesting_identity;
char requesting_engine_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1];
size_t requesting_engine_lock_id_length;
ulonglong blocking_trx_id;
ulonglong blocking_thread_id;
ulonglong blocking_event_id;
const void *blocking_identity;
char blocking_engine_lock_id[TRX_I_S_LOCK_ID_MAX_LEN + 1];
size_t blocking_engine_lock_id_length;
lock_t *wait_lock = trx->lock.wait_lock;
const lock_t *curr_lock;
int requesting_record_type;
size_t found = 0;
lock_queue_iterator_t iter;
ut_a(wait_lock != NULL);
requesting_record_type = lock_get_type(wait_lock);
if (with_filter &&
lock_get_immutable_id(wait_lock) != filter_requesting_lock_immutable_id) {
return 0;
}
requesting_trx_id = trx_get_id_for_print(trx);
if (!container->accept_requesting_transaction_id(requesting_trx_id)) {
return 0;
}
lock_get_psi_event(wait_lock, &requesting_thread_id, &requesting_event_id);
if (!container->accept_requesting_thread_id_event_id(requesting_thread_id,
requesting_event_id)) {
return 0;
}
ulint heap_no = 0;
if (requesting_record_type == LOCK_REC) {
heap_no = lock_rec_find_set_bit(wait_lock);
}
print_lock_id(wait_lock, heap_no, requesting_engine_lock_id,
sizeof(requesting_engine_lock_id));
requesting_engine_lock_id_length = strlen(requesting_engine_lock_id);
if (!container->accept_requesting_lock_id(requesting_engine_lock_id,
requesting_engine_lock_id_length)) {
return 0;
}
requesting_identity = wait_lock;
lock_queue_iterator_reset(&iter, wait_lock, ULINT_UNDEFINED);
for (curr_lock = lock_queue_iterator_get_prev(&iter); curr_lock != NULL;
curr_lock = lock_queue_iterator_get_prev(&iter)) {
if (with_filter &&
lock_get_immutable_id(curr_lock) != filter_blocking_lock_immutable_id) {
continue;
}
if (lock_has_to_wait(wait_lock, curr_lock)) {
blocking_trx_id = lock_get_trx_id(curr_lock);
if (!container->accept_blocking_transaction_id(blocking_trx_id)) {
continue;
}
lock_get_psi_event(curr_lock, &blocking_thread_id, &blocking_event_id);
if (!container->accept_blocking_thread_id_event_id(blocking_thread_id,
blocking_event_id)) {
continue;
}
blocking_identity = curr_lock;
print_lock_id(curr_lock, heap_no, blocking_engine_lock_id,
sizeof(blocking_engine_lock_id));
blocking_engine_lock_id_length = strlen(blocking_engine_lock_id);
if (!container->accept_blocking_lock_id(blocking_engine_lock_id,
blocking_engine_lock_id_length)) {
continue;
}
container->add_lock_wait_row(
g_engine, g_engine_length, requesting_engine_lock_id,
requesting_engine_lock_id_length, requesting_trx_id,
requesting_thread_id, requesting_event_id, requesting_identity,
blocking_engine_lock_id, blocking_engine_lock_id_length,
blocking_trx_id, blocking_thread_id, blocking_event_id,
blocking_identity);
found++;
}
}
return found;
}