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));
insert into t1
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'
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
{
@ -62,12 +63,116 @@ EXPLAIN
"access_type": "ref",
"possible_keys": ["a"],
"key": "a",
"used_key_parts": "TODO",
"key_length": "5",
"used_key_parts": ["a"],
"ref": ["test.t0.a"],
"rows": 1,
"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;

View File

@ -14,14 +14,29 @@ explain format=json select * from t0 where 1>2;
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));
insert into t1
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'
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;
--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;

View File

@ -12105,7 +12105,7 @@ Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc)
{
Explain_quick_select *res;
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;
}
@ -12114,7 +12114,7 @@ Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc)
{
Explain_quick_select *res;
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;
}

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;
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
@ -562,6 +562,9 @@ void Explain_table_access::fill_key_str(String *key_str)
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);
}
@ -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)
{
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()
{
//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` */
StringBuffer<64> key_str;
fill_key_str(&key_str);
fill_key_str(&key_str, false);
if (key_str.length() > 0)
push_string(&item_list, &key_str);
@ -861,15 +903,16 @@ void Explain_table_access::print_explain_json(Json_writer *writer,
writer->add_str(name);
writer->end_array();
}
/* `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);
fill_key_str(&key_str, true);
if (key_str.length())
writer->add_member("key").add_str(key_str);
/* `used_key_parts` */
if (key_str.length())
writer->add_member("used_key_parts").add_str("TODO");
}
/* `key_length` */
StringBuffer<64> key_len_str;
@ -877,8 +920,34 @@ void Explain_table_access::print_explain_json(Json_writer *writer,
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())
{
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` */
// TODO: need to print this as an array.
if (!ref_list.is_empty())
{
List_iterator_fast<char> it(ref_list);
@ -1046,6 +1115,35 @@ void Explain_quick_select::print_extra(String *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)
{

View File

@ -14,6 +14,15 @@
along with this program; if not, write to the Free Software
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 */
class Table_access_tracker
{
@ -417,21 +426,16 @@ class Explain_index_use : public Sql_alloc
{
char *key_name;
uint key_len;
/* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */
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)
{
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;
key_len= (uint)-1;
}
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 uint get_key_len() { return key_len; }
@ -449,6 +453,13 @@ public:
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 */
Explain_index_use range;
@ -458,19 +469,15 @@ public:
void print_extra(String *str);
void print_key(String *str);
void print_key_len(String *str);
private:
void print_json(Json_writer *writer);
void print_extra_recursive(String *str);
private:
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.
*/
@ -565,7 +572,7 @@ public:
private:
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);
double get_r_filtered();
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_SELECT_I *quick= NULL;
eta->key.set(thd->mem_root, NULL, (uint)-1);
eta->key.clear();
eta->quick_info= NULL;
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 */
{
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)
{
@ -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 */
{
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);
// psergey-todo: ^ is the above correct? are we necessarily joining on all
// columns?
}
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())
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());
}
}