Browse Source
SHOW EXPLAIN UPDATE/DELETE
SHOW EXPLAIN UPDATE/DELETE
- Introduce "Query Plan Footprints" (abbrev. QPFs) QPF is a part of query plan that is 1. sufficient to produce EXPLAIN output, 2. can be used to produce EXPLAIN output even after its subquery/union was executed and deleted 3. is cheap to save so that we can always save query plans - This patch doesn't fully address #2, we make/save strings for a number of EXPLAIN's columns. This will be fixed.pull/3/head
12 changed files with 1466 additions and 15 deletions
-
1libmysqld/CMakeLists.txt
-
1sql/CMakeLists.txt
-
3sql/handler.h
-
417sql/opt_qpf.cc
-
274sql/opt_qpf.h
-
5sql/sql_delete.cc
-
135sql/sql_lex.cc
-
12sql/sql_lex.h
-
8sql/sql_parse.cc
-
608sql/sql_select.cc
-
15sql/sql_select.h
-
2support-files/build-tags
@ -0,0 +1,417 @@ |
|||
/*
|
|||
TODO MP AB copyright |
|||
*/ |
|||
|
|||
#ifdef USE_PRAGMA_IMPLEMENTATION
|
|||
#pragma implementation // gcc: Class implementation
|
|||
#endif
|
|||
|
|||
#include "sql_priv.h"
|
|||
#include "sql_select.h"
|
|||
|
|||
|
|||
QPF_query::QPF_query() |
|||
{ |
|||
memset(&unions, 0, sizeof(unions)); |
|||
memset(&selects, 0, sizeof(selects)); |
|||
} |
|||
|
|||
|
|||
QPF_node *QPF_query::get_node(uint select_id) |
|||
{ |
|||
if (unions[select_id]) |
|||
return unions[select_id]; |
|||
else |
|||
return selects[select_id]; |
|||
} |
|||
|
|||
|
|||
QPF_select *QPF_query::get_select(uint select_id) |
|||
{ |
|||
return selects[select_id]; |
|||
} |
|||
|
|||
|
|||
void QPF_query::add_node(QPF_node *node) |
|||
{ |
|||
if (node->get_type() == QPF_node::QPF_UNION) |
|||
{ |
|||
QPF_union *u= (QPF_union*)node; |
|||
unions[u->get_select_id()]= u; |
|||
} |
|||
else |
|||
{ |
|||
QPF_select *sel= (QPF_select*)node; |
|||
if (sel->select_id == (int)UINT_MAX) |
|||
{ |
|||
//TODO this is a "fake select" from a UNION.
|
|||
DBUG_ASSERT(0); |
|||
} |
|||
else |
|||
selects[sel->select_id] = sel; |
|||
} |
|||
} |
|||
|
|||
|
|||
/*
|
|||
The main entry point to print EXPLAIN of the entire query |
|||
*/ |
|||
|
|||
int QPF_query::print_explain(select_result_sink *output, |
|||
uint8 explain_flags) |
|||
{ |
|||
// Start with id=1
|
|||
QPF_node *node= get_node(1); |
|||
return node->print_explain(this, output, explain_flags); |
|||
} |
|||
|
|||
|
|||
void QPF_union::push_table_name(List<Item> *item_list) |
|||
{ |
|||
} |
|||
|
|||
|
|||
static void push_str(List<Item> *item_list, const char *str) |
|||
{ |
|||
item_list->push_back(new Item_string(str, |
|||
strlen(str), system_charset_info)); |
|||
} |
|||
|
|||
|
|||
static void push_string(List<Item> *item_list, String *str) |
|||
{ |
|||
item_list->push_back(new Item_string(str->ptr(), str->length(), |
|||
system_charset_info)); |
|||
} |
|||
|
|||
|
|||
int QPF_union::print_explain(QPF_query *query, select_result_sink *output, |
|||
uint8 explain_flags) |
|||
{ |
|||
// print all children, in order
|
|||
for (int i= 0; i < (int) children.elements(); i++) |
|||
{ |
|||
QPF_select *sel= query->get_select(children.at(i)); |
|||
sel->print_explain(query, output, explain_flags); |
|||
} |
|||
|
|||
/* Print a line with "UNION RESULT" */ |
|||
List<Item> item_list; |
|||
Item *item_null= new Item_null(); |
|||
|
|||
/* `id` column */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `select_type` column */ |
|||
push_str(&item_list, fake_select_type); |
|||
|
|||
/* `table` column: something like "<union1,2>" */ |
|||
//
|
|||
{ |
|||
char table_name_buffer[SAFE_NAME_LEN]; |
|||
uint childno= 0; |
|||
uint len= 6, lastop= 0; |
|||
memcpy(table_name_buffer, STRING_WITH_LEN("<union")); |
|||
|
|||
for (; childno < children.elements() && len + lastop + 5 < NAME_LEN; |
|||
childno++) |
|||
{ |
|||
len+= lastop; |
|||
lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, |
|||
"%u,", children.at(childno)); |
|||
} |
|||
|
|||
if (childno < children.elements() || len + lastop >= NAME_LEN) |
|||
{ |
|||
memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); |
|||
len+= 4; |
|||
} |
|||
else |
|||
{ |
|||
len+= lastop; |
|||
table_name_buffer[len - 1]= '>'; // change ',' to '>'
|
|||
} |
|||
const CHARSET_INFO *cs= system_charset_info; |
|||
item_list.push_back(new Item_string(table_name_buffer, len, cs)); |
|||
} |
|||
//
|
|||
push_table_name(&item_list); |
|||
|
|||
/* `partitions` column */ |
|||
if (explain_flags & DESCRIBE_PARTITIONS) |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `type` column */ |
|||
push_str(&item_list, join_type_str[JT_ALL]); |
|||
|
|||
/* `possible_keys` column */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `key` */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `key_len` */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `ref` */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `rows` */ |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `filtered` */ |
|||
if (explain_flags & DESCRIBE_EXTENDED) |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `Extra` */ |
|||
StringBuffer<256> extra_buf; |
|||
if (using_filesort) |
|||
{ |
|||
extra_buf.append(STRING_WITH_LEN("Using filesort")); |
|||
} |
|||
const CHARSET_INFO *cs= system_charset_info; |
|||
item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); |
|||
|
|||
if (output->send_data(item_list)) |
|||
return 1; |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
int QPF_select::print_explain(QPF_query *query, select_result_sink *output, |
|||
uint8 explain_flags) |
|||
{ |
|||
if (message) |
|||
{ |
|||
List<Item> item_list; |
|||
const CHARSET_INFO *cs= system_charset_info; |
|||
Item *item_null= new Item_null(); |
|||
|
|||
item_list.push_back(new Item_int((int32) select_id)); |
|||
item_list.push_back(new Item_string(select_type, |
|||
strlen(select_type), cs)); |
|||
for (uint i=0 ; i < 7; i++) |
|||
item_list.push_back(item_null); |
|||
if (explain_flags & DESCRIBE_PARTITIONS) |
|||
item_list.push_back(item_null); |
|||
if (explain_flags & DESCRIBE_EXTENDED) |
|||
item_list.push_back(item_null); |
|||
|
|||
item_list.push_back(new Item_string(message,strlen(message),cs)); |
|||
|
|||
if (output->send_data(item_list)) |
|||
return 1; |
|||
return 0; |
|||
} |
|||
else |
|||
{ |
|||
bool using_tmp= using_temporary; |
|||
bool using_fs= using_filesort; |
|||
for (uint i=0; i< n_join_tabs; i++) |
|||
{ |
|||
join_tabs[i]->print_explain(output, explain_flags, 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; |
|||
} |
|||
} |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags, |
|||
uint select_id, const char *select_type, |
|||
bool using_temporary, bool using_filesort) |
|||
{ |
|||
List<Item> item_list; |
|||
Item *item_null= new Item_null(); |
|||
//const CHARSET_INFO *cs= system_charset_info;
|
|||
|
|||
/* `id` column */ |
|||
item_list.push_back(new Item_int((int32) select_id)); |
|||
|
|||
/* `select_type` column */ |
|||
push_str(&item_list, select_type); |
|||
|
|||
/* `table` column */ |
|||
push_string(&item_list, &table_name); |
|||
|
|||
/* `partitions` column */ |
|||
if (explain_flags & DESCRIBE_PARTITIONS) |
|||
{ |
|||
if (used_partitions_set) |
|||
{ |
|||
push_string(&item_list, &used_partitions); |
|||
} |
|||
else |
|||
item_list.push_back(item_null); |
|||
} |
|||
|
|||
/* `type` column */ |
|||
push_str(&item_list, join_type_str[type]); |
|||
|
|||
/* `possible_keys` column */ |
|||
//push_str(item_list, "TODO");
|
|||
item_list.push_back(item_null); |
|||
|
|||
/* `key` */ |
|||
if (key_set) |
|||
push_string(&item_list, &key); |
|||
else |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `key_len` */ |
|||
if (key_len_set) |
|||
push_string(&item_list, &key_len); |
|||
else |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `ref` */ |
|||
if (ref_set) |
|||
push_string(&item_list, &ref); |
|||
else |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `rows` */ |
|||
if (rows_set) |
|||
{ |
|||
item_list.push_back(new Item_int((longlong) (ulonglong) rows, |
|||
MY_INT64_NUM_DECIMAL_DIGITS)); |
|||
} |
|||
else |
|||
item_list.push_back(item_null); |
|||
|
|||
/* `filtered` */ |
|||
if (explain_flags & DESCRIBE_EXTENDED) |
|||
{ |
|||
if (filtered_set) |
|||
{ |
|||
item_list.push_back(new Item_float(filtered, 2)); |
|||
} |
|||
else |
|||
item_list.push_back(item_null); |
|||
} |
|||
|
|||
/* `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) |
|||
{ |
|||
if (first) |
|||
first= false; |
|||
else |
|||
extra_buf.append(STRING_WITH_LEN("; ")); |
|||
extra_buf.append(STRING_WITH_LEN("Using filesort")); |
|||
} |
|||
|
|||
const CHARSET_INFO *cs= system_charset_info; |
|||
item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); |
|||
|
|||
if (output->send_data(item_list)) |
|||
return 1; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
|
|||
const char * extra_tag_text[]= |
|||
{ |
|||
"ET_none", |
|||
"Using index condition", |
|||
"Using index condition(BKA)", |
|||
"Using ", //special
|
|||
"Range checked for each record (index map: 0x", //special
|
|||
"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?
|
|||
|
|||
"USING MRR: DONT PRINT ME", // Special!
|
|||
|
|||
"Distinct", |
|||
"LooseScan", |
|||
"Start temporary", |
|||
"End temporary", |
|||
"FirstMatch", //TODO: also handle special variant!
|
|||
|
|||
"Using join buffer", // Special!,
|
|||
|
|||
"const row not found", |
|||
"unique row not found", |
|||
"Impossible ON condition" |
|||
}; |
|||
|
|||
|
|||
void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) |
|||
{ |
|||
switch (tag) { |
|||
case ET_USING: |
|||
{ |
|||
// quick select
|
|||
str->append(STRING_WITH_LEN("Using ")); |
|||
str->append(quick_info); |
|||
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_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(join_buffer_type); |
|||
break; |
|||
} |
|||
default: |
|||
str->append(extra_tag_text[tag]); |
|||
} |
|||
} |
|||
|
|||
|
@ -0,0 +1,274 @@ |
|||
/************************************************************************************** |
|||
|
|||
Query Plan Footprint (QPF) structures |
|||
|
|||
These structures |
|||
- Can be produced in-expensively from query plan. |
|||
- Store sufficient information to produce either a tabular or a json EXPLAIN |
|||
output |
|||
- Have methods that produce a tabular output. |
|||
|
|||
*************************************************************************************/ |
|||
|
|||
class QPF_query; |
|||
|
|||
/* |
|||
A node can be either a SELECT, or a UNION. |
|||
*/ |
|||
class QPF_node : public Sql_alloc |
|||
{ |
|||
public: |
|||
enum qpf_node_type {QPF_UNION, QPF_SELECT}; |
|||
|
|||
virtual enum qpf_node_type get_type()= 0; |
|||
virtual int print_explain(QPF_query *query, select_result_sink *output, |
|||
uint8 explain_flags)=0; |
|||
virtual ~QPF_node(){} |
|||
}; |
|||
|
|||
|
|||
/* |
|||
Nesting. |
|||
QPF_select may have children QPF_select-s. |
|||
(these can be FROM-subqueries, or subqueries from other clauses) |
|||
|
|||
As for unions, the standard approach is: |
|||
- UNION node can be where the select node can be; |
|||
- the union has a select that retrieves results from temptable (a special |
|||
kind of child) |
|||
- and it has regular children selects that are merged into the union. |
|||
|
|||
*/ |
|||
|
|||
class QPF_table_access; |
|||
|
|||
class QPF_select : public QPF_node |
|||
{ |
|||
/*Construction interface */ |
|||
public: |
|||
enum qpf_node_type get_type() { return QPF_SELECT; } |
|||
|
|||
#if 0 |
|||
/* Constructs a finished degenerate join plan */ |
|||
QPF_select(int select_id_arg, const char *select_type_arg, const char* msg) : |
|||
select_id(select_id_arg), |
|||
select_type(select_type_arg), |
|||
message(msg), |
|||
join_tabs(NULL), n_join_tabs(0) |
|||
{} |
|||
|
|||
/* Constructs an un-finished, non degenerate join plan. */ |
|||
QPF_select(int select_id_arg, const char *select_type_arg) : |
|||
select_id(select_id_arg), |
|||
select_type(select_type_arg), |
|||
message(NULL), |
|||
join_tabs(NULL), n_join_tabs(0) |
|||
{} |
|||
#endif |
|||
QPF_select() : |
|||
message(NULL), join_tabs(NULL), |
|||
using_temporary(false), using_filesort(false) |
|||
{} |
|||
|
|||
bool add_table(QPF_table_access *tab) |
|||
{ |
|||
if (!join_tabs) |
|||
{ |
|||
join_tabs= (QPF_table_access**) malloc(sizeof(QPF_table_access*) * MAX_TABLES); |
|||
n_join_tabs= 0; |
|||
} |
|||
join_tabs[n_join_tabs++]= tab; |
|||
return false; |
|||
} |
|||
|
|||
public: |
|||
int select_id; /* -1 means NULL. */ |
|||
const char *select_type; |
|||
|
|||
/* |
|||
If message != NULL, this is a degenerate join plan, and all subsequent |
|||
members have no info |
|||
*/ |
|||
const char *message; |
|||
|
|||
/* |
|||
According to the discussion: this should be an array of "table |
|||
descriptors". |
|||
|
|||
As for SJ-Materialization. Start_materialize/end_materialize markers? |
|||
*/ |
|||
QPF_table_access** join_tabs; |
|||
uint n_join_tabs; |
|||
|
|||
/* Global join attributes. In tabular form, they are printed on the first row */ |
|||
bool using_temporary; |
|||
bool using_filesort; |
|||
|
|||
void print_tabular(select_result_sink *output, uint8 explain_flags//, |
|||
//bool *printed_anything |
|||
); |
|||
|
|||
int print_explain(QPF_query *query, select_result_sink *output, |
|||
uint8 explain_flags); |
|||
}; |
|||
|
|||
|
|||
class QPF_union : public QPF_node |
|||
{ |
|||
public: |
|||
enum qpf_node_type get_type() { return QPF_UNION; } |
|||
|
|||
int get_select_id() |
|||
{ |
|||
DBUG_ASSERT(children.elements() > 0); |
|||
return children.at(0); |
|||
} |
|||
// This has QPF_select children |
|||
Dynamic_array<int> children; |
|||
|
|||
void add_select(int select_no) |
|||
{ |
|||
children.append(select_no); |
|||
} |
|||
void push_table_name(List<Item> *item_list); |
|||
int print_explain(QPF_query *query, select_result_sink *output, |
|||
uint8 explain_flags); |
|||
|
|||
const char *fake_select_type; |
|||
bool using_filesort; |
|||
}; |
|||
|
|||
|
|||
/* |
|||
This is the whole query. |
|||
*/ |
|||
|
|||
class QPF_query |
|||
{ |
|||
public: |
|||
QPF_query(); |
|||
void add_node(QPF_node *node); |
|||
int print_explain(select_result_sink *output, uint8 explain_flags); |
|||
|
|||
/* This will return a select, or a union */ |
|||
QPF_node *get_node(uint select_id); |
|||
|
|||
/* This will return a select (even if there is a union with this id) */ |
|||
QPF_select *get_select(uint select_id); |
|||
|
|||
private: |
|||
QPF_union *unions[MAX_TABLES]; |
|||
QPF_select *selects[MAX_TABLES]; |
|||
}; |
|||
|
|||
|
|||
enum Extra_tag |
|||
{ |
|||
ET_none= 0, /* not-a-tag */ |
|||
ET_USING_INDEX_CONDITION, |
|||
ET_USING_INDEX_CONDITION_BKA, |
|||
ET_USING, /* For quick selects of various kinds */ |
|||
ET_RANGE_CHECKED_FOR_EACH_RECORD, |
|||
ET_USING_WHERE_WITH_PUSHED_CONDITION, |
|||
ET_USING_WHERE, |
|||
ET_NOT_EXISTS, |
|||
|
|||
ET_USING_INDEX, |
|||
ET_FULL_SCAN_ON_NULL_KEY, |
|||
ET_SKIP_OPEN_TABLE, |
|||
ET_OPEN_FRM_ONLY, |
|||
ET_OPEN_FULL_TABLE, |
|||
|
|||
ET_SCANNED_0_DATABASES, |
|||
ET_SCANNED_1_DATABASE, |
|||
ET_SCANNED_ALL_DATABASES, |
|||
|
|||
ET_USING_INDEX_FOR_GROUP_BY, |
|||
|
|||
ET_USING_MRR, // does not print "Using mrr". |
|||
|
|||
ET_DISTINCT, |
|||
ET_LOOSESCAN, |
|||
ET_START_TEMPORARY, |
|||
ET_END_TEMPORARY, |
|||
ET_FIRST_MATCH, |
|||
|
|||
ET_USING_JOIN_BUFFER, |
|||
|
|||
ET_CONST_ROW_NOT_FOUND, |
|||
ET_UNIQUE_ROW_NOT_FOUND, |
|||
ET_IMPOSSIBLE_ON_CONDITION, |
|||
|
|||
ET_total |
|||
}; |
|||
|
|||
|
|||
class QPF_table_access |
|||
{ |
|||
public: |
|||
void push_extra(enum Extra_tag extra_tag); |
|||
|
|||
/* Internals */ |
|||
public: |
|||
/* id and 'select_type' are cared-of by the parent QPF_select */ |
|||
TABLE *table; |
|||
StringBuffer<256> table_name; |
|||
|
|||
enum join_type type; |
|||
|
|||
StringBuffer<256> used_partitions; |
|||
bool used_partitions_set; |
|||
|
|||
key_map possible_keys; |
|||
|
|||
uint key_no; |
|||
uint key_length; |
|||
|
|||
Dynamic_array<enum Extra_tag> extra_tags; |
|||
|
|||
//temporary: |
|||
StringBuffer<256> key; |
|||
StringBuffer<256> key_len; |
|||
StringBuffer<256> ref; |
|||
bool key_set; |
|||
bool key_len_set; |
|||
bool ref_set; |
|||
|
|||
bool rows_set; |
|||
ha_rows rows; |
|||
|
|||
double filtered; |
|||
bool filtered_set; |
|||
|
|||
/* Various stuff for 'Extra' column*/ |
|||
uint join_cache_level; |
|||
|
|||
// Valid if ET_USING tag is present |
|||
StringBuffer<256> quick_info; |
|||
|
|||
// Valid if ET_USING_INDEX_FOR_GROUP_BY is present |
|||
StringBuffer<256> loose_scan_type; |
|||
|
|||
// valid with ET_RANGE_CHECKED_FOR_EACH_RECORD |
|||
key_map range_checked_map; |
|||
|
|||
// valid with ET_USING_MRR |
|||
StringBuffer <256> mrr_type; |
|||
|
|||
// valid with ET_USING_JOIN_BUFFER |
|||
StringBuffer <256> join_buffer_type; |
|||
|
|||
TABLE *firstmatch_table; |
|||
|
|||
int print_explain(select_result_sink *output, uint8 explain_flags, |
|||
uint select_id, const char *select_type, |
|||
bool using_temporary, bool using_filesort); |
|||
private: |
|||
void append_tag_name(String *str, enum Extra_tag tag); |
|||
}; |
|||
|
|||
// Update_plan and Delete_plan belong to this kind of structures, too. |
|||
|
|||
// TODO: should Update_plan inherit from QPF_table_access? |
|||
|
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue