From 041e03e251e783d51ca86e53112e3b87bd2da146 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Thu, 14 Aug 2014 01:12:05 +0400 Subject: [PATCH] EXPLAIN FORMAT=JSON: produce used_key_parts, JSON-ish output for index_merge. --- mysql-test/r/explain_json.result | 113 +++++++++++++++++++++++++++- mysql-test/t/explain_json.test | 21 +++++- sql/opt_range.cc | 4 +- sql/sql_explain.cc | 124 +++++++++++++++++++++++++++---- sql/sql_explain.h | 49 ++++++------ sql/sql_select.cc | 10 ++- 6 files changed, 274 insertions(+), 47 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index 8ef2b9f2e8c..2b2a4bf8fc9 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -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; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test index aedff8d78cb..887ef66bff9 100644 --- a/mysql-test/t/explain_json.test +++ b/mysql-test/t/explain_json.test @@ -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; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index fc2aa75e604..88d8b0551cb 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -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; } diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index f7fc3b106ce..9571e5a2886 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -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,7 +562,10 @@ void Explain_table_access::fill_key_str(String *key_str) if (quick_info) { 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); } 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) { 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,24 +903,51 @@ void Explain_table_access::print_explain_json(Json_writer *writer, writer->add_str(name); 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` */ - if (key_str.length()) - writer->add_member("used_key_parts").add_str("TODO"); - + /* `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, true); + if (key_str.length()) + writer->add_member("key").add_str(key_str); + } + /* `key_length` */ StringBuffer<64> key_len_str; fill_key_len_str(&key_len_str); 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 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 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 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 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) { diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 26aa5753900..2500b3d3c05 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -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 +{ +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: - - void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_arg) + String_list key_parts_list; + + 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_name= NULL; + 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; } @@ -448,6 +452,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 -{ -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); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 213a7eae647..8ef3f64016e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -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()); } }