1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

EXPLAIN FORMAT=JSON: produce used_key_parts, JSON-ish output for index_merge.

This commit is contained in:
Sergei Petrunia
2014-08-14 01:12:05 +04:00
parent a9d43d70f5
commit 041e03e251
6 changed files with 274 additions and 47 deletions

View File

@ -38,13 +38,14 @@ EXPLAIN
} }
} }
} }
# Try a basic join
create table t1 (a int, b int, filler char(32), key(a)); create table t1 (a int, b int, filler char(32), key(a));
insert into t1 insert into t1
select select
A.a + B.a* 10 + C.a * 100, a.a + b.a* 10 + c.a * 100,
A.a + B.a* 10 + C.a * 100, a.a + b.a* 10 + c.a * 100,
'filler' 'filler'
from t0 A, t0 B, t0 C; from t0 a, t0 b, t0 c;
explain format=json select * from t0,t1 where t1.a=t0.a; explain format=json select * from t0,t1 where t1.a=t0.a;
EXPLAIN EXPLAIN
{ {
@ -62,12 +63,116 @@ EXPLAIN
"access_type": "ref", "access_type": "ref",
"possible_keys": ["a"], "possible_keys": ["a"],
"key": "a", "key": "a",
"used_key_parts": "TODO",
"key_length": "5", "key_length": "5",
"used_key_parts": ["a"],
"ref": ["test.t0.a"], "ref": ["test.t0.a"],
"rows": 1, "rows": 1,
"filtered": 100 "filtered": 100
} }
} }
} }
# Try range and index_merge
create table t2 (a1 int, a2 int, b1 int, b2 int, key(a1,a2), key(b1,b2));
insert into t2 select a,a,a,a from t1;
explain format=json select * from t2 where a1<5;
EXPLAIN
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "t2",
"access_type": "range",
"possible_keys": ["a1"],
"key": "a1",
"key_length": "5",
"used_key_parts": ["a1"],
"rows": 5,
"filtered": 100,
"index_condition": "(t2.a1 < 5)"
}
}
}
explain format=json select * from t2 where a1=1 or b1=2;
EXPLAIN
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "t2",
"access_type": "index_merge",
"possible_keys": ["a1", "b1"],
"key_length": "5,5",
"index_merge": {
"sort_union": {
"range": {
"key": "a1",
"used_key_parts": ["a1"]
},
"range": {
"key": "b1",
"used_key_parts": ["b1"]
}
}
},
"rows": 2,
"filtered": 100,
"attached_condition": "((t2.a1 = 1) or (t2.b1 = 2))"
}
}
}
explain format=json select * from t2 where a1=1 or (b1=2 and b2=3);
EXPLAIN
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "t2",
"access_type": "index_merge",
"possible_keys": ["a1", "b1"],
"key_length": "5,10",
"index_merge": {
"sort_union": {
"range": {
"key": "a1",
"used_key_parts": ["a1"]
},
"range": {
"key": "b1",
"used_key_parts": ["b1", "b2"]
}
}
},
"rows": 2,
"filtered": 100,
"attached_condition": "((t2.a1 = 1) or ((t2.b1 = 2) and (t2.b2 = 3)))"
}
}
}
# Try ref access on two key components
explain format=json select * from t0,t2 where t2.b1=t0.a and t2.b2=4;
EXPLAIN
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "t0",
"access_type": "ALL",
"rows": 10,
"filtered": 100,
"attached_condition": "(t0.a is not null)"
},
"table": {
"table_name": "t2",
"access_type": "ref",
"possible_keys": ["b1"],
"key": "b1",
"key_length": "10",
"used_key_parts": ["b1", "b2"],
"ref": ["test.t0.a", "const"],
"rows": 1,
"filtered": 100
}
}
}
drop table t1;
drop table t0; drop table t0;

View File

@ -14,14 +14,29 @@ explain format=json select * from t0 where 1>2;
explain format=json select * from t0 where a<3; explain format=json select * from t0 where a<3;
--echo # Try a basic join
create table t1 (a int, b int, filler char(32), key(a)); create table t1 (a int, b int, filler char(32), key(a));
insert into t1 insert into t1
select select
A.a + B.a* 10 + C.a * 100, a.a + b.a* 10 + c.a * 100,
A.a + B.a* 10 + C.a * 100, a.a + b.a* 10 + c.a * 100,
'filler' 'filler'
from t0 A, t0 B, t0 C; from t0 a, t0 b, t0 c;
explain format=json select * from t0,t1 where t1.a=t0.a; explain format=json select * from t0,t1 where t1.a=t0.a;
--echo # Try range and index_merge
create table t2 (a1 int, a2 int, b1 int, b2 int, key(a1,a2), key(b1,b2));
insert into t2 select a,a,a,a from t1;
explain format=json select * from t2 where a1<5;
explain format=json select * from t2 where a1=1 or b1=2;
explain format=json select * from t2 where a1=1 or (b1=2 and b2=3);
--echo # Try ref access on two key components
explain format=json select * from t0,t2 where t2.b1=t0.a and t2.b2=4;
drop table t1;
drop table t0; drop table t0;

View File

@ -12105,7 +12105,7 @@ Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc)
{ {
Explain_quick_select *res; Explain_quick_select *res;
if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE))) if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE)))
res->range.set(alloc, head->key_info[index].name, max_used_key_length); res->range.set(alloc, &head->key_info[index], max_used_key_length);
return res; return res;
} }
@ -12114,7 +12114,7 @@ Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc)
{ {
Explain_quick_select *res; Explain_quick_select *res;
if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX))) if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX)))
res->range.set(alloc, head->key_info[index].name, max_used_key_length); res->range.set(alloc, &head->key_info[index], max_used_key_length);
return res; return res;
} }

View File

@ -541,7 +541,7 @@ void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
} }
void Explain_table_access::fill_key_str(String *key_str) void Explain_table_access::fill_key_str(String *key_str, bool is_json)
{ {
const CHARSET_INFO *cs= system_charset_info; const CHARSET_INFO *cs= system_charset_info;
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
@ -562,7 +562,10 @@ void Explain_table_access::fill_key_str(String *key_str)
if (quick_info) if (quick_info)
{ {
StringBuffer<64> buf2; StringBuffer<64> buf2;
quick_info->print_key(&buf2); if (is_json)
quick_info->print_extra_recursive(&buf2);
else
quick_info->print_key(&buf2);
key_str->append(buf2); key_str->append(buf2);
} }
if (type == JT_HASH_NEXT) if (type == JT_HASH_NEXT)
@ -570,6 +573,16 @@ void Explain_table_access::fill_key_str(String *key_str)
} }
/*
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
The column looks identical in tabular and json forms. In JSON, we consider
the column legacy, it is superceded by used_key_parts.
*/
void Explain_table_access::fill_key_len_str(String *key_len_str) void Explain_table_access::fill_key_len_str(String *key_len_str)
{ {
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
@ -601,6 +614,35 @@ void Explain_table_access::fill_key_len_str(String *key_len_str)
} }
void Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg)
{
set_pseudo_key(mem_root, key->name);
key_len= key_len_arg;
uint len= 0;
for (uint i= 0; i < key->usable_key_parts; i++)
{
key_parts_list.append_str(mem_root, key->key_part[i].field->field_name);
len += key->key_part[i].store_length;
if (len >= key_len_arg)
break;
}
}
void Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg)
{
if (key_name_arg)
{
size_t name_len= strlen(key_name_arg);
if ((key_name= (char*)alloc_root(root, name_len+1)))
memcpy(key_name, key_name_arg, name_len+1);
}
else
key_name= NULL;
key_len= -1;
}
double Explain_table_access::get_r_filtered() double Explain_table_access::get_r_filtered()
{ {
//psergey-todo: modify this to produce separate filtered% for both parts of //psergey-todo: modify this to produce separate filtered% for both parts of
@ -660,7 +702,7 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
/* `key` */ /* `key` */
StringBuffer<64> key_str; StringBuffer<64> key_str;
fill_key_str(&key_str); fill_key_str(&key_str, false);
if (key_str.length() > 0) if (key_str.length() > 0)
push_string(&item_list, &key_str); push_string(&item_list, &key_str);
@ -861,24 +903,51 @@ void Explain_table_access::print_explain_json(Json_writer *writer,
writer->add_str(name); writer->add_str(name);
writer->end_array(); writer->end_array();
} }
/* `key` */
StringBuffer<64> key_str;
fill_key_str(&key_str);
if (key_str.length())
writer->add_member("key").add_str(key_str);
/* `used_key_parts` */ /* `key` */
if (key_str.length()) /* For non-basic quick select, 'key' will not be present */
writer->add_member("used_key_parts").add_str("TODO"); 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` */ /* `key_length` */
StringBuffer<64> key_len_str; StringBuffer<64> key_len_str;
fill_key_len_str(&key_len_str); fill_key_len_str(&key_len_str);
if (key_len_str.length()) if (key_len_str.length())
writer->add_member("key_length").add_str(key_len_str); 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())
{
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();
}
// TODO: here, if quick select is not basic, print its nested form.
/* `ref` */ /* `ref` */
// TODO: need to print this as an array.
if (!ref_list.is_empty()) if (!ref_list.is_empty())
{ {
List_iterator_fast<char> it(ref_list); List_iterator_fast<char> it(ref_list);
@ -1046,6 +1115,35 @@ void Explain_quick_select::print_extra(String *str)
print_extra_recursive(str); 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());
List_iterator_fast<char> it(range.key_parts_list);
const char *name;
writer->add_member("used_key_parts").start_array();
while ((name= it++))
writer->add_str(name);
writer->end_array();
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) void Explain_quick_select::print_extra_recursive(String *str)
{ {

View File

@ -14,6 +14,15 @@
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
class String_list: public List<char>
{
public:
bool append_str(MEM_ROOT *mem_root, const char *str);
};
/* Data structures for ANALYZE */ /* Data structures for ANALYZE */
class Table_access_tracker class Table_access_tracker
{ {
@ -417,21 +426,16 @@ class Explain_index_use : public Sql_alloc
{ {
char *key_name; char *key_name;
uint key_len; uint key_len;
/* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */
public: public:
String_list key_parts_list;
void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_arg)
void clear()
{ {
if (key_name_arg) key_name= NULL;
{ key_len= (uint)-1;
size_t name_len= strlen(key_name_arg);
if ((key_name= (char*)alloc_root(root, name_len+1)))
memcpy(key_name, key_name_arg, name_len+1);
}
else
key_name= NULL;
key_len= key_len_arg;
} }
void set(MEM_ROOT *root, KEY *key_name, uint key_len_arg);
void set_pseudo_key(MEM_ROOT *root, const char *key_name);
inline const char *get_key_name() { return key_name; } inline const char *get_key_name() { return key_name; }
inline uint get_key_len() { return key_len; } inline uint get_key_len() { return key_len; }
@ -448,6 +452,13 @@ public:
{} {}
const int quick_type; const int quick_type;
bool is_basic()
{
return (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);
}
/* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */
Explain_index_use range; Explain_index_use range;
@ -458,19 +469,15 @@ public:
void print_extra(String *str); void print_extra(String *str);
void print_key(String *str); void print_key(String *str);
void print_key_len(String *str); void print_key_len(String *str);
private:
void print_json(Json_writer *writer);
void print_extra_recursive(String *str); void print_extra_recursive(String *str);
private:
const char *get_name_by_type(); const char *get_name_by_type();
}; };
class String_list: public List<char>
{
public:
bool append_str(MEM_ROOT *mem_root, const char *str);
};
/* /*
EXPLAIN data structure for a single JOIN_TAB. EXPLAIN data structure for a single JOIN_TAB.
*/ */
@ -565,7 +572,7 @@ public:
private: private:
void append_tag_name(String *str, enum explain_extra_tag tag); void append_tag_name(String *str, enum explain_extra_tag tag);
void fill_key_str(String *key_str); void fill_key_str(String *key_str, bool is_json);
void fill_key_len_str(String *key_len_str); void fill_key_len_str(String *key_len_str);
double get_r_filtered(); double get_r_filtered();
void tag_to_json(Json_writer *writer, enum explain_extra_tag tag); void tag_to_json(Json_writer *writer, enum explain_extra_tag tag);

View File

@ -23310,7 +23310,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab
quick_type= -1; quick_type= -1;
QUICK_SELECT_I *quick= NULL; QUICK_SELECT_I *quick= NULL;
eta->key.set(thd->mem_root, NULL, (uint)-1); eta->key.clear();
eta->quick_info= NULL; eta->quick_info= NULL;
tab->tracker= &eta->tracker; tab->tracker= &eta->tracker;
@ -23437,7 +23437,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab
if (key_info) /* 'index' or 'ref' access */ if (key_info) /* 'index' or 'ref' access */
{ {
eta->key.set(thd->mem_root, key_info->name, key_len); eta->key.set(thd->mem_root, key_info, key_len);
if (tab->ref.key_parts && tab_type != JT_FT) if (tab->ref.key_parts && tab_type != JT_FT)
{ {
@ -23458,8 +23458,10 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab
if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */
{ {
eta->hash_next_key.set(thd->mem_root, eta->hash_next_key.set(thd->mem_root,
table->key_info[tab->index].name, & table->key_info[tab->index],
table->key_info[tab->index].key_length); table->key_info[tab->index].key_length);
// psergey-todo: ^ is the above correct? are we necessarily joining on all
// columns?
} }
if (!key_info) if (!key_info)
@ -23490,7 +23492,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab
} }
if (key_name_buf.length()) if (key_name_buf.length())
eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); eta->key.set_pseudo_key(thd->mem_root, key_name_buf.c_ptr_safe());
} }
} }