mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 03:52:35 +01:00
58cd0bd59e
When a range rowid filter was used with an index ref access the cost of accessing the index entries for the records rejected by the filter was not taken into account. For a ref access by an index with big average number of records per key this led to poor execution plans if selectivity of the used filter was high. The patch resolves this problem. It also introduces a minor optimization that skips look-ups into a filter that turns out to be empty. With this patch the output of ANALYZE stmt reports the number of look-ups into used rowid filters. The patch also back-ports from 10.5 the code that properly sets the field TABLE::file::table for opened temporary tables. The test cases that were supposed to use rowid filters have been adjusted in order to use similar execution plans after this fix. Approved by Oleksandr Byelkin <sanja@mariadb.com>
2671 lines
71 KiB
C++
2671 lines
71 KiB
C++
/*
|
|
Copyright (c) 2013 Monty Program Ab
|
|
|
|
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 Street, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#ifdef USE_PRAGMA_IMPLEMENTATION
|
|
#pragma implementation // gcc: Class implementation
|
|
#endif
|
|
|
|
#include "mariadb.h"
|
|
#include "sql_priv.h"
|
|
#include "sql_select.h"
|
|
#include "my_json_writer.h"
|
|
#include "opt_range.h"
|
|
#include "sql_expression_cache.h"
|
|
|
|
const char * STR_DELETING_ALL_ROWS= "Deleting all rows";
|
|
const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE";
|
|
const char * STR_NO_ROWS_AFTER_PRUNING= "No matching rows after partition pruning";
|
|
|
|
const char *unit_operation_text[4]=
|
|
{
|
|
"UNIT RESULT","UNION RESULT","INTERSECT RESULT","EXCEPT RESULT"
|
|
};
|
|
|
|
const char *pushed_derived_text= "PUSHED DERIVED";
|
|
const char *pushed_select_text= "PUSHED SELECT";
|
|
|
|
static void write_item(Json_writer *writer, Item *item);
|
|
static void append_item_to_str(String *out, Item *item);
|
|
|
|
Explain_query::Explain_query(THD *thd_arg, MEM_ROOT *root) :
|
|
mem_root(root), upd_del_plan(NULL), insert_plan(NULL),
|
|
unions(root), selects(root), thd(thd_arg), apc_enabled(false),
|
|
operations(0)
|
|
{
|
|
}
|
|
|
|
static void print_json_array(Json_writer *writer,
|
|
const char *title, String_list &list)
|
|
{
|
|
List_iterator_fast<char> it(list);
|
|
const char *name;
|
|
writer->add_member(title).start_array();
|
|
while ((name= it++))
|
|
writer->add_str(name);
|
|
writer->end_array();
|
|
}
|
|
|
|
|
|
|
|
Explain_query::~Explain_query()
|
|
{
|
|
if (apc_enabled)
|
|
thd->apc_target.disable();
|
|
|
|
delete upd_del_plan;
|
|
delete insert_plan;
|
|
uint i;
|
|
for (i= 0 ; i < unions.elements(); i++)
|
|
delete unions.at(i);
|
|
for (i= 0 ; i < selects.elements(); i++)
|
|
delete selects.at(i);
|
|
}
|
|
|
|
|
|
Explain_node *Explain_query::get_node(uint select_id)
|
|
{
|
|
Explain_union *u;
|
|
if ((u= get_union(select_id)))
|
|
return u;
|
|
else
|
|
return get_select(select_id);
|
|
}
|
|
|
|
Explain_union *Explain_query::get_union(uint select_id)
|
|
{
|
|
return (unions.elements() > select_id) ? unions.at(select_id) : NULL;
|
|
}
|
|
|
|
Explain_select *Explain_query::get_select(uint select_id)
|
|
{
|
|
return (selects.elements() > select_id) ? selects.at(select_id) : NULL;
|
|
}
|
|
|
|
|
|
void Explain_query::add_node(Explain_node *node)
|
|
{
|
|
uint select_id;
|
|
operations++;
|
|
if (node->get_type() == Explain_node::EXPLAIN_UNION)
|
|
{
|
|
Explain_union *u= (Explain_union*)node;
|
|
select_id= u->get_select_id();
|
|
if (unions.elements() <= select_id)
|
|
unions.resize(MY_MAX(select_id+1, unions.elements()*2), NULL);
|
|
|
|
Explain_union *old_node;
|
|
if ((old_node= get_union(select_id)))
|
|
delete old_node;
|
|
|
|
unions.at(select_id)= u;
|
|
}
|
|
else
|
|
{
|
|
Explain_select *sel= (Explain_select*)node;
|
|
if (sel->select_id == FAKE_SELECT_LEX_ID)
|
|
{
|
|
DBUG_ASSERT(0); // this is a "fake select" from a UNION.
|
|
}
|
|
else
|
|
{
|
|
select_id= sel->select_id;
|
|
Explain_select *old_node;
|
|
|
|
if (selects.elements() <= select_id)
|
|
selects.resize(MY_MAX(select_id+1, selects.elements()*2), NULL);
|
|
|
|
if ((old_node= get_select(select_id)))
|
|
delete old_node;
|
|
|
|
selects.at(select_id)= sel;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg)
|
|
{
|
|
insert_plan= insert_plan_arg;
|
|
query_plan_ready();
|
|
}
|
|
|
|
|
|
void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg)
|
|
{
|
|
upd_del_plan= upd_del_plan_arg;
|
|
query_plan_ready();
|
|
}
|
|
|
|
|
|
void Explain_query::query_plan_ready()
|
|
{
|
|
if (!apc_enabled)
|
|
thd->apc_target.enable();
|
|
apc_enabled= true;
|
|
}
|
|
|
|
/*
|
|
Send EXPLAIN output to the client.
|
|
*/
|
|
|
|
int Explain_query::send_explain(THD *thd)
|
|
{
|
|
select_result *result;
|
|
LEX *lex= thd->lex;
|
|
|
|
if (!(result= new (thd->mem_root) select_send(thd)) ||
|
|
thd->send_explain_fields(result, lex->describe, lex->analyze_stmt))
|
|
return 1;
|
|
|
|
int res= 0;
|
|
if (thd->lex->explain_json)
|
|
print_explain_json(result, thd->lex->analyze_stmt);
|
|
else
|
|
res= print_explain(result, lex->describe, thd->lex->analyze_stmt);
|
|
|
|
if (res)
|
|
result->abort_result_set();
|
|
else
|
|
result->send_eof();
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
The main entry point to print EXPLAIN of the entire query
|
|
*/
|
|
|
|
int Explain_query::print_explain(select_result_sink *output,
|
|
uint8 explain_flags, bool is_analyze)
|
|
{
|
|
if (upd_del_plan)
|
|
{
|
|
upd_del_plan->print_explain(this, output, explain_flags, is_analyze);
|
|
return 0;
|
|
}
|
|
else if (insert_plan)
|
|
{
|
|
insert_plan->print_explain(this, output, explain_flags, is_analyze);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
/* Start printing from node with id=1 */
|
|
Explain_node *node= get_node(1);
|
|
if (!node)
|
|
return 1; /* No query plan */
|
|
return node->print_explain(this, output, explain_flags, is_analyze);
|
|
}
|
|
}
|
|
|
|
|
|
void Explain_query::print_explain_json(select_result_sink *output,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer writer;
|
|
writer.start_object();
|
|
|
|
if (upd_del_plan)
|
|
upd_del_plan->print_explain_json(this, &writer, is_analyze);
|
|
else if (insert_plan)
|
|
insert_plan->print_explain_json(this, &writer, is_analyze);
|
|
else
|
|
{
|
|
/* Start printing from node with id=1 */
|
|
Explain_node *node= get_node(1);
|
|
if (!node)
|
|
return; /* No query plan */
|
|
node->print_explain_json(this, &writer, is_analyze);
|
|
}
|
|
|
|
writer.end_object();
|
|
|
|
CHARSET_INFO *cs= system_charset_info;
|
|
List<Item> item_list;
|
|
const String *buf= writer.output.get_string();
|
|
item_list.push_back(new (thd->mem_root)
|
|
Item_string(thd, buf->ptr(), buf->length(), cs),
|
|
thd->mem_root);
|
|
output->send_data(item_list);
|
|
}
|
|
|
|
|
|
bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str)
|
|
{
|
|
return lex->explain->print_explain_str(thd, str, /*is_analyze*/ true);
|
|
}
|
|
|
|
|
|
/*
|
|
Return tabular EXPLAIN output as a text string
|
|
*/
|
|
|
|
bool Explain_query::print_explain_str(THD *thd, String *out_str,
|
|
bool is_analyze)
|
|
{
|
|
List<Item> fields;
|
|
thd->make_explain_field_list(fields, thd->lex->describe, is_analyze);
|
|
|
|
select_result_text_buffer output_buf(thd);
|
|
output_buf.send_result_set_metadata(fields, thd->lex->describe);
|
|
if (print_explain(&output_buf, thd->lex->describe, is_analyze))
|
|
return true;
|
|
output_buf.save_to(out_str);
|
|
return false;
|
|
}
|
|
|
|
|
|
static void push_str(THD *thd, List<Item> *item_list, const char *str)
|
|
{
|
|
item_list->push_back(new (thd->mem_root) Item_string_sys(thd, str),
|
|
thd->mem_root);
|
|
}
|
|
|
|
|
|
static void push_string(THD *thd, List<Item> *item_list, String *str)
|
|
{
|
|
item_list->push_back(new (thd->mem_root)
|
|
Item_string_sys(thd, str->ptr(), str->length()),
|
|
thd->mem_root);
|
|
}
|
|
|
|
static void push_string_list(THD *thd, List<Item> *item_list,
|
|
String_list &lines, String *buf)
|
|
{
|
|
List_iterator_fast<char> it(lines);
|
|
char *line;
|
|
bool first= true;
|
|
while ((line= it++))
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
buf->append(',');
|
|
|
|
buf->append(line);
|
|
}
|
|
push_string(thd, item_list, buf);
|
|
}
|
|
|
|
|
|
/*
|
|
Print an EXPLAIN output row, based on information provided in the parameters
|
|
|
|
@note
|
|
Parameters that may have NULL value in EXPLAIN output, should be passed
|
|
(char*)NULL.
|
|
|
|
@return
|
|
0 - OK
|
|
1 - OOM Error
|
|
*/
|
|
|
|
static
|
|
int print_explain_row(select_result_sink *result,
|
|
uint8 options, bool is_analyze,
|
|
uint select_number,
|
|
const char *select_type,
|
|
const char *table_name,
|
|
const char *partitions,
|
|
enum join_type jtype,
|
|
String_list *possible_keys,
|
|
const char *index,
|
|
const char *key_len,
|
|
const char *ref,
|
|
ha_rows *rows,
|
|
double *r_rows,
|
|
double r_filtered,
|
|
const char *extra)
|
|
{
|
|
THD *thd= result->thd;
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
Item *item_null= new (mem_root) Item_null(thd);
|
|
List<Item> item_list;
|
|
Item *item;
|
|
|
|
if (!select_type[0])
|
|
return 0;
|
|
|
|
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_number),
|
|
mem_root);
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
|
|
mem_root);
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, table_name),
|
|
mem_root);
|
|
if (options & DESCRIBE_PARTITIONS)
|
|
{
|
|
if (partitions)
|
|
{
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, partitions),
|
|
mem_root);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
|
|
const char *jtype_str= join_type_str[jtype];
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, jtype_str),
|
|
mem_root);
|
|
|
|
/* 'possible_keys'
|
|
The buffer must not be deallocated before we call send_data, otherwise
|
|
we may end up reading freed memory.
|
|
*/
|
|
StringBuffer<64> possible_keys_buf;
|
|
if (possible_keys && !possible_keys->is_empty())
|
|
{
|
|
push_string_list(thd, &item_list, *possible_keys, &possible_keys_buf);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* 'index */
|
|
item= index ? new (mem_root) Item_string_sys(thd, index) : item_null;
|
|
item_list.push_back(item, mem_root);
|
|
|
|
/* 'key_len */
|
|
item= key_len ? new (mem_root) Item_string_sys(thd, key_len) : item_null;
|
|
item_list.push_back(item, mem_root);
|
|
|
|
/* 'ref' */
|
|
item= ref ? new (mem_root) Item_string_sys(thd, ref) : item_null;
|
|
item_list.push_back(item, mem_root);
|
|
|
|
/* 'rows' */
|
|
StringBuffer<64> rows_str;
|
|
if (rows)
|
|
{
|
|
rows_str.append_ulonglong((ulonglong)(*rows));
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, rows_str.ptr(),
|
|
rows_str.length()), mem_root);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* 'r_rows' */
|
|
StringBuffer<64> r_rows_str;
|
|
if (is_analyze)
|
|
{
|
|
if (r_rows)
|
|
{
|
|
Item_float *fl= new (mem_root) Item_float(thd, *r_rows, 2);
|
|
String tmp;
|
|
String *res= fl->val_str(&tmp);
|
|
r_rows_str.append(res->ptr());
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, r_rows_str.ptr(),
|
|
r_rows_str.length()), mem_root);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
|
|
/* 'filtered' */
|
|
const double filtered=100.0;
|
|
if (options & DESCRIBE_EXTENDED || is_analyze)
|
|
item_list.push_back(new (mem_root) Item_float(thd, filtered, 2), mem_root);
|
|
|
|
/* 'r_filtered' */
|
|
if (is_analyze)
|
|
item_list.push_back(new (mem_root) Item_float(thd, r_filtered, 2),
|
|
mem_root);
|
|
|
|
/* 'Extra' */
|
|
if (extra)
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, extra), mem_root);
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
if (result->send_data(item_list))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
uint Explain_union::make_union_table_name(char *buf)
|
|
{
|
|
uint childno= 0;
|
|
uint len, lastop= 0;
|
|
LEX_CSTRING type;
|
|
switch (operation)
|
|
{
|
|
case OP_MIX:
|
|
lex_string_set3(&type, STRING_WITH_LEN("<unit"));
|
|
break;
|
|
case OP_UNION:
|
|
lex_string_set3(&type, STRING_WITH_LEN("<union"));
|
|
break;
|
|
case OP_INTERSECT:
|
|
lex_string_set3(&type, STRING_WITH_LEN("<intersect"));
|
|
break;
|
|
case OP_EXCEPT:
|
|
lex_string_set3(&type, STRING_WITH_LEN("<except"));
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
type.str= NULL;
|
|
type.length= 0;
|
|
}
|
|
memcpy(buf, type.str, (len= (uint)type.length));
|
|
|
|
for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
|
|
childno++)
|
|
{
|
|
len+= lastop;
|
|
lastop= (uint)my_snprintf(buf + len, NAME_LEN - len,
|
|
"%u,", union_members.at(childno));
|
|
}
|
|
|
|
if (childno < union_members.elements() || len + lastop >= NAME_LEN)
|
|
{
|
|
memcpy(buf + len, STRING_WITH_LEN("...>") + 1);
|
|
len+= 4;
|
|
}
|
|
else
|
|
{
|
|
len+= lastop;
|
|
buf[len - 1]= '>'; // change ',' to '>'
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
int Explain_union::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags,
|
|
bool is_analyze)
|
|
{
|
|
THD *thd= output->thd;
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
char table_name_buffer[SAFE_NAME_LEN];
|
|
|
|
/* print all UNION children, in order */
|
|
for (int i= 0; i < (int) union_members.elements(); i++)
|
|
{
|
|
Explain_select *sel= query->get_select(union_members.at(i));
|
|
sel->print_explain(query, output, explain_flags, is_analyze);
|
|
}
|
|
|
|
if (!using_tmp)
|
|
return 0;
|
|
|
|
/* Print a line with "UNIT RESULT" */
|
|
List<Item> item_list;
|
|
Item *item_null= new (mem_root) Item_null(thd);
|
|
|
|
/* `id` column */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `select_type` column */
|
|
push_str(thd, &item_list, fake_select_type);
|
|
|
|
/* `table` column: something like "<union1,2>" */
|
|
uint len= make_union_table_name(table_name_buffer);
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, table_name_buffer, len),
|
|
mem_root);
|
|
|
|
/* `partitions` column */
|
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `type` column */
|
|
push_str(thd, &item_list, join_type_str[JT_ALL]);
|
|
|
|
/* `possible_keys` column */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `key` */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `key_len` */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `ref` */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `rows` */
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `r_rows` */
|
|
StringBuffer<64> r_rows_str;
|
|
if (is_analyze)
|
|
{
|
|
double avg_rows= fake_select_lex_tracker.get_avg_rows();
|
|
Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
|
|
String tmp;
|
|
String *res= fl->val_str(&tmp);
|
|
r_rows_str.append(res->ptr());
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, r_rows_str.ptr(),
|
|
r_rows_str.length()), mem_root);
|
|
}
|
|
|
|
/* `filtered` */
|
|
if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `r_filtered` */
|
|
if (is_analyze)
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `Extra` */
|
|
StringBuffer<256> extra_buf;
|
|
if (using_filesort)
|
|
{
|
|
extra_buf.append(STRING_WITH_LEN("Using filesort"));
|
|
}
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, extra_buf.ptr(),
|
|
extra_buf.length()),
|
|
mem_root);
|
|
|
|
//output->unit.offset_limit_cnt= 0;
|
|
if (output->send_data(item_list))
|
|
return 1;
|
|
|
|
/*
|
|
Print all subquery children (UNION children have already been printed at
|
|
the start of this function)
|
|
*/
|
|
return print_explain_for_children(query, output, explain_flags, is_analyze);
|
|
}
|
|
|
|
|
|
void Explain_union::print_explain_json(Explain_query *query,
|
|
Json_writer *writer, bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
char table_name_buffer[SAFE_NAME_LEN];
|
|
|
|
bool started_object= print_explain_json_cache(writer, is_analyze);
|
|
|
|
writer->add_member("query_block").start_object();
|
|
|
|
if (is_recursive_cte)
|
|
writer->add_member("recursive_union").start_object();
|
|
else
|
|
writer->add_member("union_result").start_object();
|
|
|
|
if (using_tmp)
|
|
{
|
|
make_union_table_name(table_name_buffer);
|
|
writer->add_member("table_name").add_str(table_name_buffer);
|
|
writer->add_member("access_type").add_str("ALL"); // not very useful
|
|
|
|
/* r_loops (not present in tabular output) */
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_loops").add_ll(
|
|
fake_select_lex_tracker.get_loops());
|
|
}
|
|
|
|
/* `r_rows` */
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_rows");
|
|
if (fake_select_lex_tracker.has_scans())
|
|
writer->add_double(fake_select_lex_tracker.get_avg_rows());
|
|
else
|
|
writer->add_null();
|
|
}
|
|
}
|
|
writer->add_member("query_specifications").start_array();
|
|
|
|
for (int i= 0; i < (int) union_members.elements(); i++)
|
|
{
|
|
writer->start_object();
|
|
//writer->add_member("dependent").add_str("TODO");
|
|
//writer->add_member("cacheable").add_str("TODO");
|
|
Explain_select *sel= query->get_select(union_members.at(i));
|
|
sel->print_explain_json(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
writer->end_array();
|
|
|
|
print_explain_json_for_children(query, writer, is_analyze);
|
|
|
|
writer->end_object(); // union_result
|
|
writer->end_object(); // query_block
|
|
|
|
if (started_object)
|
|
writer->end_object();
|
|
}
|
|
|
|
|
|
/*
|
|
Print EXPLAINs for all children nodes (i.e. for subqueries)
|
|
*/
|
|
|
|
int Explain_node::print_explain_for_children(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags,
|
|
bool is_analyze)
|
|
{
|
|
for (int i= 0; i < (int) children.elements(); i++)
|
|
{
|
|
Explain_node *node= query->get_node(children.at(i));
|
|
/*
|
|
Note: node may not be present because for certain kinds of subqueries,
|
|
the optimizer is not able to see that they were eliminated.
|
|
*/
|
|
if (node && node->print_explain(query, output, explain_flags, is_analyze))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Explain_basic_join::add_table(Explain_table_access *tab, Explain_query *query)
|
|
{
|
|
if (!join_tabs)
|
|
{
|
|
n_join_tabs= 0;
|
|
if (!(join_tabs= ((Explain_table_access**)
|
|
alloc_root(query->mem_root,
|
|
sizeof(Explain_table_access*) *
|
|
MAX_TABLES))))
|
|
return true;
|
|
}
|
|
join_tabs[n_join_tabs++]= tab;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
This tells whether a child subquery should be printed in JSON output.
|
|
|
|
Derived tables and Non-merged semi-joins should not be printed, because they
|
|
are printed inline in Explain_table_access.
|
|
*/
|
|
bool is_connection_printable_in_json(enum Explain_node::explain_connection_type type)
|
|
{
|
|
return (type != Explain_node::EXPLAIN_NODE_DERIVED &&
|
|
type != Explain_node::EXPLAIN_NODE_NON_MERGED_SJ);
|
|
}
|
|
|
|
|
|
void Explain_node::print_explain_json_for_children(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
bool started= false;
|
|
for (int i= 0; i < (int) children.elements(); i++)
|
|
{
|
|
Explain_node *node= query->get_node(children.at(i));
|
|
|
|
/*
|
|
Note: node may not be present because for certain kinds of subqueries,
|
|
the optimizer is not able to see that they were eliminated.
|
|
*/
|
|
if (!node)
|
|
continue;
|
|
|
|
/* Derived tables are printed inside Explain_table_access objects */
|
|
if (!is_connection_printable_in_json(node->connection_type))
|
|
continue;
|
|
|
|
if (!started)
|
|
{
|
|
writer->add_member("subqueries").start_array();
|
|
started= true;
|
|
}
|
|
|
|
writer->start_object();
|
|
node->print_explain_json(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
|
|
if (started)
|
|
writer->end_array();
|
|
}
|
|
|
|
|
|
bool Explain_node::print_explain_json_cache(Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
if (cache_tracker)
|
|
{
|
|
cache_tracker->fetch_current_stats();
|
|
writer->add_member("expression_cache").start_object();
|
|
if (cache_tracker->state != Expression_cache_tracker::OK)
|
|
{
|
|
writer->add_member("state").
|
|
add_str(Expression_cache_tracker::state_str[cache_tracker->state]);
|
|
}
|
|
|
|
if (is_analyze)
|
|
{
|
|
longlong cache_reads= cache_tracker->hit + cache_tracker->miss;
|
|
writer->add_member("r_loops").add_ll(cache_reads);
|
|
if (cache_reads != 0)
|
|
{
|
|
double hit_ratio= double(cache_tracker->hit) / cache_reads * 100.0;
|
|
writer->add_member("r_hit_ratio").add_double(hit_ratio);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
Explain_basic_join::~Explain_basic_join()
|
|
{
|
|
if (join_tabs)
|
|
{
|
|
for (uint i= 0; i< n_join_tabs; i++)
|
|
delete join_tabs[i];
|
|
}
|
|
}
|
|
|
|
|
|
int Explain_select::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags, bool is_analyze)
|
|
{
|
|
THD *thd= output->thd;
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
|
|
if (select_type == pushed_derived_text || select_type == pushed_select_text)
|
|
{
|
|
print_explain_message_line(output, explain_flags, is_analyze,
|
|
select_id /*select number*/,
|
|
select_type,
|
|
NULL, /* rows */
|
|
NULL);
|
|
}
|
|
else if (message)
|
|
{
|
|
List<Item> item_list;
|
|
Item *item_null= new (mem_root) Item_null(thd);
|
|
|
|
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
|
|
mem_root);
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
|
|
mem_root);
|
|
for (uint i=0 ; i < 7; i++)
|
|
item_list.push_back(item_null, mem_root);
|
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* filtered */
|
|
if (is_analyze || explain_flags & DESCRIBE_EXTENDED)
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
if (is_analyze)
|
|
{
|
|
/* r_rows, r_filtered */
|
|
item_list.push_back(item_null, mem_root);
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
|
|
item_list.push_back(new (mem_root) Item_string_sys(thd, message),
|
|
mem_root);
|
|
|
|
if (output->send_data(item_list))
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
bool using_tmp= false;
|
|
bool using_fs= false;
|
|
|
|
for (Explain_aggr_node *node= aggr_tree; node; node= node->child)
|
|
{
|
|
switch (node->get_type())
|
|
{
|
|
case AGGR_OP_TEMP_TABLE:
|
|
using_tmp= true;
|
|
break;
|
|
case AGGR_OP_FILESORT:
|
|
using_fs= true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uint i=0; i< n_join_tabs; i++)
|
|
{
|
|
join_tabs[i]->print_explain(output, explain_flags, is_analyze, select_id,
|
|
select_type, using_tmp, using_fs);
|
|
if (i == 0)
|
|
{
|
|
/*
|
|
"Using temporary; Using filesort" should only be shown near the 1st
|
|
table
|
|
*/
|
|
using_tmp= false;
|
|
using_fs= false;
|
|
}
|
|
}
|
|
for (uint i=0; i< n_join_tabs; i++)
|
|
{
|
|
Explain_basic_join* nest;
|
|
if ((nest= join_tabs[i]->sjm_nest))
|
|
nest->print_explain(query, output, explain_flags, is_analyze);
|
|
}
|
|
}
|
|
|
|
return print_explain_for_children(query, output, explain_flags, is_analyze);
|
|
}
|
|
|
|
|
|
int Explain_basic_join::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags, bool is_analyze)
|
|
{
|
|
for (uint i=0; i< n_join_tabs; i++)
|
|
{
|
|
if (join_tabs[i]->print_explain(output, explain_flags, is_analyze,
|
|
select_id,
|
|
"MATERIALIZED" /*select_type*/,
|
|
FALSE /*using temporary*/,
|
|
FALSE /*using filesort*/))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Explain_select::add_linkage(Json_writer *writer)
|
|
{
|
|
const char *operation= NULL;
|
|
switch (linkage)
|
|
{
|
|
case UNION_TYPE:
|
|
operation= "UNION";
|
|
break;
|
|
case INTERSECT_TYPE:
|
|
operation= "INTERSECT";
|
|
break;
|
|
case EXCEPT_TYPE:
|
|
operation= "EXCEPT";
|
|
break;
|
|
default:
|
|
// It is the first or the only SELECT => no operation
|
|
break;
|
|
}
|
|
if (operation)
|
|
writer->add_member("operation").add_str(operation);
|
|
}
|
|
|
|
void Explain_select::print_explain_json(Explain_query *query,
|
|
Json_writer *writer, bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
bool started_cache= print_explain_json_cache(writer, is_analyze);
|
|
|
|
if (message ||
|
|
select_type == pushed_derived_text ||
|
|
select_type == pushed_select_text)
|
|
{
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(select_id);
|
|
add_linkage(writer);
|
|
|
|
writer->add_member("table").start_object();
|
|
writer->add_member("message").add_str(select_type == pushed_derived_text ?
|
|
"Pushed derived" :
|
|
select_type == pushed_select_text ?
|
|
"Pushed select" :
|
|
message);
|
|
writer->end_object();
|
|
|
|
print_explain_json_for_children(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
else
|
|
{
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(select_id);
|
|
add_linkage(writer);
|
|
|
|
if (is_analyze && time_tracker.get_loops())
|
|
{
|
|
writer->add_member("r_loops").add_ll(time_tracker.get_loops());
|
|
writer->add_member("r_total_time_ms").add_double(time_tracker.get_time_ms());
|
|
}
|
|
|
|
if (exec_const_cond)
|
|
{
|
|
writer->add_member("const_condition");
|
|
write_item(writer, exec_const_cond);
|
|
}
|
|
if (outer_ref_cond)
|
|
{
|
|
writer->add_member("outer_ref_condition");
|
|
write_item(writer, outer_ref_cond);
|
|
}
|
|
if (pseudo_bits_cond)
|
|
{
|
|
writer->add_member("pseudo_bits_condition");
|
|
write_item(writer, pseudo_bits_cond);
|
|
}
|
|
|
|
/* we do not print HAVING which always evaluates to TRUE */
|
|
if (having || (having_value == Item::COND_FALSE))
|
|
{
|
|
writer->add_member("having_condition");
|
|
if (likely(having))
|
|
write_item(writer, having);
|
|
else
|
|
{
|
|
/* Normally we should not go this branch, left just for safety */
|
|
DBUG_ASSERT(having_value == Item::COND_FALSE);
|
|
writer->add_str("0");
|
|
}
|
|
}
|
|
|
|
int started_objects= 0;
|
|
|
|
Explain_aggr_node *node= aggr_tree;
|
|
|
|
for (; node; node= node->child)
|
|
{
|
|
switch (node->get_type())
|
|
{
|
|
case AGGR_OP_TEMP_TABLE:
|
|
writer->add_member("temporary_table").start_object();
|
|
break;
|
|
case AGGR_OP_FILESORT:
|
|
{
|
|
writer->add_member("filesort").start_object();
|
|
((Explain_aggr_filesort*)node)->print_json_members(writer, is_analyze);
|
|
break;
|
|
}
|
|
case AGGR_OP_REMOVE_DUPLICATES:
|
|
writer->add_member("duplicate_removal").start_object();
|
|
break;
|
|
case AGGR_OP_WINDOW_FUNCS:
|
|
{
|
|
//TODO: make print_json_members virtual?
|
|
writer->add_member("window_functions_computation").start_object();
|
|
((Explain_aggr_window_funcs*)node)->print_json_members(writer, is_analyze);
|
|
break;
|
|
}
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
started_objects++;
|
|
}
|
|
|
|
Explain_basic_join::print_explain_json_interns(query, writer, is_analyze);
|
|
|
|
for (;started_objects; started_objects--)
|
|
writer->end_object();
|
|
|
|
writer->end_object();
|
|
}
|
|
|
|
if (started_cache)
|
|
writer->end_object();
|
|
}
|
|
|
|
|
|
Explain_aggr_filesort::Explain_aggr_filesort(MEM_ROOT *mem_root,
|
|
bool is_analyze,
|
|
Filesort *filesort)
|
|
: tracker(is_analyze)
|
|
{
|
|
child= NULL;
|
|
for (ORDER *ord= filesort->order; ord; ord= ord->next)
|
|
{
|
|
sort_items.push_back(ord->item[0], mem_root);
|
|
sort_directions.push_back(&ord->direction, mem_root);
|
|
}
|
|
filesort->tracker= &tracker;
|
|
}
|
|
|
|
|
|
void Explain_aggr_filesort::print_json_members(Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
char item_buf[256];
|
|
String str(item_buf, sizeof(item_buf), &my_charset_bin);
|
|
str.length(0);
|
|
|
|
List_iterator_fast<Item> it(sort_items);
|
|
List_iterator_fast<ORDER::enum_order> it_dir(sort_directions);
|
|
Item* item;
|
|
ORDER::enum_order *direction;
|
|
bool first= true;
|
|
while ((item= it++))
|
|
{
|
|
direction= it_dir++;
|
|
if (first)
|
|
first= false;
|
|
else
|
|
{
|
|
str.append(", ");
|
|
}
|
|
append_item_to_str(&str, item);
|
|
if (*direction == ORDER::ORDER_DESC)
|
|
str.append(" desc");
|
|
}
|
|
|
|
writer->add_member("sort_key").add_str(str.c_ptr_safe());
|
|
|
|
if (is_analyze)
|
|
tracker.print_json_members(writer);
|
|
}
|
|
|
|
|
|
void Explain_aggr_window_funcs::print_json_members(Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Explain_aggr_filesort *srt;
|
|
List_iterator<Explain_aggr_filesort> it(sorts);
|
|
writer->add_member("sorts").start_object();
|
|
while ((srt= it++))
|
|
{
|
|
writer->add_member("filesort").start_object();
|
|
srt->print_json_members(writer, is_analyze);
|
|
writer->end_object(); // filesort
|
|
}
|
|
writer->end_object(); // sorts
|
|
}
|
|
|
|
|
|
void Explain_basic_join::print_explain_json(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(select_id);
|
|
|
|
print_explain_json_interns(query, writer, is_analyze);
|
|
|
|
writer->end_object();
|
|
}
|
|
|
|
|
|
void Explain_basic_join::
|
|
print_explain_json_interns(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
for (uint i=0; i< n_join_tabs; i++)
|
|
{
|
|
if (join_tabs[i]->start_dups_weedout)
|
|
writer->add_member("duplicates_removal").start_object();
|
|
|
|
join_tabs[i]->print_explain_json(query, writer, is_analyze);
|
|
|
|
if (join_tabs[i]->end_dups_weedout)
|
|
writer->end_object();
|
|
}
|
|
print_explain_json_for_children(query, writer, is_analyze);
|
|
}
|
|
|
|
|
|
void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
|
|
{
|
|
extra_tags.append(extra_tag);
|
|
}
|
|
|
|
|
|
/*
|
|
Put the contents of 'key' field of EXPLAIN otuput into key_str.
|
|
|
|
It is surprisingly complex:
|
|
- hash join shows #hash#used_key
|
|
- quick selects that use single index will print index name
|
|
*/
|
|
|
|
void Explain_table_access::fill_key_str(String *key_str, bool is_json) const
|
|
{
|
|
CHARSET_INFO *cs= system_charset_info;
|
|
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
|
|
type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
|
|
const char *hash_key_prefix= "#hash#";
|
|
|
|
if (key.get_key_name())
|
|
{
|
|
if (is_hj)
|
|
key_str->append(hash_key_prefix, strlen(hash_key_prefix), cs);
|
|
|
|
key_str->append(key.get_key_name());
|
|
|
|
if (is_hj && type != JT_HASH)
|
|
key_str->append(':');
|
|
}
|
|
|
|
if (quick_info)
|
|
{
|
|
StringBuffer<64> buf2;
|
|
if (is_json)
|
|
quick_info->print_extra_recursive(&buf2);
|
|
else
|
|
quick_info->print_key(&buf2);
|
|
key_str->append(buf2);
|
|
}
|
|
if (type == JT_HASH_NEXT)
|
|
key_str->append(hash_next_key.get_key_name());
|
|
}
|
|
|
|
|
|
/*
|
|
Fill "key_length".
|
|
- this is just used key length for ref/range
|
|
- for index_merge, it is a comma-separated list of lengths.
|
|
- for hash join, it is key_len:pseudo_key_len
|
|
- [tabular form only] rowid filter length is added after "|".
|
|
|
|
In JSON, we consider this column to be legacy, it is superceded by
|
|
used_key_parts.
|
|
*/
|
|
|
|
void Explain_table_access::fill_key_len_str(String *key_len_str,
|
|
bool is_json) const
|
|
{
|
|
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
|
|
type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
|
|
if (key.get_key_len() != (uint)-1)
|
|
{
|
|
char buf[64];
|
|
size_t length;
|
|
length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
|
|
key_len_str->append(buf, length);
|
|
if (is_hj && type != JT_HASH)
|
|
key_len_str->append(':');
|
|
}
|
|
|
|
if (quick_info)
|
|
{
|
|
StringBuffer<64> buf2;
|
|
quick_info->print_key_len(&buf2);
|
|
key_len_str->append(buf2);
|
|
}
|
|
|
|
if (type == JT_HASH_NEXT)
|
|
{
|
|
char buf[64];
|
|
size_t length;
|
|
length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf;
|
|
key_len_str->append(buf, length);
|
|
}
|
|
|
|
if (!is_json && rowid_filter)
|
|
{
|
|
key_len_str->append('|');
|
|
StringBuffer<64> filter_key_len;
|
|
rowid_filter->quick->print_key_len(&filter_key_len);
|
|
key_len_str->append(filter_key_len);
|
|
}
|
|
}
|
|
|
|
|
|
bool Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg)
|
|
{
|
|
if (set_pseudo_key(mem_root, key->name.str))
|
|
return 1;
|
|
|
|
key_len= key_len_arg;
|
|
uint len= 0;
|
|
for (uint i= 0; i < key->usable_key_parts; i++)
|
|
{
|
|
if (!key_parts_list.append_str(mem_root,
|
|
key->key_part[i].field->field_name.str))
|
|
return 1;
|
|
len += key->key_part[i].store_length;
|
|
if (len >= key_len_arg)
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
bool Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg)
|
|
{
|
|
if (key_name_arg)
|
|
{
|
|
if (!(key_name= strdup_root(root, key_name_arg)))
|
|
return 1;
|
|
}
|
|
else
|
|
key_name= NULL;
|
|
key_len= ~(uint) 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Given r_filtered% from join buffer condition and join condition, produce a
|
|
combined r_filtered% number. This is needed for tabular EXPLAIN output which
|
|
has only one cell for r_filtered value.
|
|
*/
|
|
|
|
double Explain_table_access::get_r_filtered()
|
|
{
|
|
double r_filtered= tracker.get_filtered_after_where();
|
|
if (bka_type.is_using_jbuf())
|
|
r_filtered *= jbuf_tracker.get_filtered_after_where();
|
|
return r_filtered;
|
|
}
|
|
|
|
|
|
int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags,
|
|
bool is_analyze,
|
|
uint select_id, const char *select_type,
|
|
bool using_temporary, bool using_filesort)
|
|
{
|
|
THD *thd= output->thd;
|
|
MEM_ROOT *mem_root= thd->mem_root;
|
|
|
|
List<Item> item_list;
|
|
Item *item_null= new (mem_root) Item_null(thd);
|
|
|
|
/* `id` column */
|
|
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
|
|
mem_root);
|
|
|
|
/* `select_type` column */
|
|
push_str(thd, &item_list, select_type);
|
|
|
|
/* `table` column */
|
|
push_string(thd, &item_list, &table_name);
|
|
|
|
/* `partitions` column */
|
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
|
{
|
|
if (used_partitions_set)
|
|
{
|
|
push_string(thd, &item_list, &used_partitions);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
|
|
/* `type` column */
|
|
StringBuffer<64> join_type_buf;
|
|
if (rowid_filter == NULL)
|
|
push_str(thd, &item_list, join_type_str[type]);
|
|
else
|
|
{
|
|
join_type_buf.append(join_type_str[type]);
|
|
join_type_buf.append("|filter");
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, join_type_buf.ptr(),
|
|
join_type_buf.length()),
|
|
mem_root);
|
|
}
|
|
|
|
/* `possible_keys` column */
|
|
StringBuffer<64> possible_keys_buf;
|
|
if (possible_keys.is_empty())
|
|
item_list.push_back(item_null, mem_root);
|
|
else
|
|
push_string_list(thd, &item_list, possible_keys, &possible_keys_buf);
|
|
|
|
/* `key` */
|
|
StringBuffer<64> key_str;
|
|
fill_key_str(&key_str, false);
|
|
|
|
if (rowid_filter)
|
|
{
|
|
key_str.append("|");
|
|
StringBuffer<64> rowid_key_str;
|
|
rowid_filter->quick->print_key(&rowid_key_str);
|
|
key_str.append(rowid_key_str);
|
|
}
|
|
|
|
if (key_str.length() > 0)
|
|
push_string(thd, &item_list, &key_str);
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `key_len` */
|
|
StringBuffer<64> key_len_str;
|
|
fill_key_len_str(&key_len_str, false);
|
|
|
|
if (key_len_str.length() > 0)
|
|
push_string(thd, &item_list, &key_len_str);
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `ref` */
|
|
StringBuffer<64> ref_list_buf;
|
|
if (ref_list.is_empty())
|
|
{
|
|
if (type == JT_FT)
|
|
{
|
|
/* Traditionally, EXPLAIN lines with type=fulltext have ref='' */
|
|
push_str(thd, &item_list, "");
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
else
|
|
push_string_list(thd, &item_list, ref_list, &ref_list_buf);
|
|
|
|
/* `rows` */
|
|
StringBuffer<64> rows_str;
|
|
if (rows_set)
|
|
{
|
|
rows_str.append_ulonglong((ulonglong)rows);
|
|
|
|
if (rowid_filter)
|
|
{
|
|
rows_str.append(" (");
|
|
rows_str.append_ulonglong((ulonglong) (round(rowid_filter->selectivity *
|
|
100.0)));
|
|
rows_str.append("%)");
|
|
}
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, rows_str.ptr(),
|
|
rows_str.length()), mem_root);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
|
|
/* `r_rows` */
|
|
StringBuffer<64> r_rows_str;
|
|
if (is_analyze)
|
|
{
|
|
if (!tracker.has_scans())
|
|
{
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
else
|
|
{
|
|
double avg_rows= tracker.get_avg_rows();
|
|
Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
|
|
String tmp;
|
|
String *res= fl->val_str(&tmp);
|
|
r_rows_str.append(res->ptr());
|
|
if (rowid_filter)
|
|
{
|
|
r_rows_str.append(" (");
|
|
r_rows_str.append_ulonglong(
|
|
(ulonglong) (rowid_filter->tracker->get_r_selectivity_pct() * 100.0));
|
|
r_rows_str.append("%)");
|
|
}
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, r_rows_str.ptr(),
|
|
r_rows_str.length()), mem_root);
|
|
}
|
|
}
|
|
|
|
/* `filtered` */
|
|
if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
|
|
{
|
|
if (filtered_set)
|
|
{
|
|
item_list.push_back(new (mem_root) Item_float(thd, filtered, 2),
|
|
mem_root);
|
|
}
|
|
else
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
|
|
/* `r_filtered` */
|
|
if (is_analyze)
|
|
{
|
|
if (!tracker.has_scans())
|
|
{
|
|
item_list.push_back(item_null, mem_root);
|
|
}
|
|
else
|
|
{
|
|
double r_filtered= tracker.get_filtered_after_where();
|
|
if (bka_type.is_using_jbuf())
|
|
r_filtered *= jbuf_tracker.get_filtered_after_where();
|
|
item_list.push_back(new (mem_root)
|
|
Item_float(thd, r_filtered * 100.0, 2),
|
|
mem_root);
|
|
}
|
|
}
|
|
|
|
/* `Extra` */
|
|
StringBuffer<256> extra_buf;
|
|
bool first= true;
|
|
for (int i=0; i < (int)extra_tags.elements(); i++)
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
extra_buf.append(STRING_WITH_LEN("; "));
|
|
append_tag_name(&extra_buf, extra_tags.at(i));
|
|
}
|
|
|
|
if (using_temporary)
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
extra_buf.append(STRING_WITH_LEN("; "));
|
|
extra_buf.append(STRING_WITH_LEN("Using temporary"));
|
|
}
|
|
|
|
if (using_filesort || this->pre_join_sort)
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
extra_buf.append(STRING_WITH_LEN("; "));
|
|
extra_buf.append(STRING_WITH_LEN("Using filesort"));
|
|
}
|
|
|
|
if (rowid_filter)
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
extra_buf.append(STRING_WITH_LEN("; "));
|
|
extra_buf.append(STRING_WITH_LEN("Using rowid filter"));
|
|
}
|
|
|
|
item_list.push_back(new (mem_root)
|
|
Item_string_sys(thd, extra_buf.ptr(),
|
|
extra_buf.length()),
|
|
mem_root);
|
|
|
|
if (output->send_data(item_list))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Adds copy of the string to the list
|
|
|
|
@param mem_root where to allocate string
|
|
@param str string to copy and add
|
|
|
|
@return
|
|
NULL - out of memory error
|
|
poiner on allocated copy of the string
|
|
*/
|
|
|
|
const char *String_list::append_str(MEM_ROOT *mem_root, const char *str)
|
|
{
|
|
size_t len= strlen(str);
|
|
char *cp;
|
|
if (!(cp = (char*)alloc_root(mem_root, len+1)))
|
|
return NULL;
|
|
memcpy(cp, str, len+1);
|
|
push_back(cp, mem_root);
|
|
return cp;
|
|
}
|
|
|
|
|
|
static void write_item(Json_writer *writer, Item *item)
|
|
{
|
|
THD *thd= current_thd;
|
|
char item_buf[256];
|
|
String str(item_buf, sizeof(item_buf), &my_charset_bin);
|
|
str.length(0);
|
|
|
|
ulonglong save_option_bits= thd->variables.option_bits;
|
|
thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
|
|
|
|
item->print(&str, QT_EXPLAIN);
|
|
|
|
thd->variables.option_bits= save_option_bits;
|
|
writer->add_str(str.c_ptr_safe());
|
|
}
|
|
|
|
static void append_item_to_str(String *out, Item *item)
|
|
{
|
|
THD *thd= current_thd;
|
|
ulonglong save_option_bits= thd->variables.option_bits;
|
|
thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
|
|
|
|
item->print(out, QT_EXPLAIN);
|
|
thd->variables.option_bits= save_option_bits;
|
|
}
|
|
|
|
void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_tag tag)
|
|
{
|
|
switch (tag)
|
|
{
|
|
case ET_OPEN_FULL_TABLE:
|
|
writer->add_member("open_full_table").add_bool(true);
|
|
break;
|
|
case ET_SCANNED_0_DATABASES:
|
|
writer->add_member("scanned_databases").add_ll(0);
|
|
break;
|
|
case ET_SCANNED_1_DATABASE:
|
|
writer->add_member("scanned_databases").add_ll(1);
|
|
break;
|
|
case ET_SCANNED_ALL_DATABASES:
|
|
writer->add_member("scanned_databases").add_str("all");
|
|
break;
|
|
case ET_SKIP_OPEN_TABLE:
|
|
writer->add_member("skip_open_table").add_bool(true);
|
|
break;
|
|
case ET_OPEN_FRM_ONLY:
|
|
writer->add_member("open_frm_only").add_bool(true);
|
|
break;
|
|
case ET_USING_INDEX_CONDITION:
|
|
writer->add_member("index_condition");
|
|
write_item(writer, pushed_index_cond);
|
|
break;
|
|
case ET_USING_INDEX_CONDITION_BKA:
|
|
writer->add_member("index_condition_bka");
|
|
write_item(writer, pushed_index_cond);
|
|
break;
|
|
case ET_USING_WHERE:
|
|
{
|
|
/*
|
|
We are printing the condition that is checked when scanning this
|
|
table.
|
|
- when join buffer is used, it is cache_cond.
|
|
- in other cases, it is where_cond.
|
|
*/
|
|
Item *item= bka_type.is_using_jbuf()? cache_cond: where_cond;
|
|
if (item)
|
|
{
|
|
writer->add_member("attached_condition");
|
|
write_item(writer, item);
|
|
}
|
|
}
|
|
break;
|
|
case ET_USING_INDEX:
|
|
writer->add_member("using_index").add_bool(true);
|
|
break;
|
|
case ET_USING:
|
|
// index merge: case ET_USING
|
|
break;
|
|
case ET_RANGE_CHECKED_FOR_EACH_RECORD:
|
|
/* Handled as range_checked_fer */
|
|
case ET_USING_JOIN_BUFFER:
|
|
/* Do nothing. Join buffer is handled differently */
|
|
case ET_START_TEMPORARY:
|
|
case ET_END_TEMPORARY:
|
|
/* Handled as "duplicates_removal: { ... } */
|
|
case ET_FULL_SCAN_ON_NULL_KEY:
|
|
/* Handled in full_scan_on_null_key */
|
|
break;
|
|
case ET_FIRST_MATCH:
|
|
writer->add_member("first_match").add_str(firstmatch_table_name.c_ptr());
|
|
break;
|
|
case ET_LOOSESCAN:
|
|
writer->add_member("loose_scan").add_bool(true);
|
|
break;
|
|
case ET_USING_MRR:
|
|
writer->add_member("mrr_type").add_str(mrr_type.c_ptr());
|
|
break;
|
|
case ET_USING_INDEX_FOR_GROUP_BY:
|
|
writer->add_member("using_index_for_group_by");
|
|
if (loose_scan_is_scanning)
|
|
writer->add_str("scanning");
|
|
else
|
|
writer->add_bool(true);
|
|
break;
|
|
|
|
/*new:*/
|
|
case ET_CONST_ROW_NOT_FOUND:
|
|
writer->add_member("const_row_not_found").add_bool(true);
|
|
break;
|
|
case ET_UNIQUE_ROW_NOT_FOUND:
|
|
/*
|
|
Currently, we never get here. All SELECTs that have
|
|
ET_UNIQUE_ROW_NOT_FOUND for a table are converted into degenerate
|
|
SELECTs with message="Impossible WHERE ...".
|
|
MySQL 5.6 has the same property.
|
|
I'm leaving the handling in just for the sake of covering all enum
|
|
members and safety.
|
|
*/
|
|
writer->add_member("unique_row_not_found").add_bool(true);
|
|
break;
|
|
case ET_IMPOSSIBLE_ON_CONDITION:
|
|
writer->add_member("impossible_on_condition").add_bool(true);
|
|
break;
|
|
case ET_USING_WHERE_WITH_PUSHED_CONDITION:
|
|
/*
|
|
It would be nice to print the pushed condition, but current Storage
|
|
Engine API doesn't provide any way to do that
|
|
*/
|
|
writer->add_member("pushed_condition").add_bool(true);
|
|
break;
|
|
|
|
case ET_NOT_EXISTS:
|
|
writer->add_member("not_exists").add_bool(true);
|
|
break;
|
|
case ET_DISTINCT:
|
|
writer->add_member("distinct").add_bool(true);
|
|
break;
|
|
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
static
|
|
void add_json_keyset(Json_writer *writer, const char *elem_name,
|
|
String_list *keyset)
|
|
{
|
|
if (!keyset->is_empty())
|
|
print_json_array(writer, elem_name, *keyset);
|
|
}
|
|
|
|
|
|
void Explain_rowid_filter::print_explain_json(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
writer->add_member("rowid_filter").start_object();
|
|
quick->print_json(writer);
|
|
writer->add_member("rows").add_ll(rows);
|
|
writer->add_member("selectivity_pct").add_double(selectivity * 100.0);
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_rows").add_double(tracker->get_container_elements());
|
|
writer->add_member("r_lookups").add_ll(tracker->get_container_lookups());
|
|
writer->add_member("r_selectivity_pct").
|
|
add_double(tracker->get_r_selectivity_pct() * 100.0);
|
|
writer->add_member("r_buffer_size").
|
|
add_double((double) (tracker->get_container_buff_size()));
|
|
writer->add_member("r_filling_time_ms").
|
|
add_double(tracker->get_time_fill_container_ms());
|
|
}
|
|
writer->end_object(); // rowid_filter
|
|
}
|
|
|
|
|
|
void Explain_table_access::print_explain_json(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
if (pre_join_sort)
|
|
{
|
|
/* filesort was invoked on this join tab before doing the join with the rest */
|
|
writer->add_member("read_sorted_file").start_object();
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_rows");
|
|
/*
|
|
r_rows when reading filesort result. This can be less than the number
|
|
of rows produced by filesort due to NL-join having LIMIT.
|
|
*/
|
|
if (tracker.has_scans())
|
|
writer->add_double(tracker.get_avg_rows());
|
|
else
|
|
writer->add_null();
|
|
|
|
/*
|
|
r_filtered when reading filesort result. We should have checked the
|
|
WHERE while doing filesort but lets check just in case.
|
|
*/
|
|
if (tracker.has_scans() && tracker.get_filtered_after_where() < 1.0)
|
|
{
|
|
writer->add_member("r_filtered");
|
|
writer->add_double(tracker.get_filtered_after_where()*100.0);
|
|
}
|
|
}
|
|
writer->add_member("filesort").start_object();
|
|
pre_join_sort->print_json_members(writer, is_analyze);
|
|
}
|
|
|
|
if (bka_type.is_using_jbuf())
|
|
{
|
|
writer->add_member("block-nl-join").start_object();
|
|
}
|
|
|
|
if (range_checked_fer)
|
|
{
|
|
range_checked_fer->print_json(writer, is_analyze);
|
|
}
|
|
|
|
if (full_scan_on_null_key)
|
|
writer->add_member("full-scan-on-null_key").start_object();
|
|
|
|
writer->add_member("table").start_object();
|
|
|
|
writer->add_member("table_name").add_str(table_name);
|
|
|
|
if (used_partitions_set)
|
|
print_json_array(writer, "partitions", used_partitions_list);
|
|
|
|
writer->add_member("access_type").add_str(join_type_str[type]);
|
|
|
|
add_json_keyset(writer, "possible_keys", &possible_keys);
|
|
|
|
/* `key` */
|
|
/* For non-basic quick select, 'key' will not be present */
|
|
if (!quick_info || quick_info->is_basic())
|
|
{
|
|
StringBuffer<64> key_str;
|
|
fill_key_str(&key_str, true);
|
|
if (key_str.length())
|
|
writer->add_member("key").add_str(key_str);
|
|
}
|
|
|
|
/* `key_length` */
|
|
StringBuffer<64> key_len_str;
|
|
fill_key_len_str(&key_len_str, true);
|
|
if (key_len_str.length())
|
|
writer->add_member("key_length").add_str(key_len_str);
|
|
|
|
/* `used_key_parts` */
|
|
String_list *parts_list= NULL;
|
|
if (quick_info && quick_info->is_basic())
|
|
parts_list= &quick_info->range.key_parts_list;
|
|
else
|
|
parts_list= &key.key_parts_list;
|
|
|
|
if (parts_list && !parts_list->is_empty())
|
|
print_json_array(writer, "used_key_parts", *parts_list);
|
|
|
|
if (quick_info && !quick_info->is_basic())
|
|
{
|
|
writer->add_member("index_merge").start_object();
|
|
quick_info->print_json(writer);
|
|
writer->end_object();
|
|
}
|
|
|
|
/* `ref` */
|
|
if (!ref_list.is_empty())
|
|
print_json_array(writer, "ref", ref_list);
|
|
|
|
if (rowid_filter)
|
|
{
|
|
rowid_filter->print_explain_json(query, writer, is_analyze);
|
|
}
|
|
|
|
/* r_loops (not present in tabular output) */
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_loops").add_ll(tracker.get_loops());
|
|
}
|
|
|
|
/* `rows` */
|
|
if (rows_set)
|
|
writer->add_member("rows").add_ull(rows);
|
|
|
|
/* `r_rows` */
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_rows");
|
|
if (pre_join_sort)
|
|
{
|
|
/* Get r_rows value from filesort */
|
|
if (pre_join_sort->tracker.get_r_loops())
|
|
writer->add_double(pre_join_sort->tracker.get_avg_examined_rows());
|
|
else
|
|
writer->add_null();
|
|
}
|
|
else
|
|
{
|
|
if (tracker.has_scans())
|
|
writer->add_double(tracker.get_avg_rows());
|
|
else
|
|
writer->add_null();
|
|
}
|
|
|
|
if (op_tracker.get_loops())
|
|
{
|
|
double total_time= op_tracker.get_time_ms();
|
|
if (rowid_filter)
|
|
total_time+= rowid_filter->tracker->get_time_fill_container_ms();
|
|
writer->add_member("r_total_time_ms").add_double(total_time);
|
|
}
|
|
}
|
|
|
|
/* `filtered` */
|
|
if (filtered_set)
|
|
writer->add_member("filtered").add_double(filtered);
|
|
|
|
/* `r_filtered` */
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_filtered");
|
|
if (pre_join_sort)
|
|
{
|
|
/* Get r_filtered value from filesort */
|
|
if (pre_join_sort->tracker.get_r_loops())
|
|
writer->add_double(pre_join_sort->tracker.get_r_filtered()*100);
|
|
else
|
|
writer->add_null();
|
|
}
|
|
else
|
|
{
|
|
/* Get r_filtered from the NL-join runtime */
|
|
if (tracker.has_scans())
|
|
writer->add_double(tracker.get_filtered_after_where()*100.0);
|
|
else
|
|
writer->add_null();
|
|
}
|
|
}
|
|
|
|
for (int i=0; i < (int)extra_tags.elements(); i++)
|
|
{
|
|
tag_to_json(writer, extra_tags.at(i));
|
|
}
|
|
|
|
if (full_scan_on_null_key)
|
|
writer->end_object(); //"full-scan-on-null_key"
|
|
|
|
if (range_checked_fer)
|
|
writer->end_object(); // "range-checked-for-each-record"
|
|
|
|
if (bka_type.is_using_jbuf())
|
|
{
|
|
writer->end_object(); // "block-nl-join"
|
|
writer->add_member("buffer_type").add_str(bka_type.incremental?
|
|
"incremental":"flat");
|
|
writer->add_member("buffer_size").add_size(bka_type.join_buffer_size);
|
|
writer->add_member("join_type").add_str(bka_type.join_alg);
|
|
if (bka_type.mrr_type.length())
|
|
writer->add_member("mrr_type").add_str(bka_type.mrr_type);
|
|
if (where_cond)
|
|
{
|
|
writer->add_member("attached_condition");
|
|
write_item(writer, where_cond);
|
|
}
|
|
|
|
if (is_analyze)
|
|
{
|
|
//writer->add_member("r_loops").add_ll(jbuf_tracker.get_loops());
|
|
writer->add_member("r_filtered");
|
|
if (jbuf_tracker.has_scans())
|
|
writer->add_double(jbuf_tracker.get_filtered_after_where()*100.0);
|
|
else
|
|
writer->add_null();
|
|
}
|
|
}
|
|
|
|
if (derived_select_number)
|
|
{
|
|
/* This is a derived table. Print its contents here */
|
|
writer->add_member("materialized").start_object();
|
|
Explain_node *node= query->get_node(derived_select_number);
|
|
if (node->get_type() == Explain_node::EXPLAIN_SELECT &&
|
|
((Explain_select*)node)->is_lateral)
|
|
{
|
|
writer->add_member("lateral").add_ll(1);
|
|
}
|
|
node->print_explain_json(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
if (non_merged_sjm_number)
|
|
{
|
|
/* This is a non-merged semi-join table. Print its contents here */
|
|
writer->add_member("materialized").start_object();
|
|
writer->add_member("unique").add_ll(1);
|
|
Explain_node *node= query->get_node(non_merged_sjm_number);
|
|
node->connection_type= Explain_node::EXPLAIN_NODE_NON_MERGED_SJ;
|
|
node->print_explain_json(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
if (sjm_nest)
|
|
{
|
|
/* This is a non-merged semi-join table. Print its contents here */
|
|
writer->add_member("materialized").start_object();
|
|
writer->add_member("unique").add_ll(1);
|
|
sjm_nest->print_explain_json(query, writer, is_analyze);
|
|
writer->end_object();
|
|
}
|
|
|
|
if (pre_join_sort)
|
|
{
|
|
writer->end_object(); // filesort
|
|
writer->end_object(); // read_sorted_file
|
|
}
|
|
|
|
writer->end_object();
|
|
}
|
|
|
|
|
|
/*
|
|
Elements in this array match members of enum explain_extra_tag, defined in
|
|
sql_explain.h
|
|
*/
|
|
|
|
const char * extra_tag_text[]=
|
|
{
|
|
"ET_none",
|
|
"Using index condition",
|
|
"Using index condition(BKA)",
|
|
"Using ", // special handling
|
|
"Range checked for each record (index map: 0x", // special handling
|
|
"Using where with pushed condition",
|
|
"Using where",
|
|
"Not exists",
|
|
|
|
"Using index",
|
|
"Full scan on NULL key",
|
|
"Skip_open_table",
|
|
"Open_frm_only",
|
|
"Open_full_table",
|
|
|
|
"Scanned 0 databases",
|
|
"Scanned 1 database",
|
|
"Scanned all databases",
|
|
|
|
"Using index for group-by", // special handling
|
|
|
|
"USING MRR: DONT PRINT ME", // special handling
|
|
|
|
"Distinct",
|
|
"LooseScan",
|
|
"Start temporary",
|
|
"End temporary",
|
|
"FirstMatch", // special handling
|
|
|
|
"Using join buffer", // special handling
|
|
|
|
"Const row not found",
|
|
"Unique row not found",
|
|
"Impossible ON condition",
|
|
};
|
|
|
|
|
|
void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag)
|
|
{
|
|
switch (tag) {
|
|
case ET_USING:
|
|
{
|
|
// quick select
|
|
str->append(STRING_WITH_LEN("Using "));
|
|
quick_info->print_extra(str);
|
|
break;
|
|
}
|
|
case ET_RANGE_CHECKED_FOR_EACH_RECORD:
|
|
{
|
|
/* 4 bits per 1 hex digit + terminating '\0' */
|
|
char buf[MAX_KEY / 4 + 1];
|
|
str->append(STRING_WITH_LEN("Range checked for each "
|
|
"record (index map: 0x"));
|
|
str->append(range_checked_fer->keys_map.print(buf));
|
|
str->append(')');
|
|
break;
|
|
}
|
|
case ET_USING_MRR:
|
|
{
|
|
str->append(mrr_type);
|
|
break;
|
|
}
|
|
case ET_USING_JOIN_BUFFER:
|
|
{
|
|
str->append(extra_tag_text[tag]);
|
|
|
|
str->append(STRING_WITH_LEN(" ("));
|
|
const char *buffer_type= bka_type.incremental ? "incremental" : "flat";
|
|
str->append(buffer_type);
|
|
str->append(STRING_WITH_LEN(", "));
|
|
str->append(bka_type.join_alg);
|
|
str->append(STRING_WITH_LEN(" join"));
|
|
str->append(STRING_WITH_LEN(")"));
|
|
if (bka_type.mrr_type.length())
|
|
{
|
|
str->append(STRING_WITH_LEN("; "));
|
|
str->append(bka_type.mrr_type);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ET_FIRST_MATCH:
|
|
{
|
|
if (firstmatch_table_name.length())
|
|
{
|
|
str->append("FirstMatch(");
|
|
str->append(firstmatch_table_name);
|
|
str->append(")");
|
|
}
|
|
else
|
|
str->append(extra_tag_text[tag]);
|
|
break;
|
|
}
|
|
case ET_USING_INDEX_FOR_GROUP_BY:
|
|
{
|
|
str->append(extra_tag_text[tag]);
|
|
if (loose_scan_is_scanning)
|
|
str->append(" (scanning)");
|
|
break;
|
|
}
|
|
default:
|
|
str->append(extra_tag_text[tag]);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
This is called for top-level Explain_quick_select only. The point of this
|
|
function is:
|
|
- index_merge should print $index_merge_type (child, ...)
|
|
- 'range' should not print anything.
|
|
*/
|
|
|
|
void Explain_quick_select::print_extra(String *str)
|
|
{
|
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
|
|
{
|
|
/* print nothing */
|
|
}
|
|
else
|
|
print_extra_recursive(str);
|
|
}
|
|
|
|
void Explain_quick_select::print_json(Json_writer *writer)
|
|
{
|
|
if (is_basic())
|
|
{
|
|
writer->add_member("range").start_object();
|
|
|
|
writer->add_member("key").add_str(range.get_key_name());
|
|
|
|
print_json_array(writer, "used_key_parts", range.key_parts_list);
|
|
|
|
writer->end_object();
|
|
}
|
|
else
|
|
{
|
|
writer->add_member(get_name_by_type()).start_object();
|
|
|
|
List_iterator_fast<Explain_quick_select> it (children);
|
|
Explain_quick_select* child;
|
|
while ((child = it++))
|
|
child->print_json(writer);
|
|
|
|
writer->end_object();
|
|
}
|
|
}
|
|
|
|
void Explain_quick_select::print_extra_recursive(String *str)
|
|
{
|
|
if (is_basic())
|
|
{
|
|
str->append(range.get_key_name());
|
|
}
|
|
else
|
|
{
|
|
str->append(get_name_by_type());
|
|
str->append('(');
|
|
List_iterator_fast<Explain_quick_select> it (children);
|
|
Explain_quick_select* child;
|
|
bool first= true;
|
|
while ((child = it++))
|
|
{
|
|
if (first)
|
|
first= false;
|
|
else
|
|
str->append(',');
|
|
|
|
child->print_extra_recursive(str);
|
|
}
|
|
str->append(')');
|
|
}
|
|
}
|
|
|
|
|
|
const char * Explain_quick_select::get_name_by_type()
|
|
{
|
|
switch (quick_type) {
|
|
case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
|
|
return "sort_union";
|
|
case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
|
|
return "union";
|
|
case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
|
|
return "intersect";
|
|
case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT:
|
|
return "sort_intersect";
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return "unknown quick select type";
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
This prints a comma-separated list of used indexes, ignoring nesting
|
|
*/
|
|
|
|
void Explain_quick_select::print_key(String *str)
|
|
{
|
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
|
|
{
|
|
if (str->length() > 0)
|
|
str->append(',');
|
|
str->append(range.get_key_name());
|
|
}
|
|
else
|
|
{
|
|
List_iterator_fast<Explain_quick_select> it (children);
|
|
Explain_quick_select* child;
|
|
while ((child = it++))
|
|
{
|
|
child->print_key(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
This prints a comma-separated list of used key_lengths, ignoring nesting
|
|
*/
|
|
|
|
void Explain_quick_select::print_key_len(String *str)
|
|
{
|
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
|
|
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
|
|
{
|
|
char buf[64];
|
|
size_t length;
|
|
length= longlong10_to_str(range.get_key_len(), buf, 10) - buf;
|
|
if (str->length() > 0)
|
|
str->append(',');
|
|
str->append(buf, length);
|
|
}
|
|
else
|
|
{
|
|
List_iterator_fast<Explain_quick_select> it (children);
|
|
Explain_quick_select* child;
|
|
while ((child = it++))
|
|
{
|
|
child->print_key_len(str);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int Explain_delete::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags,
|
|
bool is_analyze)
|
|
{
|
|
if (deleting_all_rows)
|
|
{
|
|
const char *msg= STR_DELETING_ALL_ROWS;
|
|
int res= print_explain_message_line(output, explain_flags, is_analyze,
|
|
1 /*select number*/,
|
|
select_type, &rows, msg);
|
|
return res;
|
|
|
|
}
|
|
else
|
|
{
|
|
return Explain_update::print_explain(query, output, explain_flags,
|
|
is_analyze);
|
|
}
|
|
}
|
|
|
|
|
|
void Explain_delete::print_explain_json(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
if (deleting_all_rows)
|
|
{
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(1);
|
|
writer->add_member("table").start_object();
|
|
// just like mysql-5.6, we don't print table name. Is this ok?
|
|
writer->add_member("message").add_str(STR_DELETING_ALL_ROWS);
|
|
writer->end_object(); // table
|
|
writer->end_object(); // query_block
|
|
return;
|
|
}
|
|
Explain_update::print_explain_json(query, writer, is_analyze);
|
|
}
|
|
|
|
|
|
int Explain_update::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags,
|
|
bool is_analyze)
|
|
{
|
|
StringBuffer<64> key_buf;
|
|
StringBuffer<64> key_len_buf;
|
|
StringBuffer<64> extra_str;
|
|
if (impossible_where || no_partitions)
|
|
{
|
|
const char *msg= impossible_where ?
|
|
STR_IMPOSSIBLE_WHERE :
|
|
STR_NO_ROWS_AFTER_PRUNING;
|
|
int res= print_explain_message_line(output, explain_flags, is_analyze,
|
|
1 /*select number*/,
|
|
select_type,
|
|
NULL, /* rows */
|
|
msg);
|
|
return res;
|
|
}
|
|
|
|
if (quick_info)
|
|
{
|
|
quick_info->print_key(&key_buf);
|
|
quick_info->print_key_len(&key_len_buf);
|
|
|
|
StringBuffer<64> quick_buf;
|
|
quick_info->print_extra(&quick_buf);
|
|
if (quick_buf.length())
|
|
{
|
|
extra_str.append(STRING_WITH_LEN("Using "));
|
|
extra_str.append(quick_buf);
|
|
}
|
|
}
|
|
else if (key.get_key_name())
|
|
{
|
|
const char *name= key.get_key_name();
|
|
key_buf.set(name, strlen(name), &my_charset_bin);
|
|
char buf[64];
|
|
size_t length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
|
|
key_len_buf.copy(buf, length, &my_charset_bin);
|
|
}
|
|
|
|
if (using_where)
|
|
{
|
|
if (extra_str.length() !=0)
|
|
extra_str.append(STRING_WITH_LEN("; "));
|
|
extra_str.append(STRING_WITH_LEN("Using where"));
|
|
}
|
|
|
|
if (mrr_type.length() != 0)
|
|
{
|
|
if (extra_str.length() !=0)
|
|
extra_str.append(STRING_WITH_LEN("; "));
|
|
extra_str.append(mrr_type);
|
|
}
|
|
|
|
if (is_using_filesort())
|
|
{
|
|
if (extra_str.length() !=0)
|
|
extra_str.append(STRING_WITH_LEN("; "));
|
|
extra_str.append(STRING_WITH_LEN("Using filesort"));
|
|
}
|
|
|
|
if (using_io_buffer)
|
|
{
|
|
if (extra_str.length() !=0)
|
|
extra_str.append(STRING_WITH_LEN("; "));
|
|
extra_str.append(STRING_WITH_LEN("Using buffer"));
|
|
}
|
|
|
|
/*
|
|
Single-table DELETE commands do not do "Using temporary".
|
|
"Using index condition" is also not possible (which is an unjustified limitation)
|
|
*/
|
|
double r_filtered= 100 * tracker.get_filtered_after_where();
|
|
double r_rows= tracker.get_avg_rows();
|
|
|
|
print_explain_row(output, explain_flags, is_analyze,
|
|
1, /* id */
|
|
select_type,
|
|
table_name.c_ptr(),
|
|
used_partitions_set? used_partitions.c_ptr() : NULL,
|
|
jtype,
|
|
&possible_keys,
|
|
key_buf.length()? key_buf.c_ptr() : NULL,
|
|
key_len_buf.length() ? key_len_buf.c_ptr() : NULL,
|
|
NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */
|
|
&rows,
|
|
tracker.has_scans()? &r_rows : NULL,
|
|
r_filtered,
|
|
extra_str.c_ptr_safe());
|
|
|
|
return print_explain_for_children(query, output, explain_flags, is_analyze);
|
|
}
|
|
|
|
|
|
void Explain_update::print_explain_json(Explain_query *query,
|
|
Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(1);
|
|
|
|
/* This is the total time it took to do the UPDATE/DELETE */
|
|
if (is_analyze && command_tracker.get_loops())
|
|
{
|
|
writer->add_member("r_total_time_ms").
|
|
add_double(command_tracker.get_time_ms());
|
|
}
|
|
|
|
if (impossible_where || no_partitions)
|
|
{
|
|
const char *msg= impossible_where ? STR_IMPOSSIBLE_WHERE :
|
|
STR_NO_ROWS_AFTER_PRUNING;
|
|
writer->add_member("table").start_object();
|
|
writer->add_member("message").add_str(msg);
|
|
writer->end_object(); // table
|
|
writer->end_object(); // query_block
|
|
return;
|
|
}
|
|
|
|
DBUG_ASSERT(!(is_using_filesort() && using_io_buffer));
|
|
|
|
bool doing_buffering= false;
|
|
|
|
if (is_using_filesort())
|
|
{
|
|
writer->add_member("filesort").start_object();
|
|
if (is_analyze)
|
|
filesort_tracker->print_json_members(writer);
|
|
doing_buffering= true;
|
|
}
|
|
|
|
if (using_io_buffer)
|
|
{
|
|
writer->add_member("buffer").start_object();
|
|
doing_buffering= true;
|
|
}
|
|
|
|
/* Produce elements that are common for buffered and un-buffered cases */
|
|
writer->add_member("table").start_object();
|
|
|
|
if (get_type() == EXPLAIN_UPDATE)
|
|
writer->add_member("update").add_ll(1);
|
|
else
|
|
writer->add_member("delete").add_ll(1);
|
|
|
|
writer->add_member("table_name").add_str(table_name);
|
|
|
|
if (used_partitions_set)
|
|
print_json_array(writer, "partitions", used_partitions_list);
|
|
|
|
writer->add_member("access_type").add_str(join_type_str[jtype]);
|
|
|
|
if (!possible_keys.is_empty())
|
|
{
|
|
List_iterator_fast<char> it(possible_keys);
|
|
const char *name;
|
|
writer->add_member("possible_keys").start_array();
|
|
while ((name= it++))
|
|
writer->add_str(name);
|
|
writer->end_array();
|
|
}
|
|
|
|
/* `key`, `key_length` */
|
|
if (quick_info && quick_info->is_basic())
|
|
{
|
|
StringBuffer<64> key_buf;
|
|
StringBuffer<64> key_len_buf;
|
|
quick_info->print_extra_recursive(&key_buf);
|
|
quick_info->print_key_len(&key_len_buf);
|
|
|
|
writer->add_member("key").add_str(key_buf);
|
|
writer->add_member("key_length").add_str(key_len_buf);
|
|
}
|
|
else if (key.get_key_name())
|
|
{
|
|
writer->add_member("key").add_str(key.get_key_name());
|
|
writer->add_member("key_length").add_str(key.get_key_len());
|
|
}
|
|
|
|
/* `used_key_parts` */
|
|
String_list *parts_list= NULL;
|
|
if (quick_info && quick_info->is_basic())
|
|
parts_list= &quick_info->range.key_parts_list;
|
|
else
|
|
parts_list= &key.key_parts_list;
|
|
|
|
if (parts_list && !parts_list->is_empty())
|
|
{
|
|
List_iterator_fast<char> it(*parts_list);
|
|
const char *name;
|
|
writer->add_member("used_key_parts").start_array();
|
|
while ((name= it++))
|
|
writer->add_str(name);
|
|
writer->end_array();
|
|
}
|
|
|
|
if (quick_info && !quick_info->is_basic())
|
|
{
|
|
writer->add_member("index_merge").start_object();
|
|
quick_info->print_json(writer);
|
|
writer->end_object();
|
|
}
|
|
|
|
/* `rows` */
|
|
writer->add_member("rows").add_ull(rows);
|
|
|
|
|
|
if (mrr_type.length() != 0)
|
|
writer->add_member("mrr_type").add_str(mrr_type.ptr());
|
|
|
|
if (is_analyze)
|
|
{
|
|
if (doing_buffering)
|
|
{
|
|
ha_rows r_rows;
|
|
double r_filtered;
|
|
|
|
if (is_using_filesort())
|
|
{
|
|
if (filesort_tracker->get_r_loops())
|
|
r_rows= (ha_rows) filesort_tracker->get_avg_examined_rows();
|
|
else
|
|
r_rows= 0;
|
|
r_filtered= filesort_tracker->get_r_filtered() * 100.0;
|
|
}
|
|
else
|
|
{
|
|
if (buf_tracker.has_scans())
|
|
r_rows= (ha_rows) buf_tracker.get_avg_rows();
|
|
else
|
|
r_rows= 0;
|
|
r_filtered= buf_tracker.get_filtered_after_where() * 100.0;
|
|
}
|
|
writer->add_member("r_rows").add_ull(r_rows);
|
|
writer->add_member("r_filtered").add_double(r_filtered);
|
|
}
|
|
else /* Not doing buffering */
|
|
{
|
|
writer->add_member("r_rows");
|
|
if (tracker.has_scans())
|
|
writer->add_double(tracker.get_avg_rows());
|
|
else
|
|
writer->add_null();
|
|
|
|
/* There is no 'filtered' estimate in UPDATE/DELETE atm */
|
|
double r_filtered= tracker.get_filtered_after_where() * 100.0;
|
|
writer->add_member("r_filtered").add_double(r_filtered);
|
|
}
|
|
|
|
if (table_tracker.get_loops())
|
|
{
|
|
writer->add_member("r_total_time_ms").
|
|
add_double(table_tracker.get_time_ms());
|
|
}
|
|
}
|
|
|
|
if (where_cond)
|
|
{
|
|
writer->add_member("attached_condition");
|
|
write_item(writer, where_cond);
|
|
}
|
|
|
|
/*** The part of plan that is before the buffering/sorting ends here ***/
|
|
if (is_using_filesort())
|
|
writer->end_object();
|
|
|
|
if (using_io_buffer)
|
|
writer->end_object();
|
|
|
|
writer->end_object(); // table
|
|
|
|
print_explain_json_for_children(query, writer, is_analyze);
|
|
writer->end_object(); // query_block
|
|
}
|
|
|
|
|
|
int Explain_insert::print_explain(Explain_query *query,
|
|
select_result_sink *output,
|
|
uint8 explain_flags,
|
|
bool is_analyze)
|
|
{
|
|
const char *select_type="INSERT";
|
|
print_explain_row(output, explain_flags, is_analyze,
|
|
1, /* id */
|
|
select_type,
|
|
table_name.c_ptr(),
|
|
NULL, // partitions
|
|
JT_ALL,
|
|
NULL, // possible_keys
|
|
NULL, // key
|
|
NULL, // key_len
|
|
NULL, // ref
|
|
NULL, // rows
|
|
NULL, // r_rows
|
|
100.0, // r_filtered
|
|
NULL);
|
|
|
|
return print_explain_for_children(query, output, explain_flags, is_analyze);
|
|
}
|
|
|
|
void Explain_insert::print_explain_json(Explain_query *query,
|
|
Json_writer *writer, bool is_analyze)
|
|
{
|
|
Json_writer_nesting_guard guard(writer);
|
|
|
|
writer->add_member("query_block").start_object();
|
|
writer->add_member("select_id").add_ll(1);
|
|
writer->add_member("table").start_object();
|
|
writer->add_member("table_name").add_str(table_name.c_ptr());
|
|
writer->end_object(); // table
|
|
print_explain_json_for_children(query, writer, is_analyze);
|
|
writer->end_object(); // query_block
|
|
}
|
|
|
|
|
|
void delete_explain_query(LEX *lex)
|
|
{
|
|
DBUG_ENTER("delete_explain_query");
|
|
delete lex->explain;
|
|
lex->explain= NULL;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void create_explain_query(LEX *lex, MEM_ROOT *mem_root)
|
|
{
|
|
DBUG_ASSERT(!lex->explain);
|
|
DBUG_ENTER("create_explain_query");
|
|
|
|
lex->explain= new (mem_root) Explain_query(lex->thd, mem_root);
|
|
DBUG_ASSERT(mem_root == current_thd->mem_root);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root)
|
|
{
|
|
if (!lex->explain)
|
|
create_explain_query(lex, mem_root);
|
|
}
|
|
|
|
|
|
/**
|
|
Build arrays for collectiong keys statistics, sdd possible key names
|
|
to the list and name array
|
|
|
|
@param alloc MEM_ROOT to put data in
|
|
@param list list of possible key names to fill
|
|
@param table table of the keys
|
|
@patam possible_keys possible keys map
|
|
|
|
@retval 0 - OK
|
|
@retval 1 - Error
|
|
*/
|
|
|
|
int Explain_range_checked_fer::append_possible_keys_stat(MEM_ROOT *alloc,
|
|
TABLE *table,
|
|
key_map possible_keys)
|
|
{
|
|
uint j;
|
|
multi_alloc_root(alloc, &keys_stat, sizeof(ha_rows) * table->s->keys,
|
|
&keys_stat_names, sizeof(char *) * table->s->keys, NULL);
|
|
if ((!keys_stat) || (!keys_stat_names))
|
|
{
|
|
keys_stat= NULL;
|
|
keys_stat_names= NULL;
|
|
return 1;
|
|
}
|
|
keys_map= possible_keys;
|
|
keys= table->s->keys;
|
|
bzero(keys_stat, sizeof(ha_rows) * table->s->keys);
|
|
for (j= 0; j < table->s->keys; j++)
|
|
{
|
|
if (possible_keys.is_set(j))
|
|
{
|
|
if (!(keys_stat_names[j]= key_set.append_str(alloc,
|
|
table->key_info[j].name.str)))
|
|
return 1;
|
|
}
|
|
else
|
|
keys_stat_names[j]= NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Explain_range_checked_fer::collect_data(QUICK_SELECT_I *quick)
|
|
{
|
|
if (quick)
|
|
{
|
|
if (quick->index == MAX_KEY)
|
|
index_merge++;
|
|
else
|
|
{
|
|
DBUG_ASSERT(quick->index < keys);
|
|
DBUG_ASSERT(keys_stat);
|
|
DBUG_ASSERT(keys_stat_names);
|
|
DBUG_ASSERT(keys_stat_names[ quick->index]);
|
|
keys_stat[quick->index]++;
|
|
}
|
|
}
|
|
else
|
|
full_scan++;
|
|
}
|
|
|
|
|
|
void Explain_range_checked_fer::print_json(Json_writer *writer,
|
|
bool is_analyze)
|
|
{
|
|
writer->add_member("range-checked-for-each-record").start_object();
|
|
add_json_keyset(writer, "keys", &key_set);
|
|
if (is_analyze)
|
|
{
|
|
writer->add_member("r_keys").start_object();
|
|
writer->add_member("full_scan").add_ll(full_scan);
|
|
writer->add_member("index_merge").add_ll(index_merge);
|
|
if (keys_stat)
|
|
{
|
|
writer->add_member("range").start_object();
|
|
for (uint i= 0; i < keys; i++)
|
|
{
|
|
if (keys_stat_names[i])
|
|
{
|
|
writer->add_member(keys_stat_names[i]).add_ll(keys_stat[i]);
|
|
}
|
|
}
|
|
writer->end_object();
|
|
}
|
|
writer->end_object();
|
|
}
|
|
}
|