summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleksandr Byelkin <sanja@mariadb.com>2015-03-25 18:27:10 +0100
committerSergei Petrunia <psergey@askmonty.org>2015-06-30 23:07:14 +0300
commitc6aee27b73232fc6ba3e8b55adbb0abba4f0171b (patch)
treec996b7f96ff4d063b2112e8b863825a2cff906b2
parent498a264d19f041c36d71e41a32c16ac40a014a3e (diff)
downloadmariadb-git-c6aee27b73232fc6ba3e8b55adbb0abba4f0171b.tar.gz
MDEV-7811: EXPLAIN/ANALYZE FORMAT=JSON should show subquery cache
-rw-r--r--mysql-test/r/subselect_cache.result234
-rw-r--r--mysql-test/t/subselect_cache.test10
-rw-r--r--sql/item.cc13
-rw-r--r--sql/item.h2
-rw-r--r--sql/item_subselect.cc19
-rw-r--r--sql/item_subselect.h5
-rw-r--r--sql/sql_explain.cc30
-rw-r--r--sql/sql_explain.h16
-rw-r--r--sql/sql_expression_cache.cc15
-rw-r--r--sql/sql_expression_cache.h48
-rw-r--r--sql/sql_lex.cc18
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;
/*