mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
EXPLAIN FORMAT=JSON: support SJ-Materialization
- Switch Explain data structure from "flat" representation of SJ-Materialization into nested one. - Update functions that print tabular output to operate on the nested structure. - Add function to generate JSON output.
This commit is contained in:
parent
c46eadb2b3
commit
753718c201
6 changed files with 253 additions and 83 deletions
|
@ -167,11 +167,5 @@ EXPLAIN
|
|||
}
|
||||
}
|
||||
}
|
||||
select count(*) from t1 A, t1 B where A.b<20 and B.b<60;
|
||||
count(*)
|
||||
1200
|
||||
select count(*) from t1 A, t1 B where A.b<20 and B.b<60 and A.c > B.c;
|
||||
count(*)
|
||||
190
|
||||
drop table t1;
|
||||
drop table t0;
|
||||
|
|
|
@ -540,5 +540,57 @@ EXPLAIN
|
|||
}
|
||||
}
|
||||
}
|
||||
drop table t1;
|
||||
#
|
||||
# Semi-join Materialization
|
||||
#
|
||||
create table t2 like t1;
|
||||
insert into t2 select * from t1;
|
||||
explain format=json
|
||||
select * from t1,t2 where t1.a in ( select a from t0);
|
||||
EXPLAIN
|
||||
{
|
||||
"query_block": {
|
||||
"select_id": 1,
|
||||
"table": {
|
||||
"table_name": "t1",
|
||||
"access_type": "ALL",
|
||||
"rows": 10,
|
||||
"filtered": 100
|
||||
},
|
||||
"table": {
|
||||
"table_name": "<subquery2>",
|
||||
"access_type": "eq_ref",
|
||||
"possible_keys": ["distinct_key"],
|
||||
"key": "distinct_key",
|
||||
"key_length": "4",
|
||||
"used_key_parts": ["a"],
|
||||
"ref": ["func"],
|
||||
"rows": 1,
|
||||
"filtered": 100,
|
||||
"materialized": {
|
||||
"unique": 1,
|
||||
"query_block": {
|
||||
"select_id": 2,
|
||||
"table": {
|
||||
"table_name": "t0",
|
||||
"access_type": "ALL",
|
||||
"rows": 10,
|
||||
"filtered": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"block-nl-join": {
|
||||
"table": {
|
||||
"table_name": "t2",
|
||||
"access_type": "ALL",
|
||||
"rows": 10,
|
||||
"filtered": 100
|
||||
},
|
||||
"buffer_type": "flat",
|
||||
"join_type": "BNL"
|
||||
}
|
||||
}
|
||||
}
|
||||
drop table t1,t2;
|
||||
drop table t0;
|
||||
|
|
|
@ -105,6 +105,15 @@ tbl2 where cnt=tbl2.a;
|
|||
--echo #
|
||||
explain format=json
|
||||
select * from t1 where a in (select max(a) from t1 group by b);
|
||||
drop table t1;
|
||||
|
||||
--echo #
|
||||
--echo # Semi-join Materialization
|
||||
--echo #
|
||||
create table t2 like t1;
|
||||
insert into t2 select * from t1;
|
||||
explain format=json
|
||||
select * from t1,t2 where t1.a in ( select a from t0);
|
||||
|
||||
drop table t1,t2;
|
||||
drop table t0;
|
||||
|
||||
|
|
|
@ -602,7 +602,7 @@ void Explain_select::replace_table(uint idx, Explain_table_access *new_tab)
|
|||
}
|
||||
|
||||
|
||||
Explain_select::~Explain_select()
|
||||
Explain_basic_join::~Explain_basic_join()
|
||||
{
|
||||
if (join_tabs)
|
||||
{
|
||||
|
@ -663,34 +663,77 @@ int Explain_select::print_explain(Explain_query *query,
|
|||
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::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(select_id);
|
||||
if (message)
|
||||
{
|
||||
writer->add_member("query_block").start_object();
|
||||
writer->add_member("select_id").add_ll(select_id);
|
||||
|
||||
writer->add_member("table").start_object();
|
||||
writer->add_member("message").add_str(message);
|
||||
writer->end_object();
|
||||
|
||||
print_explain_json_for_children(query, writer, is_analyze);
|
||||
writer->end_object();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint i=0; i< n_join_tabs; i++)
|
||||
{
|
||||
// psergey-todo: Need to honor SJM nests...
|
||||
join_tabs[i]->print_explain_json(query, writer, is_analyze);
|
||||
}
|
||||
/*
|
||||
TODO: how does this approach allow to print ORDER BY members?
|
||||
Explain_basic_join does not have ORDER/GROUP.
|
||||
A: factor out join tab printing loop into a common func.
|
||||
*/
|
||||
Explain_basic_join::print_explain_json(query, writer, is_analyze);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Explain_basic_join::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(select_id);
|
||||
for (uint i=0; i< n_join_tabs; i++)
|
||||
{
|
||||
join_tabs[i]->print_explain_json(query, writer, is_analyze);
|
||||
}
|
||||
print_explain_json_for_children(query, writer, is_analyze);
|
||||
writer->end_object();
|
||||
}
|
||||
|
@ -837,17 +880,11 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
|
|||
List<Item> item_list;
|
||||
Item *item_null= new Item_null();
|
||||
|
||||
if (sjm_nest_select_id)
|
||||
select_id= sjm_nest_select_id;
|
||||
|
||||
/* `id` column */
|
||||
item_list.push_back(new Item_int((int32) select_id));
|
||||
|
||||
/* `select_type` column */
|
||||
if (sjm_nest_select_id)
|
||||
push_str(&item_list, "MATERIALIZED");
|
||||
else
|
||||
push_str(&item_list, select_type);
|
||||
push_str(&item_list, select_type);
|
||||
|
||||
/* `table` column */
|
||||
push_string(&item_list, &table_name);
|
||||
|
@ -1242,6 +1279,14 @@ void Explain_table_access::print_explain_json(Explain_query *query,
|
|||
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();
|
||||
}
|
||||
|
||||
writer->end_object();
|
||||
}
|
||||
|
|
|
@ -94,7 +94,8 @@ public:
|
|||
enum explain_node_type
|
||||
{
|
||||
EXPLAIN_UNION,
|
||||
EXPLAIN_SELECT,
|
||||
EXPLAIN_SELECT,
|
||||
EXPLAIN_BASIC_JOIN,
|
||||
EXPLAIN_UPDATE,
|
||||
EXPLAIN_DELETE,
|
||||
EXPLAIN_INSERT
|
||||
|
@ -144,6 +145,49 @@ public:
|
|||
class Explain_table_access;
|
||||
|
||||
|
||||
/*
|
||||
A basic join. This is only used for SJ-Materialization nests.
|
||||
|
||||
Basic join doesn't have ORDER/GROUP/DISTINCT operations. It also cannot be
|
||||
degenerate.
|
||||
|
||||
It has its own select_id.
|
||||
*/
|
||||
class Explain_basic_join : public Explain_node
|
||||
{
|
||||
public:
|
||||
enum explain_node_type get_type() { return EXPLAIN_BASIC_JOIN; }
|
||||
|
||||
Explain_basic_join() : join_tabs(NULL) {}
|
||||
~Explain_basic_join();
|
||||
|
||||
bool add_table(Explain_table_access *tab)
|
||||
{
|
||||
if (!join_tabs)
|
||||
{
|
||||
join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) *
|
||||
MAX_TABLES, MYF(0));
|
||||
n_join_tabs= 0;
|
||||
}
|
||||
join_tabs[n_join_tabs++]= tab;
|
||||
return false;
|
||||
}
|
||||
|
||||
int get_select_id() { return select_id; }
|
||||
|
||||
int select_id;
|
||||
|
||||
int print_explain(Explain_query *query, select_result_sink *output,
|
||||
uint8 explain_flags, bool is_analyze);
|
||||
void print_explain_json(Explain_query *query, Json_writer *writer,
|
||||
bool is_analyze);
|
||||
|
||||
/* A flat array of Explain structs for tables. */
|
||||
Explain_table_access** join_tabs;
|
||||
uint n_join_tabs;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
EXPLAIN structure for a SELECT.
|
||||
|
||||
|
@ -159,30 +203,16 @@ class Explain_table_access;
|
|||
a way get node's children.
|
||||
*/
|
||||
|
||||
class Explain_select : public Explain_node
|
||||
class Explain_select : public Explain_basic_join
|
||||
{
|
||||
public:
|
||||
enum explain_node_type get_type() { return EXPLAIN_SELECT; }
|
||||
|
||||
Explain_select() :
|
||||
message(NULL), join_tabs(NULL),
|
||||
message(NULL),
|
||||
using_temporary(false), using_filesort(false)
|
||||
{}
|
||||
|
||||
~Explain_select();
|
||||
|
||||
bool add_table(Explain_table_access *tab)
|
||||
{
|
||||
if (!join_tabs)
|
||||
{
|
||||
join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) *
|
||||
MAX_TABLES, MYF(0));
|
||||
n_join_tabs= 0;
|
||||
}
|
||||
join_tabs[n_join_tabs++]= tab;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
This is used to save the results of "late" test_if_skip_sort_order() calls
|
||||
that are made from JOIN::exec
|
||||
|
@ -190,24 +220,14 @@ public:
|
|||
void replace_table(uint idx, Explain_table_access *new_tab);
|
||||
|
||||
public:
|
||||
int select_id;
|
||||
const char *select_type;
|
||||
|
||||
int get_select_id() { return select_id; }
|
||||
|
||||
/*
|
||||
If message != NULL, this is a degenerate join plan, and all subsequent
|
||||
members have no info
|
||||
*/
|
||||
const char *message;
|
||||
|
||||
/*
|
||||
A flat array of Explain structs for tables. The order is "just like EXPLAIN
|
||||
would print them".
|
||||
*/
|
||||
Explain_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;
|
||||
|
@ -521,19 +541,15 @@ public:
|
|||
non_merged_sjm_number(0),
|
||||
where_cond(NULL),
|
||||
cache_cond(NULL),
|
||||
pushed_index_cond(NULL)
|
||||
pushed_index_cond(NULL),
|
||||
sjm_nest(NULL)
|
||||
{}
|
||||
~Explain_table_access() { delete sjm_nest; }
|
||||
|
||||
void push_extra(enum explain_extra_tag extra_tag);
|
||||
|
||||
/* Internals */
|
||||
public:
|
||||
/*
|
||||
0 means this tab is not inside SJM nest and should use Explain_select's id
|
||||
other value means the tab is inside an SJM nest.
|
||||
*/
|
||||
int sjm_nest_select_id;
|
||||
|
||||
/* id and 'select_type' are cared-of by the parent Explain_select */
|
||||
StringBuffer<32> table_name;
|
||||
|
||||
|
@ -610,6 +626,8 @@ public:
|
|||
|
||||
Item *pushed_index_cond;
|
||||
|
||||
Explain_basic_join *sjm_nest;
|
||||
|
||||
int print_explain(select_result_sink *output, uint8 explain_flags,
|
||||
bool is_analyze,
|
||||
uint select_id, const char *select_type,
|
||||
|
|
|
@ -8186,6 +8186,41 @@ JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind,
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
Enumerate JOIN_TABs in "EXPLAIN order". This order
|
||||
- const tabs are included
|
||||
- we enumerate "optimization tabs".
|
||||
-
|
||||
*/
|
||||
|
||||
JOIN_TAB *first_explain_order_tab(JOIN* join)
|
||||
{
|
||||
JOIN_TAB* tab;
|
||||
tab= join->table_access_tabs;
|
||||
return (tab->bush_children) ? tab->bush_children->start : tab;
|
||||
}
|
||||
|
||||
|
||||
JOIN_TAB *next_explain_order_tab(JOIN* join, JOIN_TAB* tab)
|
||||
{
|
||||
/* If we're inside SJM nest and have reached its end, get out */
|
||||
if (tab->last_leaf_in_bush)
|
||||
return tab->bush_root_tab;
|
||||
|
||||
/* Move to next tab in the array we're traversing */
|
||||
tab++;
|
||||
|
||||
if (tab == join->table_access_tabs + join->top_join_tab_count)
|
||||
return NULL; /* Outside SJM nest and reached EOF */
|
||||
|
||||
if (tab->bush_children)
|
||||
return tab->bush_children->start;
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
|
||||
JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls)
|
||||
{
|
||||
JOIN_TAB *tab= join->join_tab;
|
||||
|
@ -23277,16 +23312,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab
|
|||
tab->tracker= &eta->tracker;
|
||||
tab->jbuf_tracker= &eta->jbuf_tracker;
|
||||
|
||||
/* id */
|
||||
if (tab->bush_root_tab)
|
||||
{
|
||||
JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start;
|
||||
eta->sjm_nest_select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier();
|
||||
}
|
||||
else
|
||||
eta->sjm_nest_select_id= 0;
|
||||
|
||||
/* select_type is kept in Explain_select */
|
||||
/* id and select_type are kept in Explain_select */
|
||||
|
||||
/* table */
|
||||
if (table->derived_select_number)
|
||||
|
@ -23719,12 +23745,22 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table,
|
|||
if (select_lex->master_unit()->derived)
|
||||
xpl_sel->connection_type= Explain_node::EXPLAIN_NODE_DERIVED;
|
||||
|
||||
JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
|
||||
if (need_tmp_table)
|
||||
xpl_sel->using_temporary= true;
|
||||
|
||||
for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
|
||||
tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab))
|
||||
if (need_order)
|
||||
xpl_sel->using_filesort= true;
|
||||
|
||||
JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
|
||||
JOIN_TAB* prev_bush_root_tab= NULL;
|
||||
|
||||
Explain_basic_join *cur_parent= xpl_sel;
|
||||
|
||||
for (JOIN_TAB *tab= first_explain_order_tab(join); tab;
|
||||
tab= next_explain_order_tab(join, tab))
|
||||
//for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
|
||||
// tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab))
|
||||
{
|
||||
|
||||
JOIN_TAB *saved_join_tab= NULL;
|
||||
TABLE *table=tab->table;
|
||||
|
||||
|
@ -23735,6 +23771,7 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table,
|
|||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (join->table_access_tabs == join->join_tab &&
|
||||
tab == (first_top_tab + join->const_tables) && pre_sort_join_tab)
|
||||
{
|
||||
|
@ -23743,21 +23780,36 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table,
|
|||
}
|
||||
|
||||
Explain_table_access *eta= new (output->mem_root) Explain_table_access;
|
||||
xpl_sel->add_table(eta);
|
||||
|
||||
if (tab->bush_root_tab != prev_bush_root_tab)
|
||||
{
|
||||
if (tab->bush_root_tab)
|
||||
{
|
||||
/*
|
||||
We've entered an SJ-Materialization nest. Create an object for it.
|
||||
*/
|
||||
cur_parent= new Explain_basic_join;
|
||||
|
||||
JOIN_TAB *first_child= tab->bush_root_tab->bush_children->start;
|
||||
cur_parent->select_id=
|
||||
first_child->emb_sj_nest->sj_subq_pred->get_identifier();
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
We've just left an SJ-Materialization nest. We are at the join tab
|
||||
that 'embeds the nest'
|
||||
*/
|
||||
DBUG_ASSERT(tab->bush_children);
|
||||
eta->sjm_nest= cur_parent;
|
||||
cur_parent= xpl_sel;
|
||||
}
|
||||
}
|
||||
prev_bush_root_tab= tab->bush_root_tab;
|
||||
|
||||
cur_parent->add_table(eta);
|
||||
tab->save_explain_data(eta, used_tables, distinct, first_top_tab);
|
||||
|
||||
if (need_tmp_table)
|
||||
{
|
||||
need_tmp_table=0;
|
||||
xpl_sel->using_temporary= true;
|
||||
}
|
||||
if (need_order)
|
||||
{
|
||||
need_order=0;
|
||||
xpl_sel->using_filesort= true;
|
||||
}
|
||||
|
||||
if (saved_join_tab)
|
||||
tab= saved_join_tab;
|
||||
|
||||
|
|
Loading…
Reference in a new issue