2013-06-17 11:59:38 +04:00
|
|
|
/**************************************************************************************
|
|
|
|
|
|
|
|
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:
|
2013-06-20 15:15:24 +04:00
|
|
|
enum qpf_node_type {QPF_UNION, QPF_SELECT, QPF_UPDATE, QPF_DELETE };
|
2013-06-17 11:59:38 +04:00
|
|
|
virtual enum qpf_node_type get_type()= 0;
|
2013-06-18 10:57:36 +04:00
|
|
|
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
virtual int get_select_id()= 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
A node may have children nodes. When a node's QPF (Query Plan Footprint) is
|
|
|
|
created, children nodes may not yet have QPFs. This is why we store ids.
|
|
|
|
*/
|
2013-06-18 10:57:36 +04:00
|
|
|
Dynamic_array<int> children;
|
|
|
|
void add_child(int select_no)
|
|
|
|
{
|
|
|
|
children.append(select_no);
|
|
|
|
}
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
virtual int print_explain(QPF_query *query, select_result_sink *output,
|
|
|
|
uint8 explain_flags)=0;
|
2013-06-20 15:15:24 +04:00
|
|
|
|
|
|
|
int print_explain_for_children(QPF_query *query, select_result_sink *output,
|
|
|
|
uint8 explain_flags);
|
2013-06-17 11:59:38 +04:00
|
|
|
virtual ~QPF_node(){}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
class QPF_table_access;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
/*
|
|
|
|
Query Plan Footprint of a SELECT.
|
|
|
|
|
|
|
|
A select can be:
|
2013-06-27 17:02:44 +04:00
|
|
|
1. A degenerate case. In this case, message!=NULL, and it contains a
|
|
|
|
description of what kind of degenerate case it is (e.g. "Impossible
|
|
|
|
WHERE").
|
|
|
|
2. a non-degenrate join. In this case, join_tabs describes the join.
|
2013-06-19 18:47:31 +04:00
|
|
|
|
|
|
|
In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation.
|
2013-06-27 17:02:44 +04:00
|
|
|
|
|
|
|
In both cases, the select may have children nodes. class QPF_node provides
|
|
|
|
a way get node's children.
|
2013-06-17 11:59:38 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
class QPF_select : public QPF_node
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
enum qpf_node_type get_type() { return QPF_SELECT; }
|
|
|
|
|
|
|
|
QPF_select() :
|
|
|
|
message(NULL), join_tabs(NULL),
|
|
|
|
using_temporary(false), using_filesort(false)
|
|
|
|
{}
|
2013-06-17 19:39:55 +04:00
|
|
|
|
|
|
|
~QPF_select();
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
bool add_table(QPF_table_access *tab)
|
|
|
|
{
|
|
|
|
if (!join_tabs)
|
|
|
|
{
|
2013-06-17 19:39:55 +04:00
|
|
|
join_tabs= (QPF_table_access**) my_malloc(sizeof(QPF_table_access*) *
|
|
|
|
MAX_TABLES, MYF(0));
|
2013-06-17 11:59:38 +04:00
|
|
|
n_join_tabs= 0;
|
|
|
|
}
|
|
|
|
join_tabs[n_join_tabs++]= tab;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2013-06-19 18:47:31 +04:00
|
|
|
int select_id;
|
2013-06-17 11:59:38 +04:00
|
|
|
const char *select_type;
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
int get_select_id() { return select_id; }
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
/*
|
|
|
|
If message != NULL, this is a degenerate join plan, and all subsequent
|
|
|
|
members have no info
|
|
|
|
*/
|
|
|
|
const char *message;
|
|
|
|
|
|
|
|
/*
|
2013-06-19 18:47:31 +04:00
|
|
|
A flat array of Query Plan Footprints. The order is "just like EXPLAIN
|
|
|
|
would print them".
|
2013-06-17 11:59:38 +04:00
|
|
|
*/
|
|
|
|
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;
|
2013-06-17 19:39:55 +04:00
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
int print_explain(QPF_query *query, select_result_sink *output,
|
|
|
|
uint8 explain_flags);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
/*
|
|
|
|
Query Plan Footprint of a UNION.
|
|
|
|
|
|
|
|
A UNION may or may not have "Using filesort".
|
|
|
|
*/
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
class QPF_union : public QPF_node
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
enum qpf_node_type get_type() { return QPF_UNION; }
|
|
|
|
|
|
|
|
int get_select_id()
|
|
|
|
{
|
2013-06-18 10:57:36 +04:00
|
|
|
DBUG_ASSERT(union_members.elements() > 0);
|
|
|
|
return union_members.at(0);
|
2013-06-17 11:59:38 +04:00
|
|
|
}
|
2013-06-18 10:57:36 +04:00
|
|
|
/*
|
2013-06-19 18:47:31 +04:00
|
|
|
Members of the UNION. Note: these are different from UNION's "children".
|
2013-06-18 10:57:36 +04:00
|
|
|
Example:
|
|
|
|
|
|
|
|
(select * from t1) union
|
|
|
|
(select * from t2) order by (select col1 from t3 ...)
|
|
|
|
|
|
|
|
here
|
2013-06-19 18:47:31 +04:00
|
|
|
- select-from-t1 and select-from-t2 are "union members",
|
2013-06-18 10:57:36 +04:00
|
|
|
- select-from-t3 is the only "child".
|
|
|
|
*/
|
|
|
|
Dynamic_array<int> union_members;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
void add_select(int select_no)
|
|
|
|
{
|
2013-06-18 10:57:36 +04:00
|
|
|
union_members.append(select_no);
|
2013-06-17 11:59:38 +04:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2013-06-20 15:15:24 +04:00
|
|
|
class QPF_delete;
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
/*
|
2013-06-20 15:15:24 +04:00
|
|
|
Query Plan Footprint (QPF) for a query (i.e. a statement).
|
|
|
|
|
|
|
|
This should be able to survive when the query plan was deleted. Currently,
|
|
|
|
we do not intend for it survive until after query's MEM_ROOT is freed. It
|
|
|
|
does surivive freeing of query's items.
|
|
|
|
|
|
|
|
For reference, the process of post-query cleanup is as follows:
|
|
|
|
|
|
|
|
>dispatch_command
|
|
|
|
| >mysql_parse
|
|
|
|
| | ...
|
|
|
|
| | lex_end()
|
|
|
|
| | ...
|
|
|
|
| | >THD::cleanup_after_query
|
|
|
|
| | | ...
|
|
|
|
| | | free_items()
|
|
|
|
| | | ...
|
|
|
|
| | <THD::cleanup_after_query
|
|
|
|
| |
|
|
|
|
| <mysql_parse
|
|
|
|
|
|
|
|
|
| log_slow_statement()
|
|
|
|
|
|
|
|
|
| free_root()
|
|
|
|
|
|
|
|
|
>dispatch_command
|
|
|
|
|
|
|
|
That is, the order of actions is:
|
|
|
|
- free query's Items
|
|
|
|
- write to slow query log
|
|
|
|
- free query's MEM_ROOT
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
*/
|
|
|
|
|
2013-06-17 19:39:55 +04:00
|
|
|
class QPF_query : public Sql_alloc
|
2013-06-17 11:59:38 +04:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
QPF_query();
|
2013-06-17 19:39:55 +04:00
|
|
|
~QPF_query();
|
2013-06-19 18:47:31 +04:00
|
|
|
/* Add a new node */
|
2013-06-17 11:59:38 +04:00
|
|
|
void add_node(QPF_node *node);
|
|
|
|
|
|
|
|
/* 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);
|
2013-06-18 21:08:34 +04:00
|
|
|
|
2013-06-21 22:26:03 +04:00
|
|
|
QPF_union *get_union(uint select_id);
|
|
|
|
|
2013-06-20 15:15:24 +04:00
|
|
|
/* QPF_delete inherits from QPF_update */
|
|
|
|
QPF_update *upd_del_plan;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
/* Produce a tabular EXPLAIN output */
|
|
|
|
int print_explain(select_result_sink *output, uint8 explain_flags);
|
2013-06-27 18:52:47 +04:00
|
|
|
|
|
|
|
/* If true, at least part of EXPLAIN can be printed */
|
|
|
|
bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; }
|
2013-06-21 13:26:53 +04:00
|
|
|
MEM_ROOT *mem_root;
|
2013-06-17 11:59:38 +04:00
|
|
|
private:
|
2013-06-21 22:26:03 +04:00
|
|
|
Dynamic_array<QPF_union*> unions;
|
|
|
|
Dynamic_array<QPF_select*> selects;
|
2013-06-27 17:02:44 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
Debugging aid: count how many times add_node() was called. Ideally, it
|
|
|
|
should be one, we currently allow O(1) query plan saves for each
|
|
|
|
select or union. The goal is not to have O(#rows_in_some_table), which
|
|
|
|
is unacceptable.
|
|
|
|
*/
|
2013-06-27 16:41:12 +04:00
|
|
|
longlong operations;
|
2013-06-17 11:59:38 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2013-06-19 18:47:31 +04:00
|
|
|
/*
|
|
|
|
Query Plan Footprint for a JOIN_TAB.
|
|
|
|
*/
|
2013-06-17 19:39:55 +04:00
|
|
|
class QPF_table_access : public Sql_alloc
|
2013-06-17 11:59:38 +04:00
|
|
|
{
|
|
|
|
public:
|
|
|
|
void push_extra(enum Extra_tag extra_tag);
|
|
|
|
|
|
|
|
/* Internals */
|
|
|
|
public:
|
2013-06-18 10:57:36 +04:00
|
|
|
/*
|
|
|
|
0 means this tab is not inside SJM nest and should use QPF_select's id
|
|
|
|
other value means the tab is inside an SJM nest.
|
|
|
|
*/
|
|
|
|
int sjm_nest_select_id;
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
/* id and 'select_type' are cared-of by the parent QPF_select */
|
|
|
|
TABLE *table;
|
2013-06-18 10:57:36 +04:00
|
|
|
StringBuffer<64> table_name;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
enum join_type type;
|
|
|
|
|
2013-06-18 10:57:36 +04:00
|
|
|
StringBuffer<64> used_partitions;
|
2013-06-17 11:59:38 +04:00
|
|
|
bool used_partitions_set;
|
|
|
|
|
|
|
|
key_map possible_keys;
|
2013-06-18 10:57:36 +04:00
|
|
|
StringBuffer<64> possible_keys_str;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
2013-06-18 10:57:36 +04:00
|
|
|
/* Not used? */
|
2013-06-17 11:59:38 +04:00
|
|
|
uint key_no;
|
|
|
|
uint key_length;
|
|
|
|
|
2013-06-18 10:57:36 +04:00
|
|
|
bool key_set; /* not set means 'NULL' should be printed */
|
|
|
|
StringBuffer<64> key;
|
|
|
|
|
|
|
|
bool key_len_set; /* not set means 'NULL' should be printed */
|
|
|
|
StringBuffer<64> key_len;
|
|
|
|
|
|
|
|
bool ref_set; /* not set means 'NULL' should be printed */
|
|
|
|
StringBuffer<64> ref;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
2013-06-27 17:02:44 +04:00
|
|
|
bool rows_set; /* not set means 'NULL' should be printed */
|
2013-06-17 11:59:38 +04:00
|
|
|
ha_rows rows;
|
|
|
|
|
2013-06-27 17:02:44 +04:00
|
|
|
bool filtered_set; /* not set means 'NULL' should be printed */
|
2013-06-18 10:57:36 +04:00
|
|
|
double filtered;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
2013-06-27 17:02:44 +04:00
|
|
|
/*
|
|
|
|
Contents of the 'Extra' column. Some are converted into strings, some have
|
|
|
|
parameters, values for which are stored below.
|
|
|
|
*/
|
|
|
|
Dynamic_array<enum Extra_tag> extra_tags;
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
// Valid if ET_USING tag is present
|
2013-06-18 10:57:36 +04:00
|
|
|
StringBuffer<64> quick_info;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
// Valid if ET_USING_INDEX_FOR_GROUP_BY is present
|
2013-06-18 10:57:36 +04:00
|
|
|
StringBuffer<64> loose_scan_type;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
// valid with ET_RANGE_CHECKED_FOR_EACH_RECORD
|
|
|
|
key_map range_checked_map;
|
|
|
|
|
|
|
|
// valid with ET_USING_MRR
|
2013-06-18 21:08:34 +04:00
|
|
|
StringBuffer<64> mrr_type;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
// valid with ET_USING_JOIN_BUFFER
|
2013-06-18 21:08:34 +04:00
|
|
|
StringBuffer<64> join_buffer_type;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
2013-06-18 10:57:36 +04:00
|
|
|
//TABLE *firstmatch_table;
|
|
|
|
StringBuffer<64> firstmatch_table_name;
|
2013-06-17 11:59:38 +04:00
|
|
|
|
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2013-06-20 15:15:24 +04:00
|
|
|
|
|
|
|
/*
|
2013-06-27 17:02:44 +04:00
|
|
|
Query Plan Footprint for single-table UPDATE.
|
|
|
|
|
|
|
|
This is similar to QPF_table_access, except that it is more restrictive.
|
|
|
|
Also, it can have UPDATE operation options, but currently there aren't any.
|
2013-06-20 15:15:24 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
class QPF_update : public QPF_node
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
virtual enum qpf_node_type get_type() { return QPF_UPDATE; }
|
|
|
|
virtual int get_select_id() { return 1; /* always root */ }
|
|
|
|
|
2013-06-20 20:58:26 +04:00
|
|
|
const char *select_type;
|
|
|
|
|
2013-06-20 15:15:24 +04:00
|
|
|
bool impossible_where;
|
|
|
|
StringBuffer<64> table_name;
|
|
|
|
|
|
|
|
enum join_type jtype;
|
|
|
|
StringBuffer<128> possible_keys_line;
|
|
|
|
StringBuffer<128> key_str;
|
|
|
|
StringBuffer<128> key_len_str;
|
|
|
|
StringBuffer<64> mrr_type;
|
|
|
|
|
|
|
|
bool using_where;
|
|
|
|
ha_rows rows;
|
|
|
|
|
|
|
|
bool using_filesort;
|
|
|
|
|
|
|
|
virtual int print_explain(QPF_query *query, select_result_sink *output,
|
|
|
|
uint8 explain_flags);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2013-06-27 17:02:44 +04:00
|
|
|
Query Plan Footprint for a single-table DELETE.
|
2013-06-20 15:15:24 +04:00
|
|
|
*/
|
|
|
|
|
|
|
|
class QPF_delete: public QPF_update
|
|
|
|
{
|
|
|
|
public:
|
2013-06-27 17:02:44 +04:00
|
|
|
/*
|
|
|
|
TRUE means we're going to call handler->delete_all_rows() and not read any
|
|
|
|
rows.
|
|
|
|
*/
|
2013-06-20 15:15:24 +04:00
|
|
|
bool deleting_all_rows;
|
|
|
|
|
|
|
|
virtual enum qpf_node_type get_type() { return QPF_DELETE; }
|
|
|
|
virtual int get_select_id() { return 1; /* always root */ }
|
|
|
|
|
|
|
|
virtual int print_explain(QPF_query *query, select_result_sink *output,
|
|
|
|
uint8 explain_flags);
|
|
|
|
};
|
|
|
|
|
2013-06-17 11:59:38 +04:00
|
|
|
|