diff options
author | Oleksandr Byelkin <sanja@mariadb.com> | 2015-03-25 18:27:10 +0100 |
---|---|---|
committer | Sergei Petrunia <psergey@askmonty.org> | 2015-06-30 23:07:14 +0300 |
commit | c6aee27b73232fc6ba3e8b55adbb0abba4f0171b (patch) | |
tree | c996b7f96ff4d063b2112e8b863825a2cff906b2 | |
parent | 498a264d19f041c36d71e41a32c16ac40a014a3e (diff) | |
download | mariadb-git-c6aee27b73232fc6ba3e8b55adbb0abba4f0171b.tar.gz |
MDEV-7811: EXPLAIN/ANALYZE FORMAT=JSON should show subquery cache
-rw-r--r-- | mysql-test/r/subselect_cache.result | 234 | ||||
-rw-r--r-- | mysql-test/t/subselect_cache.test | 10 | ||||
-rw-r--r-- | sql/item.cc | 13 | ||||
-rw-r--r-- | sql/item.h | 2 | ||||
-rw-r--r-- | sql/item_subselect.cc | 19 | ||||
-rw-r--r-- | sql/item_subselect.h | 5 | ||||
-rw-r--r-- | sql/sql_explain.cc | 30 | ||||
-rw-r--r-- | sql/sql_explain.h | 16 | ||||
-rw-r--r-- | sql/sql_expression_cache.cc | 15 | ||||
-rw-r--r-- | sql/sql_expression_cache.h | 48 | ||||
-rw-r--r-- | sql/sql_lex.cc | 18 |
11 files changed, 401 insertions, 9 deletions
diff --git a/mysql-test/r/subselect_cache.result b/mysql-test/r/subselect_cache.result index e5a2fe12526..2cf1961ec5b 100644 --- a/mysql-test/r/subselect_cache.result +++ b/mysql-test/r/subselect_cache.result @@ -39,6 +39,240 @@ Handler_read_prev 0 Handler_read_rnd 0 Handler_read_rnd_deleted 0 Handler_read_rnd_next 31 +analyze format=json +select a, (select d from t2 where b=c) from t1; +ANALYZE +{ + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "t1", + "access_type": "ALL", + "r_loops": 1, + "rows": 10, + "r_rows": 10, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "r_loops": 4, + "r_total_time_ms": "REPLACED", + "expression_cache": { + "state": "ENABLED", + "r_hit": 6, + "r_miss": 4, + "r_loops": 10, + "r_hit_ratio": 60 + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 4, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 18.75, + "attached_condition": "(t1.b = t2.c)" + } + } + } + ] + } +} +analyze format=json +select a, (select d from t2 where b=c), (select d from t2 where b=c union select 1 order by 1 limit 1) from t1; +ANALYZE +{ + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "t1", + "access_type": "ALL", + "r_loops": 1, + "rows": 10, + "r_rows": 10, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "union_result": { + "table_name": "<union3,4>", + "access_type": "ALL", + "r_loops": 4, + "r_rows": 1, + "expression_cache": { + "state": "ENABLED", + "r_hit": 6, + "r_miss": 4, + "r_loops": 10, + "r_hit_ratio": 60 + }, + "query_specifications": [ + { + "query_block": { + "select_id": 3, + "r_loops": 4, + "r_total_time_ms": "REPLACED", + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 4, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 18.75, + "attached_condition": "(t1.b = t2.c)" + } + } + }, + { + "query_block": { + "select_id": 4, + "table": { + "message": "No tables used" + } + } + } + ] + } + } + }, + { + "query_block": { + "select_id": 2, + "r_loops": 4, + "r_total_time_ms": "REPLACED", + "expression_cache": { + "state": "ENABLED", + "r_hit": 6, + "r_miss": 4, + "r_loops": 10, + "r_hit_ratio": 60 + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 4, + "rows": 4, + "r_rows": 4, + "r_total_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 18.75, + "attached_condition": "(t1.b = t2.c)" + } + } + } + ] + } +} +explain format=json +select a, (select d from t2 where b=c) from t1; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "select_id": 2, + "expression_cache": { + "state": "UNINITIALYZED" + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 4, + "filtered": 100, + "attached_condition": "(t1.b = t2.c)" + } + } + } + ] + } +} +explain format=json +select a, (select d from t2 where b=c), (select d from t2 where b=c union select 1 order by 1 limit 1) from t1; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "union_result": { + "table_name": "<union3,4>", + "access_type": "ALL", + "expression_cache": { + "state": "UNINITIALYZED" + }, + "query_specifications": [ + { + "query_block": { + "select_id": 3, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 4, + "filtered": 100, + "attached_condition": "(t1.b = t2.c)" + } + } + }, + { + "query_block": { + "select_id": 4, + "table": { + "message": "No tables used" + } + } + } + ] + } + } + }, + { + "query_block": { + "select_id": 2, + "expression_cache": { + "state": "UNINITIALYZED" + }, + "table": { + "table_name": "t2", + "access_type": "ALL", + "rows": 4, + "filtered": 100, + "attached_condition": "(t1.b = t2.c)" + } + } + } + ] + } +} set optimizer_switch='subquery_cache=off'; flush status; select a, (select d from t2 where b=c) from t1; diff --git a/mysql-test/t/subselect_cache.test b/mysql-test/t/subselect_cache.test index 1276e546030..21247541fb6 100644 --- a/mysql-test/t/subselect_cache.test +++ b/mysql-test/t/subselect_cache.test @@ -24,6 +24,16 @@ select a, (select d from t2 where b=c) from t1; show status like "subquery_cache%"; show status like '%Handler_read%'; +--replace_regex /"r_total_time_ms": [0-9]*[.]?[0-9]*/"r_total_time_ms": "REPLACED"/ +analyze format=json +select a, (select d from t2 where b=c) from t1; +--replace_regex /"r_total_time_ms": [0-9]*[.]?[0-9]*/"r_total_time_ms": "REPLACED"/ +analyze format=json +select a, (select d from t2 where b=c), (select d from t2 where b=c union select 1 order by 1 limit 1) from t1; +explain format=json +select a, (select d from t2 where b=c) from t1; +explain format=json +select a, (select d from t2 where b=c), (select d from t2 where b=c union select 1 order by 1 limit 1) from t1; set optimizer_switch='subquery_cache=off'; flush status; diff --git a/sql/item.cc b/sql/item.cc index b4de9732b59..14e14968278 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7554,6 +7554,19 @@ bool Item_cache_wrapper::set_cache(THD *thd) DBUG_RETURN(expr_cache == NULL); } +Expression_cache_stat* Item_cache_wrapper::set_stat(MEM_ROOT *mem_root) +{ + if (expr_cache) + { + Expression_cache_stat* stat= + new(mem_root) Expression_cache_stat(expr_cache); + if (stat) + ((Expression_cache_tmptable *)expr_cache)->set_stat(stat); + return stat; + } + return NULL; +} + /** Check if the current values of the parameters are in the expression cache diff --git a/sql/item.h b/sql/item.h index 825435908c3..cecc0f24514 100644 --- a/sql/item.h +++ b/sql/item.h @@ -3804,6 +3804,7 @@ public: class Item_cache; class Expression_cache; +class Expression_cache_stat; /** The objects of this class can store its values in an expression cache. @@ -3838,6 +3839,7 @@ public: enum Type real_type() const { return orig_item->type(); } bool set_cache(THD *thd); + Expression_cache_stat* set_stat(MEM_ROOT *mem_root); bool fix_fields(THD *thd, Item **it); void cleanup(); diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index fa398dc6e21..0b90063f9cb 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1201,7 +1201,10 @@ Item* Item_singlerow_subselect::expr_cache_insert_transformer(uchar *thd_arg) if (expr_cache_is_needed(thd) && (expr_cache= set_expr_cache(thd))) + { + set_expr_cache_stat(thd); DBUG_RETURN(expr_cache); + } DBUG_RETURN(this); } @@ -1497,7 +1500,10 @@ Item* Item_exists_subselect::expr_cache_insert_transformer(uchar *thd_arg) if (substype() == EXISTS_SUBS && expr_cache_is_needed(thd) && (expr_cache= set_expr_cache(thd))) + { + set_expr_cache_stat(thd); DBUG_RETURN(expr_cache); + } DBUG_RETURN(this); } @@ -6556,3 +6562,16 @@ void subselect_table_scan_engine::cleanup() { } +void Item_subselect::set_expr_cache_stat(THD *thd) +{ + if(!expr_cache) + return; + + Explain_query *qw= thd->lex->explain; + DBUG_ASSERT(qw); + Explain_node *node= qw->get_node(unit->first_select()->select_number); + if (!node) + return; + DBUG_ASSERT(expr_cache->type() == Item::EXPR_CACHE_ITEM); + node->cache_stat= ((Item_cache_wrapper *)expr_cache)->set_stat(qw->mem_root); +} diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 6628a1bf86b..cad66cd3442 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -244,7 +244,10 @@ public: bool limit_index_condition_pushdown_processor(uchar *opt_arg) { return TRUE; - } + } + + void set_expr_cache_stat(THD *thd); + friend class select_result_interceptor; friend class Item_in_optimizer; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 21959423084..210700a2804 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -23,6 +23,7 @@ #include "sql_select.h" #include "my_json_writer.h" #include "opt_range.h" +#include "sql_expression_cache.h" const char * STR_DELETING_ALL_ROWS= "Deleting all rows"; const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE"; @@ -543,6 +544,7 @@ void Explain_union::print_explain_json(Explain_query *query, writer->add_null(); } + print_explain_json_cache(writer, is_analyze); writer->add_member("query_specifications").start_array(); for (int i= 0; i < (int) union_members.elements(); i++) @@ -640,6 +642,30 @@ void Explain_node::print_explain_json_for_children(Explain_query *query, } +void Explain_node::print_explain_json_cache(Json_writer *writer, + bool is_analyze) +{ + if (cache_stat) + { + cache_stat->flush_stat(); + writer->add_member("expression_cache").start_object(); + writer->add_member("state"). + add_str(Expression_cache_stat::state_str[cache_stat->state]); + if (is_analyze) + { + writer->add_member("r_hit").add_ll(cache_stat->hit); + writer->add_member("r_miss").add_ll(cache_stat->miss); + writer->add_member("r_loops").add_ll(cache_stat->hit + + cache_stat->miss); + writer->add_member("r_hit_ratio").add_ll(((double)cache_stat->hit)/ + ((double)(cache_stat->hit + + cache_stat->miss)) * 100.0); + } + writer->end_object(); + } +} + + void Explain_select::replace_table(uint idx, Explain_table_access *new_tab) { delete join_tabs[idx]; @@ -757,17 +783,19 @@ void Explain_select::print_explain_json(Explain_query *query, { writer->add_member("query_block").start_object(); writer->add_member("select_id").add_ll(select_id); - + if (is_analyze && time_tracker.get_loops()) { writer->add_member("r_loops").add_ll(time_tracker.get_loops()); writer->add_member("r_total_time_ms").add_double(time_tracker.get_time_ms()); } + if (exec_const_cond) { writer->add_member("const_condition"); write_item(writer, exec_const_cond); } + print_explain_json_cache(writer, is_analyze); Filesort_tracker *first_table_sort= NULL; bool first_table_sort_used= false; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index dd2b5783b41..427553c1088 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -84,9 +84,10 @@ class Explain_query; class Explain_node : public Sql_alloc { public: - Explain_node(MEM_ROOT *root) : - connection_type(EXPLAIN_NODE_OTHER), - children(root) + Explain_node(MEM_ROOT *root) : + cache_stat(NULL), + connection_type(EXPLAIN_NODE_OTHER), + children(root) {} /* A type specifying what kind of node this is */ enum explain_node_type @@ -106,10 +107,14 @@ public: EXPLAIN_NODE_NON_MERGED_SJ /* aka JTBM semi-join */ }; - virtual enum explain_node_type get_type()= 0; virtual int get_select_id()= 0; + /** + expression cache statistics + */ + Expression_cache_stat* cache_stat; + /* How this node is connected to its parent. (NOTE: EXPLAIN_NODE_NON_MERGED_SJ is set very late currently) @@ -135,6 +140,7 @@ public: uint8 explain_flags, bool is_analyze); void print_explain_json_for_children(Explain_query *query, Json_writer *writer, bool is_analyze); + void print_explain_json_cache(Json_writer *writer, bool is_analyze); virtual ~Explain_node(){} }; @@ -221,7 +227,7 @@ public: members have no info */ const char *message; - + /* Expensive constant condition */ Item *exec_const_cond; diff --git a/sql/sql_expression_cache.cc b/sql/sql_expression_cache.cc index 824d21eea20..bda0aaafebe 100644 --- a/sql/sql_expression_cache.cc +++ b/sql/sql_expression_cache.cc @@ -43,7 +43,7 @@ ulong subquery_cache_miss, subquery_cache_hit; Expression_cache_tmptable::Expression_cache_tmptable(THD *thd, List<Item> &dependants, Item *value) - :cache_table(NULL), table_thd(thd), items(dependants), val(value), + :cache_table(NULL), table_thd(thd), stat(NULL), items(dependants), val(value), hit(0), miss(0), inited (0) { DBUG_ENTER("Expression_cache_tmptable::Expression_cache_tmptable"); @@ -61,6 +61,9 @@ void Expression_cache_tmptable::disable_cache() cache_table->file->ha_index_end(); free_tmp_table(table_thd, cache_table); cache_table= NULL; + flush_stat(); + if (stat) + stat->cache= NULL; } @@ -164,6 +167,7 @@ void Expression_cache_tmptable::init() goto error; } + flush_stat(); DBUG_VOID_RETURN; error: @@ -180,6 +184,11 @@ Expression_cache_tmptable::~Expression_cache_tmptable() if (cache_table) disable_cache(); + else + { + flush_stat(); + stat= NULL; + } } @@ -323,3 +332,7 @@ void Expression_cache_tmptable::print(String *str, enum_query_type query_type) } str->append('>'); } + + +const char *Expression_cache_stat::state_str[3]= +{"UNINITIALYZED", "DISABLED", "ENABLED"}; diff --git a/sql/sql_expression_cache.h b/sql/sql_expression_cache.h index 48a8e33a787..33c67f8eaca 100644 --- a/sql/sql_expression_cache.h +++ b/sql/sql_expression_cache.h @@ -19,6 +19,7 @@ #include "sql_select.h" + /** Interface for expression cache @@ -62,6 +63,11 @@ public: Initialize this cache */ virtual void init()= 0; + + /** + Save this object's statistics into Expression_cache_stat object + */ + virtual void flush_stat()= 0; }; struct st_table_ref; @@ -69,6 +75,30 @@ struct st_join_table; class Item_field; +class Expression_cache_stat :public Sql_alloc +{ +public: + enum expr_cache_state {UNINITED, STOPPED, OK}; + Expression_cache_stat(Expression_cache *c) : + cache(c), hit(0), miss(0), state(UNINITED) + {} + + Expression_cache *cache; + ulong hit, miss; + enum expr_cache_state state; + + static const char* state_str[3]; + void set(ulong h, ulong m, enum expr_cache_state s) + {hit= h; miss= m; state= s;} + + void flush_stat() + { + if (cache) + cache->flush_stat(); + } +}; + + /** Implementation of expression cache over a temporary table */ @@ -85,6 +115,20 @@ public: bool is_inited() { return inited; }; void init(); + void set_stat(Expression_cache_stat *st) + { + stat= st; + flush_stat(); + } + virtual void flush_stat() + { + if (stat) + stat->set(hit, miss, (inited ? (cache_table ? + Expression_cache_stat::OK : + Expression_cache_stat::STOPPED) : + Expression_cache_stat::UNINITED)); + } + private: void disable_cache(); @@ -94,6 +138,8 @@ private: TABLE *cache_table; /* Thread handle for the temporary table */ THD *table_thd; + /* EXPALIN/ANALYZE statistics */ + Expression_cache_stat *stat; /* TABLE_REF for index lookup */ struct st_table_ref ref; /* Cached result */ @@ -103,7 +149,7 @@ private: /* Value Item example */ Item *val; /* hit/miss counters */ - uint hit, miss; + ulong hit, miss; /* Set on if the object has been succesfully initialized with init() */ bool inited; }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index ac2eda2d0de..6c835c4293f 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3505,6 +3505,8 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) bool empty_union_result= true; bool is_correlated_unit= false; + bool first= true; + bool union_plan_saved= false; /* If the subquery is a UNION, optimize all the subqueries in the UNION. If there is no UNION, then the loop will execute once for the subquery. @@ -3512,6 +3514,17 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) { JOIN *inner_join= sl->join; + if (first) + first= false; + else + { + if (!union_plan_saved) + { + union_plan_saved= true; + if (un->save_union_explain(un->thd->lex->explain)) + return true; /* Failure */ + } + } if (!inner_join) continue; SELECT_LEX *save_select= un->thd->lex->current_select; @@ -4365,10 +4378,15 @@ void LEX::restore_set_statement_var() int st_select_lex_unit::save_union_explain(Explain_query *output) { SELECT_LEX *first= first_select(); + + if (output->get_union(first->select_number)) + return 0; /* Already added */ + Explain_union *eu= new (output->mem_root) Explain_union(output->mem_root, thd->lex->analyze_stmt); + if (derived) eu->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; /* |