516 lines
16 KiB
C++
516 lines
16 KiB
C++
/* Copyright (c) 2018, 2021, Alibaba 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/PolarDB-X Engine 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/PolarDB-X Engine.
|
|
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 */
|
|
|
|
|
|
#include "my_murmur3.h" // my_murmur3_32
|
|
#include "my_sys.h" // MY_WME
|
|
#include "mysql/components/services/psi_mutex_bits.h" // PSI_mutex_key
|
|
#include "mysql/components/services/psi_rwlock_bits.h" // PSI_rwlock_key
|
|
#include "mysql/psi/mysql_memory.h"
|
|
#include "mysql/psi/mysql_mutex.h"
|
|
#include "mysql/psi/mysql_rwlock.h"
|
|
#include "mysql/service_mysql_alloc.h" // my_malloc
|
|
#include "sql/auth/auth_acls.h" // SELECT_ACL
|
|
#include "sql/auth/auth_common.h" // check_access
|
|
#include "sql/field.h" // store
|
|
#include "sql/handler.h" // update_statistics
|
|
#include "sql/log.h"
|
|
#include "sql/psi_memory_key.h"
|
|
|
|
#include "sql/table.h"
|
|
|
|
#include "sql/sql_show.h"
|
|
#include "sql/sql_statistics.h"
|
|
|
|
bool opt_tablestat = true;
|
|
bool opt_indexstat = true;
|
|
|
|
/* Max retry times when generate object stats */
|
|
static uint object_statistics_retry_max_times = 10;
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
static PSI_memory_key key_memory_object_stats;
|
|
static PSI_rwlock_key key_LOCK_object_stats;
|
|
#endif
|
|
|
|
ST_FIELD_INFO table_stats_fields_info[] = {
|
|
{"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema", 0},
|
|
{"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name", 0},
|
|
{"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0,
|
|
"Rows_read", 0},
|
|
{"ROWS_CHANGED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0,
|
|
"Rows_changed", 0},
|
|
{"ROWS_CHANGED_X_INDEXES", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
|
|
0, 0, "Rows_changed_x_#indexes", 0},
|
|
{"ROWS_INSERTED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0,
|
|
"Rows_inserted", 0},
|
|
{"ROWS_DELETED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0,
|
|
"Rows_deleted", 0},
|
|
{"ROWS_UPDATED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG, 0, 0,
|
|
"Rows_updated", 0},
|
|
{"ROWS_READ_DELETE_MARK", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
|
|
0, 0, "Rows_read_delete_mark", 0},
|
|
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}};
|
|
|
|
ST_FIELD_INFO index_stats_fields_info[] = {
|
|
{"TABLE_SCHEMA", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_schema", 0},
|
|
{"TABLE_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Table_name", 0},
|
|
{"INDEX_NAME", NAME_LEN, MYSQL_TYPE_STRING, 0, 0, "Index_name", 0},
|
|
{"ROWS_READ", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0,
|
|
"Rows_read", 0},
|
|
{"SCAN_USED", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONG, 0, 0,
|
|
"Scan_used", 0},
|
|
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, 0}};
|
|
|
|
|
|
|
|
/**
|
|
We want to accumulate the memory, so define a new allocator.
|
|
*/
|
|
void *String_stats_alloc::operator()(size_t s) const {
|
|
return my_malloc(key_memory_object_stats, s, MYF(MY_WME | ME_FATALERROR));
|
|
}
|
|
|
|
/**
|
|
If String_stats_type is treated as key, here should realize the hash function.
|
|
the std::hash template specialization.
|
|
*/
|
|
namespace std {
|
|
|
|
size_t hash<String_stats_type>::operator()(const String_stats_type &s) const {
|
|
return murmur3_32(reinterpret_cast<const uchar *>(s.c_str()), s.size(), 0);
|
|
}
|
|
|
|
} // namespace std;
|
|
|
|
template String_stats_type::~basic_string();
|
|
|
|
/**
|
|
Allocate and init a new Object_stats.
|
|
|
|
@retval Object_stats New allocated object
|
|
*/
|
|
template <typename T>
|
|
T *object_stats_allocator() {
|
|
void *ptr = nullptr;
|
|
T *stats = nullptr;
|
|
|
|
ptr = my_malloc(key_memory_object_stats, sizeof(T),
|
|
MYF(MY_WME | ME_FATALERROR));
|
|
|
|
if (ptr) stats = new (ptr) T();
|
|
|
|
return stats;
|
|
}
|
|
|
|
Table_stats::Table_stats() {
|
|
rows_read = 0;
|
|
rows_changed = 0;
|
|
rows_deleted = 0;
|
|
rows_inserted = 0;
|
|
rows_updated = 0;
|
|
rows_changed_x_indexes = 0;
|
|
rds_rows_read_del_mark = 0;
|
|
}
|
|
|
|
/**
|
|
Accumulate the data from Stats_data;
|
|
|
|
@param[in] data Stats_data
|
|
@param[in] key_number How many keys
|
|
*/
|
|
void Table_stats::accumulate(Stats_data &data, size_t key_number) {
|
|
rows_read += data.rows_read;
|
|
rows_changed += data.rows_changed;
|
|
rows_deleted += data.rows_deleted;
|
|
rows_inserted += data.rows_inserted;
|
|
rows_updated += data.rows_updated;
|
|
rows_changed_x_indexes += data.rows_changed * (key_number ? key_number : 1);
|
|
rds_rows_read_del_mark += data.rds_rows_read_del_mark;
|
|
}
|
|
|
|
Index_stats::Index_stats() {
|
|
rows_read = 0;
|
|
scan_used = 0;
|
|
}
|
|
|
|
/**
|
|
Accumulate the data from Stats_data;
|
|
|
|
@param[in] data Stats_data
|
|
@param[in] key_number Which key
|
|
*/
|
|
void Index_stats::accumulate(Stats_data &data, size_t key_number) {
|
|
if (key_number < MAX_KEY) rows_read += data.index_rows_read[key_number];
|
|
if (key_number < MAX_KEY)
|
|
scan_used += data.index_scan_used[key_number];
|
|
}
|
|
|
|
/* Table_stats template specialization */
|
|
template Table_stats *object_stats_allocator<Table_stats>();
|
|
|
|
/* Index_stats template specialization */
|
|
template Index_stats *object_stats_allocator<Index_stats>();
|
|
|
|
/**
|
|
Constructor of Object stats cache
|
|
*/
|
|
Object_stats_cache::Object_stats_cache()
|
|
: m_table_map(key_memory_object_stats),
|
|
m_index_map(key_memory_object_stats) {
|
|
init();
|
|
}
|
|
|
|
/**
|
|
The singleton instance entry.
|
|
*/
|
|
Object_stats_cache *Object_stats_cache::instance() {
|
|
static Object_stats_cache cache;
|
|
return &cache;
|
|
}
|
|
|
|
void Object_stats_cache::init() {
|
|
mysql_rwlock_init(key_LOCK_object_stats, &m_table_lock);
|
|
mysql_rwlock_init(key_LOCK_object_stats, &m_index_lock);
|
|
}
|
|
|
|
Object_stats_cache::~Object_stats_cache() {}
|
|
|
|
void Object_stats_cache::destroy() {
|
|
mysql_rwlock_destroy(&m_table_lock);
|
|
mysql_rwlock_destroy(&m_index_lock);
|
|
destroy_map<Table_stats>(map<Table_stats>());
|
|
destroy_map<Index_stats>(map<Index_stats>());
|
|
}
|
|
|
|
/**
|
|
Accumulate the table statistics
|
|
|
|
@param[in] table TABLE object
|
|
@param[in] data stats_data reference
|
|
*/
|
|
void Object_stats_cache::update_table_stats(TABLE_SHARE *share) {
|
|
Stats_data &data = share->stats_data;
|
|
Object_stats *stats = nullptr;
|
|
|
|
if ((!data.rows_read && !data.rows_changed && !data.rds_rows_read_del_mark) ||
|
|
!share->db.str || !share->table_name.str)
|
|
return;
|
|
|
|
String_stats_type key(share->table_cache_key.str,
|
|
share->table_cache_key.length);
|
|
|
|
RWlock_helper helper(&m_table_lock, false);
|
|
DBUG_ASSERT(helper.effect());
|
|
uint retrys = 0;
|
|
|
|
retry:
|
|
if (retrys++ > object_statistics_retry_max_times) {
|
|
sql_print_warning("Table statistics retry %d times.", retrys);
|
|
return;
|
|
}
|
|
helper.rdlock();
|
|
if (!(stats = lookup<Table_stats>(key))) {
|
|
helper.unlock();
|
|
helper.wrlock();
|
|
|
|
std::pair<Table_stats *, bool> res = generate_stats<Table_stats>(key);
|
|
if (!res.second) {
|
|
helper.unlock();
|
|
goto retry;
|
|
} else {
|
|
stats = res.first;
|
|
}
|
|
}
|
|
|
|
/* TODO: Move it later if support flushing in future. */
|
|
helper.unlock();
|
|
if (stats) {
|
|
stats->accumulate(data, share->keys);
|
|
data.reset_table();
|
|
}
|
|
}
|
|
|
|
/**
|
|
Accumulate the index statistics
|
|
|
|
@param[in] table TABLE object
|
|
@param[in] data stats_data reference
|
|
*/
|
|
void Object_stats_cache::update_index_stats(TABLE_SHARE *share) {
|
|
char buf[NAME_LEN * 3 + 3];
|
|
size_t length;
|
|
KEY *info;
|
|
Object_stats *stats = nullptr;
|
|
Stats_data &data = share->stats_data;
|
|
|
|
for (size_t i = 0; i < share->keys; i++) {
|
|
stats = nullptr;
|
|
if (data.index_rows_read[i] > 0) {
|
|
info = share->key_info + i;
|
|
length = snprintf(buf, NAME_LEN * 3 + 3, "%s%c%s%c%s", share->db.str,
|
|
'\0', share->table_name.str, '\0', info->name);
|
|
String_stats_type key(buf, length);
|
|
|
|
RWlock_helper helper(&m_index_lock, false);
|
|
DBUG_ASSERT(helper.effect());
|
|
uint retrys = 0;
|
|
|
|
retry:
|
|
if (retrys++ > object_statistics_retry_max_times) {
|
|
sql_print_warning("Index statistics retry %d times.", retrys);
|
|
return;
|
|
}
|
|
helper.rdlock();
|
|
if (!(stats = lookup<Index_stats>(key))) {
|
|
helper.unlock();
|
|
helper.wrlock();
|
|
std::pair<Index_stats *, bool> res = generate_stats<Index_stats>(key);
|
|
|
|
if (!res.second) {
|
|
helper.unlock();
|
|
goto retry;
|
|
} else {
|
|
stats = res.first;
|
|
}
|
|
}
|
|
/* TODO: Move it later if support flushing in future. */
|
|
helper.unlock();
|
|
if (stats) {
|
|
stats->accumulate(data, i);
|
|
data.reset_index(i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Fill the statistics into table_statisitcs table.
|
|
|
|
@param[in] thd User connection context
|
|
@param[in] table Current opened schema table
|
|
*/
|
|
int Object_stats_cache::fill_table_stats(THD *thd, TABLE *table) {
|
|
const char *table_schema;
|
|
const char *table_name;
|
|
|
|
RWlock_helper helper(&m_table_lock, true);
|
|
DBUG_ASSERT(helper.effect());
|
|
helper.rdlock();
|
|
for (auto it = map<Table_stats>().cbegin(); it != map<Table_stats>().cend();
|
|
it++) {
|
|
restore_record(table, s->default_values);
|
|
|
|
table_schema = (it->first).c_str();
|
|
table_name = table_schema + strlen(table_schema) + 1;
|
|
|
|
const Table_stats *table_stats = it->second;
|
|
TABLE_LIST tmp_table;
|
|
|
|
memset((char *)&tmp_table, 0, sizeof(tmp_table));
|
|
tmp_table.table_name = table_name;
|
|
tmp_table.db = table_schema;
|
|
tmp_table.grant.privilege = 0;
|
|
|
|
if (check_access(thd, SELECT_ACL, tmp_table.db, &tmp_table.grant.privilege,
|
|
0, 0, is_infoschema_db(table_schema)) ||
|
|
check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1))
|
|
continue;
|
|
|
|
table->field[0]->store(table_schema, strlen(table_schema), system_charset_info);
|
|
table->field[1]->store(table_name, strlen(table_name), system_charset_info);
|
|
table->field[2]->store((longlong)table_stats->rows_read, true);
|
|
table->field[3]->store((longlong)table_stats->rows_changed, true);
|
|
table->field[4]->store((longlong)table_stats->rows_changed_x_indexes, true);
|
|
|
|
table->field[5]->store((longlong)table_stats->rows_inserted, true);
|
|
table->field[6]->store((longlong)table_stats->rows_deleted, true);
|
|
table->field[7]->store((longlong)table_stats->rows_updated, true);
|
|
table->field[8]->store((longlong)table_stats->rds_rows_read_del_mark, true);
|
|
|
|
if (schema_table_store_record(thd, table)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Fill the statistics into index_statisitcs table.
|
|
|
|
@param[in] thd User connection context
|
|
@param[in] table Current opened schema table
|
|
*/
|
|
int Object_stats_cache::fill_index_stats(THD *thd, TABLE *table) {
|
|
const char *table_schema;
|
|
const char *table_name;
|
|
const char *index_name;
|
|
|
|
RWlock_helper helper(&m_index_lock, true);
|
|
DBUG_ASSERT(helper.effect());
|
|
|
|
helper.rdlock();
|
|
for (auto it = map<Index_stats>().cbegin(); it != map<Index_stats>().cend();
|
|
it++) {
|
|
restore_record(table, s->default_values);
|
|
|
|
table_schema = (it->first).c_str();
|
|
table_name = table_schema + strlen(table_schema) + 1;
|
|
index_name = table_name + strlen(table_name) + 1;
|
|
|
|
const Index_stats *index_stats = it->second;
|
|
TABLE_LIST tmp_table;
|
|
|
|
memset((char *)&tmp_table, 0, sizeof(tmp_table));
|
|
tmp_table.table_name = table_name;
|
|
tmp_table.db = table_schema;
|
|
tmp_table.grant.privilege = 0;
|
|
|
|
if (check_access(thd, SELECT_ACL, tmp_table.db,
|
|
&tmp_table.grant.privilege, 0, 0,
|
|
is_infoschema_db(table_schema)) ||
|
|
check_grant(thd, SELECT_ACL, &tmp_table, 1, UINT_MAX, 1))
|
|
continue;
|
|
|
|
table->field[0]->store(table_schema, strlen(table_schema), system_charset_info);
|
|
table->field[1]->store(table_name, strlen(table_name), system_charset_info);
|
|
table->field[2]->store(index_name, strlen(index_name), system_charset_info);
|
|
table->field[3]->store((longlong)index_stats->rows_read, true);
|
|
table->field[4]->store((longlong)index_stats->scan_used, true);
|
|
|
|
if (schema_table_store_record(thd, table)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void handler::update_table_statistics() {
|
|
if (!table || !table->s ||
|
|
(!stats_data.rows_read && !stats_data.rows_changed &&
|
|
!stats_data.rds_rows_read_del_mark) ||
|
|
!table->s->db.str || !table->s->table_name.str)
|
|
return;
|
|
|
|
table->s->stats_data.rows_read += stats_data.rows_read;
|
|
table->s->stats_data.rows_changed += stats_data.rows_changed;
|
|
table->s->stats_data.rows_inserted += stats_data.rows_inserted;
|
|
table->s->stats_data.rows_deleted += stats_data.rows_deleted;
|
|
table->s->stats_data.rows_updated += stats_data.rows_updated;
|
|
table->s->stats_data.rds_rows_read_del_mark +=
|
|
stats_data.rds_rows_read_del_mark;
|
|
}
|
|
|
|
void handler::update_index_statistics() {
|
|
if (!table || !table->s || !table->s->table_cache_key.str || !table->s->table_name.str)
|
|
return;
|
|
|
|
for (uint x = 0; x < table->s->keys; ++x) {
|
|
if (stats_data.index_rows_read[x]) {
|
|
table->s->stats_data.index_rows_read[x] += stats_data.index_rows_read[x];
|
|
table->s->stats_data.index_scan_used[x] ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Accumulate the operated rows once statement end.
|
|
*/
|
|
void handler::update_statistics() {
|
|
if (opt_tablestat)
|
|
update_table_statistics();
|
|
|
|
if (opt_indexstat)
|
|
update_index_statistics();
|
|
|
|
stats_data.reset();
|
|
}
|
|
|
|
/**
|
|
Send the table stats back to the client.
|
|
*/
|
|
int fill_schema_table_stats(THD *thd, TABLE_LIST *tables,
|
|
Item *__attribute__((unused))) {
|
|
TABLE *table = tables->table;
|
|
DBUG_ENTER("fill_schema_table_stats");
|
|
|
|
mysql_mutex_lock(&LOCK_open);
|
|
|
|
for (const auto &key_and_value : *table_def_cache) {
|
|
TABLE_SHARE *share = key_and_value.second.get();
|
|
Object_stats_cache::instance()->update_table_stats(share);
|
|
}
|
|
|
|
mysql_mutex_unlock(&LOCK_open);
|
|
|
|
DBUG_RETURN(Object_stats_cache::instance()->fill_table_stats(thd, table));
|
|
}
|
|
|
|
/**
|
|
Send the index stats back to the client.
|
|
*/
|
|
int fill_schema_index_stats(THD *thd, TABLE_LIST *tables,
|
|
Item *__attribute__((unused))) {
|
|
TABLE *table = tables->table;
|
|
DBUG_ENTER("fill_schema_index_stats");
|
|
|
|
mysql_mutex_lock(&LOCK_open);
|
|
|
|
for (const auto &key_and_value : *table_def_cache) {
|
|
TABLE_SHARE *share = key_and_value.second.get();
|
|
Object_stats_cache::instance()->update_index_stats(share);
|
|
}
|
|
|
|
mysql_mutex_unlock(&LOCK_open);
|
|
|
|
DBUG_RETURN(Object_stats_cache::instance()->fill_index_stats(thd, table));
|
|
}
|
|
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
static PSI_rwlock_info object_statistics_rwlocks[] = {
|
|
{&key_LOCK_object_stats, "LOCK_sql_statistics", PSI_FLAG_SINGLETON, 0,
|
|
PSI_DOCUMENT_ME}};
|
|
|
|
static PSI_memory_info object_statistics_memory[] = {
|
|
{&key_memory_object_stats, "object_statistics", PSI_FLAG_ONLY_GLOBAL_STAT,
|
|
0, PSI_DOCUMENT_ME}};
|
|
|
|
static void init_object_statistics_psi_keys() {
|
|
const char *category = "sql";
|
|
int count;
|
|
|
|
count = static_cast<int>(array_elements(object_statistics_rwlocks));
|
|
mysql_rwlock_register(category, object_statistics_rwlocks, count);
|
|
|
|
count = static_cast<int>(array_elements(object_statistics_memory));
|
|
mysql_memory_register(category, object_statistics_memory, count);
|
|
}
|
|
#endif
|
|
/**
|
|
Init the object statistics context when booting, since the singleton instance
|
|
is not thread safe.
|
|
*/
|
|
void object_statistics_context_init() {
|
|
#ifdef HAVE_PSI_INTERFACE
|
|
init_object_statistics_psi_keys();
|
|
#endif
|
|
|
|
Object_stats_cache::instance();
|
|
}
|
|
|
|
void object_statistics_context_destroy() {
|
|
Object_stats_cache::instance()->destroy();
|
|
}
|