sql/sql_plans.cc (194 lines of code) (raw):
#include "sql_base.h"
#include "sql_parse.h"
#include "sql_show.h"
#include "mysqld.h"
#ifdef HAVE_RAPIDJSON
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#endif
#include <boost/algorithm/string/trim.hpp>
#include "my_md5.h"
/*
SQL_PLAN
Provides the execution plan details of SQL statements
*/
#define SQL_PLAN_LENGTH_MAX 8192
ST_FIELD_INFO sql_plan_fields_info[]=
{
{"PLAN_ID", MD5_BUFF_LENGTH, MYSQL_TYPE_STRING, 0,0,0, SKIP_OPEN_TABLE},
{"PLAN_LENGTH", MY_INT64_NUM_DECIMAL_DIGITS, MYSQL_TYPE_LONGLONG,
0, MY_I_S_UNSIGNED, 0, SKIP_OPEN_TABLE},
{"PLAN_DATA", SQL_PLAN_LENGTH_MAX, MYSQL_TYPE_STRING, 0,0,0, SKIP_OPEN_TABLE},
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
};
/* Global sql plan hash map to track and update plans in-memory */
std::unordered_map<md5_key, SQL_PLAN*> global_sql_plans;
/*
The current utilization for the sql plans
*/
ulonglong sql_plans_size = 0;
/*
It's possible for this mutex to be locked twice by one thread when
ha_myisam::write_row() errors out during a information schema query.
We use an error checking mutex here so we can handle this situation.
More details: https://github.com/facebook/mysql-5.6/issues/132.
*/
static bool lock_sql_plans()
{
/*
In debug mode safe_mutex is turned on, and
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP is ignored.
However, safe_mutex contains information about the thread ID
which we can use to determine if we are re-locking the calling thread.
In release builds pthread_mutex_t is used, which respects the
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP property.
*/
#if defined(SAFE_MUTEX) && !defined(DBUG_OFF)
if (!pthread_equal(pthread_self(),
(&(&LOCK_global_sql_plans)->m_mutex)->thread))
{
mysql_mutex_lock(&LOCK_global_sql_plans);
return false;
}
return true;
#else
return mysql_mutex_lock(&LOCK_global_sql_plans) == EDEADLK;
#endif
}
static void unlock_sql_plans(bool acquired)
{
/* If lock was already acquired by calling thread, do nothing. */
if (acquired)
return;
/* Otherwise, unlock the mutex */
mysql_mutex_unlock(&LOCK_global_sql_plans);
}
/*
free_global_sql_plans
Frees global_sql_plans map and its content
*/
void free_global_sql_plans(void)
{
bool lock_acquired = lock_sql_plans();
for (auto it= global_sql_plans.begin(); it != global_sql_plans.end(); ++it)
{
my_free(it->second->plan_data);
my_free(it->second);
}
global_sql_plans.clear();
sql_plans_size = 0;
unlock_sql_plans(lock_acquired);
}
/*
normalize_plan and friends (array, object, node)
Traverse the JSON document and remove members that match
undesired types ("rows", "filtered", "attached_condition",
"index_condition", "partitions")
*/
#ifdef HAVE_RAPIDJSON
static bool is_excluded_name(const std::string& name)
{
if (!name.empty() &&
(name == "rows" ||
name == "filtered" ||
name == "partitions" ||
name == "index_condition" ||
name == "attached_condition"))
return true;
else
return false;
}
static void normalize_array(rapidjson::Value& node);
static void normalize_object(rapidjson::Value &node);
static void normalize_node(rapidjson::Value &node)
{
if (node.IsArray())
normalize_array(node);
else if (node.IsObject())
normalize_object(node);
}
static void normalize_object(rapidjson::Value &node)
{
for (auto childNode = node.MemberBegin(); childNode != node.MemberEnd(); )
{
if (is_excluded_name(childNode->name.GetString()))
childNode = node.RemoveMember(childNode);
else
{
normalize_node(childNode->value);
++childNode;
}
}
}
static void normalize_array(rapidjson::Value& node)
{
for (rapidjson::SizeType i = 0; i < node.Size(); ++i)
normalize_node(node[i]);
}
static void normalize_plan(rapidjson::Value& node)
{
normalize_node(node);
}
#endif /*HAVE_RAPIDJSON*/
/*
insert_sql_plan
Inserts the provided SQL plan: compute the plan ID tne store it in the global
plan map if it does not exist yet.
*/
void insert_sql_plan(THD *thd, String *json_plan)
{
if (json_plan->is_empty())
return;
/* Check whether we reached the limits for SQL stats and plans
This is the first check before acquiring the lock on HT
*/
if (is_sql_stats_collection_above_limit())
return;
/* Compute the Plan ID as the MD5 hash of the plan data */
md5_key plan_id;
#ifdef HAVE_RAPIDJSON
if (normalized_plan_id)
{
memset(plan_id.data(), 0, MD5_HASH_SIZE);
/* parse JSON plan (string form) and get the JSON document */
rapidjson::Document plan_doc;
if (!plan_doc.Parse(json_plan->c_ptr()).HasParseError() &&
plan_doc.IsObject())
{
/* Traverse the JSON document and remove members that match
undesired types ("rows", "filtered", "attached_condition"
*/
normalize_plan(plan_doc);
/* get the normalized plan (document form) as a JSON string */
rapidjson::StringBuffer plan_buf;
rapidjson::Writer<rapidjson::StringBuffer> writer(plan_buf);
if (plan_doc.Accept(writer))
{
std::string norm_plan = plan_buf.GetString();
boost::trim(norm_plan);
compute_md5_hash((char*) plan_id.data(),
norm_plan.c_str(),
norm_plan.length());
}
}
}
else
#endif /*HAVE_RAPIDJSON*/
compute_md5_hash((char*) plan_id.data(),
json_plan->c_ptr(),
json_plan->length());
bool lock_acquired = lock_sql_plans();
// Check again inside the lock and release the lock if exiting
if (is_sql_stats_collection_above_limit())
{
unlock_sql_plans(lock_acquired);
return;
}
/* Get or create the SQL_PLAN object for this sql statement. */
SQL_PLAN *sql_plan;
auto sql_plan_iter= global_sql_plans.find(plan_id);
if (sql_plan_iter == global_sql_plans.end())
{
if (!(sql_plan= ((SQL_PLAN*)my_malloc(sizeof(SQL_PLAN), MYF(MY_WME)))) ||
!(sql_plan->plan_data= ((char*)my_malloc(MY_MIN(json_plan->length(),
SQL_PLAN_LENGTH_MAX) + 1,
MYF(MY_WME)))))
{
sql_print_error("Cannot allocate memory for SQL_PLAN.");
my_free(sql_plan->plan_data);
my_free(sql_plan);
unlock_sql_plans(lock_acquired);
return;
}
/* store the original plan length */
sql_plan->plan_len = json_plan->length();
/* store truncated plan, up to 8k */
uint plan_len = MY_MIN(json_plan->length(), SQL_PLAN_LENGTH_MAX);
memcpy(sql_plan->plan_data, json_plan->c_ptr(), plan_len);
sql_plan->plan_data[plan_len] = '\0';
thd->mt_key_set(THD::PLAN_ID, plan_id.data());
auto ret= global_sql_plans.emplace(plan_id, sql_plan);
if (! ret.second)
DBUG_ASSERT(0);
sql_plans_size += (MD5_HASH_SIZE + sizeof(SQL_PLAN) + plan_len);
}
else
thd->mt_key_set(THD::PLAN_ID, sql_plan_iter->first.data());
unlock_sql_plans(lock_acquired);
}
/* Fills the SQL_PLANS table. */
int fill_sql_plans(THD *thd, TABLE_LIST *tables, Item *cond)
{
DBUG_ENTER("fill_sql_plans");
TABLE* table= tables->table;
int result = 0;
if (mt_tables_access_control && check_global_access(thd, PROCESS_ACL))
result = -1;
bool lock_acquired = lock_sql_plans();
for (auto iter= global_sql_plans.cbegin();
!result && iter != global_sql_plans.cend(); ++iter)
{
int f= 0;
SQL_PLAN *sql_plan = iter->second;
restore_record(table, s->default_values);
/* PLAN ID */
char plan_id_hex_string[MD5_BUFF_LENGTH];
array_to_hex(plan_id_hex_string, iter->first.data(), iter->first.size());
table->field[f++]->store(plan_id_hex_string, MD5_BUFF_LENGTH,
system_charset_info);
/* Plan length */
table->field[f++]->store(sql_plan->plan_len, TRUE);
/* Plan data */
table->field[f++]->store(sql_plan->plan_data,
sql_plan->plan_len,
system_charset_info);
if (schema_table_store_record(thd, table))
result = -1;
}
unlock_sql_plans(lock_acquired);
DBUG_RETURN(result);
}