From 18fec5128b6fd9712f63e306f03f16833f2599b2 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 08:20:14 +0400 Subject: [PATCH 01/64] EXPLAIN DELETE for MariaDB - Backported the code to 10.0-base - Removed incorrect assert --- mysql-test/r/show_explain.result | 14 +- mysql-test/t/show_explain.test | 6 +- sql/my_apc.h | 2 + sql/sql_delete.cc | 221 ++++++++++++++++++++++++++++--- sql/sql_lex.cc | 31 +++-- sql/sql_lex.h | 48 +++++++ sql/sql_parse.cc | 80 ++++++++--- sql/sql_select.cc | 158 ++++++++++++++++++---- sql/sql_select.h | 22 +++ sql/sql_show.cc | 4 +- sql/sql_string.h | 32 +++++ sql/sql_update.cc | 5 +- sql/sql_yacc.yy | 10 +- 13 files changed, 550 insertions(+), 83 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index da132a102e2..8baf4d40e7c 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -192,16 +192,24 @@ ERROR HY000: Target is not running an EXPLAINable command drop table t2; set debug_dbug=@old_debug; # -# Attempt SHOW EXPLAIN for a DELETE +# Attempt SHOW EXPLAIN for a DELETE (UPD: now works) # create table t2 as select a as a, a as dummy from t0 limit 2; set @show_explain_probe_select_id=2; set debug_dbug='+d,show_explain_probe_join_exec_start'; delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +Warnings: +Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; set debug_dbug=@old_debug; # diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 57d86ece4f4..d87bf2f14d4 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -249,7 +249,7 @@ drop table t2; set debug_dbug=@old_debug; --echo # ---echo # Attempt SHOW EXPLAIN for a DELETE +--echo # Attempt SHOW EXPLAIN for a DELETE (UPD: now works) --echo # create table t2 as select a as a, a as dummy from t0 limit 2; set @show_explain_probe_select_id=2; @@ -257,10 +257,10 @@ set debug_dbug='+d,show_explain_probe_join_exec_start'; send delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; diff --git a/sql/my_apc.h b/sql/my_apc.h index 7f19809c082..ee94595e78f 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -64,6 +64,8 @@ public: { return test(apc_calls); } + + inline bool is_enabled() { return enabled; } /* Functor class for calls you can schedule */ class Apc_call diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 4bcb62ef764..d7a612f3d56 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -40,6 +40,129 @@ #include "records.h" // init_read_record, #include "sql_derived.h" // mysql_handle_list_of_derived // end_read_record + + +/* + @brief + Print query plan of a single-table DELETE command + + @detail + This function is used by EXPLAIN DELETE and by SHOW EXPLAIN when it is + invoked on a running DELETE statement. +*/ + +int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (deleting_all_rows || impossible_where) + { + const char *msg= deleting_all_rows? "Deleting all rows": "Impossible where"; + if (print_explain_message_line(output, explain_flags, 1/*select number*/, + "SIMPLE", msg)) + { + return 1; + } + *printed_anything= true; + return 0; + } + + select_lex->set_explain_type(FALSE); + /* + Print an EXPLAIN line. We dont have join, so we can't directly use + JOIN::print_explain. + We do have a SELECT_LEX (TODO but how is it useful? it has select_type.. + and that's it?) + */ + + enum join_type jtype; + if (select && select->quick) + { + int quick_type= select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + jtype= JT_INDEX_MERGE; + else + jtype= JT_RANGE; + } + else + { + if (index == MAX_KEY) + jtype= JT_ALL; + else + jtype= JT_NEXT; + } + + StringBuffer<128> possible_keys_line; + make_possible_keys_line(table, possible_keys, &possible_keys_line); + + const char *key_name; + const char *key_len; + + StringBuffer<128> key_str; + StringBuffer<128> key_len_str; + StringBuffer<128> extra_str; + + /* Calculate key_len */ + if (select && select->quick) + { + select->quick->add_keys_and_lengths(&key_str, &key_len_str); + key_name= key_str.c_ptr(); + key_len= key_len_str.c_ptr(); + } + else + { + key_name= (index == MAX_KEY)? NULL : table->key_info[index].name; + key_len= NULL; + } + + if (select && select->cond) + extra_str.append(STRING_WITH_LEN("Using where")); + if (select && select->quick && + select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) + { + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &extra_str); + } + + if (using_filesort) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using filesort")); + } + + /* + Single-table DELETE commands do not do "Using temporary". + "Using index condition" is also not possible (which is an unjustified limitation) + */ + + print_explain_row(output, explain_flags, + 1, /* id */ + select_lex->type, + table->pos_in_table_list->alias, + // partitions, + jtype, + possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, + key_name, + key_len, + NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ + select ? select->records : table_rows, + extra_str.c_ptr()); + + *printed_anything= true; + + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (unit->print_explain(output, explain_flags, printed_anything)) + return 1; + } + return 0; +} + + /** Implement DELETE SQL word. @@ -61,12 +184,16 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, bool const_cond_result; ha_rows deleted= 0; bool reverse= FALSE; + bool err= true; ORDER *order= (ORDER *) ((order_list && order_list->elements) ? order_list->first : NULL); - uint usable_index= MAX_KEY; SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; + + Delete_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -90,6 +217,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } thd_proc_info(thd, "init"); table->map=1; + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; if (mysql_prepare_delete(thd, table_list, &conds)) DBUG_RETURN(TRUE); @@ -163,6 +292,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); ha_rows const maybe_deleted= table->file->stats.records; DBUG_PRINT("debug", ("Trying to use delete_all_rows()")); + + query_plan.set_delete_all_rows(maybe_deleted); + if (thd->lex->describe) + goto exit_without_my_ok; + if (!(error=table->file->ha_delete_all_rows())) { /* @@ -187,7 +321,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, Item::cond_result result; conds= remove_eq_conds(thd, conds, &result); if (result == Item::COND_FALSE) // Impossible where + { limit= 0; + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -195,6 +334,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { free_underlaid_joins(thd, select_lex); // No matching record + //psergey-explain-todo: No-partitions used EXPLAIN here.. my_ok(thd, 0); DBUG_RETURN(0); } @@ -211,6 +351,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); if ((select && select->check_quick(thd, safe_update, limit)) || !limit) { + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -243,26 +387,46 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (order) { - uint length= 0; - SORT_FIELD *sortorder; - ha_rows examined_rows; - ha_rows found_rows; - table->update_const_key_parts(conds); order= simple_remove_const(order, conds); - bool need_sort; if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered") - need_sort= FALSE; - usable_index= MAX_KEY; + query_plan.using_filesort= FALSE; + query_plan.index= MAX_KEY; } else - usable_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); - if (need_sort) + query_plan.index= get_index_for_order(order, table, select, limit, + &query_plan.using_filesort, + &reverse); + } + + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; + thd->lex->delete_plan= &query_plan; + + /* + Ok, we have generated a query plan for the DELETE. + - if we're running EXPLAIN DELETE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto exit_without_my_ok; + + thd->apc_target.enable(); + DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", + dbug_serve_apcs(thd, 1);); + + if (query_plan.using_filesort) + { + ha_rows examined_rows; + ha_rows found_rows; + uint length= 0; + SORT_FIELD *sortorder; + { - DBUG_ASSERT(usable_index == MAX_KEY); + DBUG_ASSERT(query_plan.index == MAX_KEY); table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE), MYF(MY_FAE | MY_ZEROFILL | MY_THREAD_SPECIFIC)); @@ -276,6 +440,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, &thd->lex->select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } thd->examined_row_count+= examined_rows; @@ -294,19 +459,21 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } - if (usable_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) { delete select; free_underlaid_joins(thd, select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } } else - init_read_record_idx(&info, thd, table, 1, usable_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); init_ftfuncs(thd, select_lex, 1); thd_proc_info(thd, "updating"); @@ -398,6 +565,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_NORMAL); + thd->apc_target.disable(); cleanup: /* Invalidate the table in the query cache if something changed. This must @@ -458,6 +626,29 @@ cleanup: DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); + + /* Special exits */ +exit_without_my_ok: + thd->lex->delete_plan= &query_plan; + + select_send *result; + bool printed_anything; + if (!(result= new select_send())) + return 1; /* purecov: inspected */ + List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ + result->prepare(dummy, &thd->lex->unit); + thd->send_explain_fields(result); + int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + + if (err2) + result->abort_result_set(); + else + result->send_eof(); + + delete select; + free_underlaid_joins(thd, select_lex); + //table->set_keyread(false); + DBUG_RETURN((err || thd->is_error() || thd->killed) ? 1 : 0); } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b8ce3b6720e..74e4b3e1162 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,6 +448,7 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; + lex->delete_plan= NULL; lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); @@ -2557,6 +2558,7 @@ LEX::LEX() INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); + delete_plan= NULL; } @@ -4166,12 +4168,17 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } - -int print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, - uint8 options, - const char *message); +int LEX::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (delete_plan) + { + delete_plan->print_explain(output, explain_flags, printed_anything); + return 0; + } + int res= unit.print_explain(output, explain_flags, printed_anything); + return res; +} int st_select_lex::print_explain(select_result_sink *output, @@ -4235,8 +4242,9 @@ int st_select_lex::print_explain(select_result_sink *output, DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); msg= "Query plan already deleted"; } - res= print_explain_message_line(output, this, TRUE /* on_the_fly */, - 0, msg); + set_explain_type(TRUE/* on_the_fly */); + res= print_explain_message_line(output, 0/*options*/, select_number, type, + msg); } err: return res; @@ -4256,9 +4264,10 @@ int st_select_lex_unit::print_explain(select_result_sink *output, EXPLAIN state" error. */ const char *msg="Query plan already deleted"; - res= print_explain_message_line(output, first, TRUE /* on_the_fly */, - 0, msg); - return 0; + first->set_explain_type(TRUE/* on_the_fly */); + res= print_explain_message_line(output, 0/*options*/, first->select_number, + first->type, msg); + return res; } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index be7cdd8ca1d..af35ccb01b0 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2344,6 +2344,48 @@ protected: LEX *m_lex; }; +class Delete_plan; +class SQL_SELECT; + +/* Query plan of a single-table DELETE */ +class Delete_plan +{ + bool deleting_all_rows; + bool impossible_where; +public: + + TABLE *table; + SQL_SELECT *select; + uint index; + ha_rows table_rows; /* Use if select==NULL */ + bool using_filesort; + key_map possible_keys; + + /* + Top-level select_lex. Most of its fields are not used, we need it only to + get to the subqueries. + */ + SELECT_LEX *select_lex; + + /* Construction functions */ + Delete_plan() : + deleting_all_rows(false), impossible_where(false), using_filesort(false) {} + + /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ + void set_delete_all_rows(ha_rows rows_arg) + { + deleting_all_rows= true; + table_rows= rows_arg; + } + + /* Set this plan to be a plan to do nothing because of impossible WHRE*/ + void set_impossible_where() { impossible_where= true; } + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); +}; + + /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2355,6 +2397,9 @@ struct LEX: public Query_tables_list /* list of all SELECT_LEX */ SELECT_LEX *all_selects_list; + /* For single-table DELETE: its query plan */ + Delete_plan *delete_plan; + char *length,*dec,*change; LEX_STRING name; char *help_arg; @@ -2769,6 +2814,9 @@ struct LEX: public Query_tables_list } return FALSE; } + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0abb249d97b..d5fd5c1b32d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3212,7 +3212,8 @@ end_with_restore_list: { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *del_result; + bool explain= test(lex->describe); + select_result *result; if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3227,37 +3228,72 @@ end_with_restore_list: if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; - MYSQL_MULTI_DELETE_START(thd->query()); + if (!explain) + { + MYSQL_MULTI_DELETE_START(thd->query()); + } + if ((res= mysql_multi_delete_prepare(thd))) { - MYSQL_MULTI_DELETE_DONE(1, 0); + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(1, 0); + } goto error; } - if (!thd->is_fatal_error && - (del_result= new multi_delete(aux_tables, lex->table_count))) + if (!thd->is_fatal_error) { - res= mysql_select(thd, &select_lex->ref_pointer_array, - select_lex->get_table_list(), - select_lex->with_wild, - select_lex->item_list, - select_lex->where, - 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, - (ORDER *)NULL, - (select_lex->options | thd->variables.option_bits | - SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | - OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, - del_result, unit, select_lex); - res|= thd->is_error(); - MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); - if (res) - del_result->abort_result_set(); - delete del_result; + if (explain) + { + result= new select_send(); + if (thd->send_explain_fields(result)) + { + delete result; + result= NULL; + } + select_lex->set_explain_type(FALSE); + } + else + result= new multi_delete(aux_tables, lex->table_count); + + if (result) + { + res= mysql_select(thd, &select_lex->ref_pointer_array, + select_lex->get_table_list(), + select_lex->with_wild, + select_lex->item_list, + select_lex->where, + 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, + (ORDER *)NULL, + (select_lex->options | thd->variables.option_bits | + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | + OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, + result, unit, select_lex); + res|= thd->is_error(); + + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); + } + + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ + else + { + if (explain) + result->send_eof(); + } + delete result; + } } else { res= TRUE; // Error - MYSQL_MULTI_DELETE_DONE(1, 0); + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(1, 0); + } } break; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 41c0b07bc49..230355c550e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -21594,23 +21594,20 @@ void JOIN::clear() /* Print an EXPLAIN line with all NULLs and given message in the 'Extra' column */ + int print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, uint8 options, + uint select_number, + const char *select_type, const char *message) { const CHARSET_INFO *cs= system_charset_info; Item *item_null= new Item_null(); List item_list; - if (on_the_fly) - select_lex->set_explain_type(on_the_fly); - - item_list.push_back(new Item_int((int32) - select_lex->select_number)); - item_list.push_back(new Item_string(select_lex->type, - strlen(select_lex->type), cs)); + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); for (uint i=0 ; i < 7; i++) item_list.push_back(item_null); if (options & DESCRIBE_PARTITIONS) @@ -21626,6 +21623,104 @@ int print_explain_message_line(select_result_sink *result, } +/* + Make a comma-separated list of possible_keys names and add it into the string +*/ + +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line) +{ + if (!possible_keys.is_clear_all()) + { + uint j; + for (j=0 ; j < table->s->keys ; j++) + { + if (possible_keys.is_set(j)) + { + if (line->length()) + line->append(','); + line->append(table->key_info[j].name, + strlen(table->key_info[j].name), + system_charset_info); + } + } + } +} + +/* + Print an EXPLAIN output row, based on information provided in the parameters + + @note + Parameters that may have NULL value in EXPLAIN output, should be passed + (char*)NULL. + + @return + 0 - OK + 1 - OOM Error +*/ + +int print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + //const char *partitions, (todo) + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows rows, + const char *extra) +{ + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + List item_list; + Item *item; + + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + item_list.push_back(new Item_string(table_name, + strlen(table_name), cs)); + if (options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); // psergey-todo: produce proper value + + const char *jtype_str= join_type_str[jtype]; + item_list.push_back(new Item_string(jtype_str, + strlen(jtype_str), cs)); + + item= possible_keys? new Item_string(possible_keys, strlen(possible_keys), + cs) : item_null; + item_list.push_back(item); + + /* 'index */ + item= index ? new Item_string(index, strlen(index), cs) : item_null; + item_list.push_back(item); + + /* 'key_len */ + item= key_len ? new Item_string(key_len, strlen(key_len), cs) : item_null; + item_list.push_back(item); + + /* 'ref' */ + item= ref ? new Item_string(ref, strlen(ref), cs) : item_null; + item_list.push_back(item); + + /* 'rows' */ + item_list.push_back(new Item_int(rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + /* 'filtered' */ + if (options & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* 'Extra' */ + item_list.push_back(new Item_string(extra, strlen(extra), cs)); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, SELECT_LEX *select_lex, uint8 explain_flags) { @@ -21705,6 +21800,26 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, } +/* + Append MRR information from quick select to the given string +*/ + +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) +{ + char mrr_str_buf[128]; + mrr_str_buf[0]=0; + int len; + handler *h= quick->head->file; + len= h->multi_range_read_explain_info(quick->mrr_flags, mrr_str_buf, + sizeof(mrr_str_buf)); + if (len > 0) + { + res->append(STRING_WITH_LEN("; ")); + res->append(mrr_str_buf, len); + } +} + + /** EXPLAIN handling. @@ -21746,8 +21861,12 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, */ if (message) { - if (print_explain_message_line(result, join->select_lex, on_the_fly, - explain_flags, message)) + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); + + if (print_explain_message_line(result, explain_flags, + join->select_lex->select_number, + join->select_lex->type, message)) error= 1; } @@ -21762,7 +21881,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, join->select_lex->master_unit()->derived->is_materialized_derived()) { table_map used_tables=0; - //if (!join->select_lex->type) + if (on_the_fly) join->select_lex->set_explain_type(on_the_fly); @@ -22171,19 +22290,8 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, */ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) { - char mrr_str_buf[128]; - mrr_str_buf[0]=0; - int len; - uint mrr_flags= - ((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags; - len= table->file->multi_range_read_explain_info(mrr_flags, - mrr_str_buf, - sizeof(mrr_str_buf)); - if (len > 0) - { - extra.append(STRING_WITH_LEN("; ")); - extra.append(mrr_str_buf, len); - } + explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), + &extra); } if (need_tmp_table) diff --git a/sql/sql_select.h b/sql/sql_select.h index 638de926d75..61a72357d84 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1804,6 +1804,28 @@ void eliminate_tables(JOIN *join); /* Index Condition Pushdown entry point function */ void push_index_cond(JOIN_TAB *tab, uint keyno); +/* EXPLAIN-related utility functions */ +int print_explain_message_line(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *message); +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res); +int print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + //const char *partitions, (todo) + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows rows, + const char *extra); +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); + /**************************************************************************** Temporary table support for SQL Runtime ***************************************************************************/ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 095ad409a94..f3bb14b6e9f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2337,8 +2337,8 @@ void Show_explain_request::call_in_target_thread() DBUG_ASSERT(current_thd == target_thd); set_current_thd(request_thd); - if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, - &printed_anything)) + if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/, + &printed_anything)) { failed_to_produce= TRUE; } diff --git a/sql/sql_string.h b/sql/sql_string.h index 58cda343dac..e08b7a2581f 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -500,6 +500,38 @@ public: } }; + +// The following class is a backport from MySQL 5.6: +/** + String class wrapper with a preallocated buffer of size buff_sz + + This class allows to replace sequences of: + char buff[12345]; + String str(buff, sizeof(buff)); + str.length(0); + with a simple equivalent declaration: + StringBuffer<12345> str; +*/ + +template +class StringBuffer : public String +{ + char buff[buff_sz]; + +public: + StringBuffer() : String(buff, buff_sz, &my_charset_bin) { length(0); } + explicit StringBuffer(const CHARSET_INFO *cs) : String(buff, buff_sz, cs) + { + length(0); + } + StringBuffer(const char *str, size_t length, const CHARSET_INFO *cs) + : String(buff, buff_sz, cs) + { + set(str, length, cs); + } +}; + + static inline bool check_if_only_end_space(CHARSET_INFO *cs, const char *str, const char *end) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 28b9fe7eacd..84adfc2c3f5 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1394,7 +1394,10 @@ bool mysql_multi_update(THD *thd, thd->abort_on_warning= test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); - +/* + psergey-explain-todo: + This is the place where EXPLAIN should be handled. +*/ List total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9664aad1e19..7bac9c8ded7 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11881,13 +11881,21 @@ describe: opt_describe_column {} | describe_command opt_extended_describe { Lex->describe|= DESCRIBE_NORMAL; } - select + explanable_command { LEX *lex=Lex; lex->select_lex.options|= SELECT_DESCRIBE; } ; +explanable_command: + select + | insert + | replace + | update + | delete + ; + describe_command: DESC | DESCRIBE From b38b44cfe1c62b4c2211aec7c3f1407f14a2aa21 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 08:24:48 +0400 Subject: [PATCH 02/64] EXPLAIN DELETE for MariaDB - Include the testcases in the backport. --- mysql-test/r/explain_non_select.result | 53 +++++++++++++++++++ mysql-test/r/show_explain_non_select.result | 24 +++++++++ mysql-test/t/explain_non_select.test | 47 +++++++++++++++++ mysql-test/t/show_explain_non_select.test | 57 +++++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 mysql-test/r/explain_non_select.result create mode 100644 mysql-test/r/show_explain_non_select.result create mode 100644 mysql-test/t/explain_non_select.test create mode 100644 mysql-test/t/show_explain_non_select.test diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result new file mode 100644 index 00000000000..aa592f0b198 --- /dev/null +++ b/mysql-test/r/explain_non_select.result @@ -0,0 +1,53 @@ +drop table if exists t0; +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +# +# Tests for single-table DELETE +# +explain select * from t0 where a=3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +explain delete from t0 where a=3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +# DELETE without WHERE is a special case: +explain delete from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Deleting all rows +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +# This should use an index, possible_keys=NULL because there is no WHERE +explain delete from t1 order by a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL a NULL NULL 512 +# This should use range, possible_keys={a,b} +explain delete from t1 where a<20 and b < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,b a 5 NULL 1 Using where +# This should use ALL + filesort +explain delete from t1 order by a+1 limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 Using filesort +# This should use range + using filesort +explain delete from t1 where a<20 order by b limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where; Using filesort +# Try some subqueries: +explain delete from t1 where a < (select max(a) from t0); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 range a a 5 NULL 1 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +explain delete from t1 where a < (select max(a) from t0 where a < t1.b); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 8 Using where +# +# Tests for multi-table DELETE +# +explain delete t1 from t0, t1 where t0.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +drop table t0, t1; diff --git a/mysql-test/r/show_explain_non_select.result b/mysql-test/r/show_explain_non_select.result new file mode 100644 index 00000000000..5358981e6f2 --- /dev/null +++ b/mysql-test/r/show_explain_non_select.result @@ -0,0 +1,24 @@ +drop table if exists t0, t1; +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +set debug_dbug='d,show_explain_probe_delete_exec_start'; +delete from t1 where a<10 and b+1>1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +Warnings: +Note 1003 delete from t1 where a<10 and b+1>1000 +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using where +Warnings: +Note 1003 delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000 +drop table t0,t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test new file mode 100644 index 00000000000..bc3c2bb6c16 --- /dev/null +++ b/mysql-test/t/explain_non_select.test @@ -0,0 +1,47 @@ +# +# MariaDB tests for EXPLAIN UPDATE/DELETE. +# +--disable_warnings +drop table if exists t0; +--enable_warnings + +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +--echo # +--echo # Tests for single-table DELETE +--echo # + +explain select * from t0 where a=3; +explain delete from t0 where a=3; + +--echo # DELETE without WHERE is a special case: +explain delete from t0; + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +--echo # This should use an index, possible_keys=NULL because there is no WHERE +explain delete from t1 order by a limit 2; + +--echo # This should use range, possible_keys={a,b} +explain delete from t1 where a<20 and b < 10; + +--echo # This should use ALL + filesort +explain delete from t1 order by a+1 limit 2; + +--echo # This should use range + using filesort +explain delete from t1 where a<20 order by b limit 2; + +--echo # Try some subqueries: +explain delete from t1 where a < (select max(a) from t0); +explain delete from t1 where a < (select max(a) from t0 where a < t1.b); + +--echo # +--echo # Tests for multi-table DELETE +--echo # +explain delete t1 from t0, t1 where t0.a = t1.a; + +drop table t0, t1; diff --git a/mysql-test/t/show_explain_non_select.test b/mysql-test/t/show_explain_non_select.test new file mode 100644 index 00000000000..da788779626 --- /dev/null +++ b/mysql-test/t/show_explain_non_select.test @@ -0,0 +1,57 @@ +# +# SHOW EXPLAIN tests for non-select subqueries +# +#--source include/have_debug.inc +#--source include/have_innodb.inc +#--source include/not_embedded.inc + +--disable_warnings +drop table if exists t0, t1; +--enable_warnings + +# +# Setup two threads and their ids +# +let $thr1=`select connection_id()`; +connect (con2, localhost, root,,); +connection con2; +let $thr2=`select connection_id()`; +connection default; + +# +# Create tables +# +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; + +# +# Test SHOW EXPLAIN for single-table DELETEs +# +connection con2; +set debug_dbug='d,show_explain_probe_delete_exec_start'; +send delete from t1 where a<10 and b+1>1000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +send delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; + + +drop table t0,t1; From d2995031d9214206689660069024525808c8a683 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 14:37:08 +0400 Subject: [PATCH 03/64] SHOW EXPLAIN for MariaDB - Support [SHOW] EXPLAIN UPDATE (needs code cleanup). --- mysql-test/r/explain_non_select.result | 53 +++++++- mysql-test/r/show_explain.result | 18 ++- mysql-test/r/show_explain_non_select.result | 24 +++- mysql-test/t/explain_non_select.test | 39 ++++++ mysql-test/t/show_explain.test | 4 +- mysql-test/t/show_explain_non_select.test | 31 ++++- sql/sql_delete.cc | 26 +++- sql/sql_lex.cc | 8 +- sql/sql_lex.h | 41 ++++-- sql/sql_select.cc | 3 +- sql/sql_update.cc | 134 ++++++++++++++++---- 11 files changed, 318 insertions(+), 63 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index aa592f0b198..1c8e444c4b9 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -38,7 +38,7 @@ id select_type table type possible_keys key key_len ref rows Extra explain delete from t1 where a < (select max(a) from t0); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 range a a 5 NULL 1 Using where -2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 SUBQUERY t0 ALL NULL NULL NULL NULL 8 explain delete from t1 where a < (select max(a) from t0 where a < t1.b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where @@ -51,3 +51,54 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where 1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index drop table t0, t1; +# ################################################################### +# ## EXPLAIN UPDATE tests +# ################################################################### +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +explain update t0 set a=3 where a=4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +explain update t1 set a=a+1 where 3>4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +explain update t1 set a=a+1 where a=3 and a=4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +# This should use an index, possible_keys=NULL because there is no WHERE +explain update t1 set a=a+1 order by a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +# This should use range, possible_keys={a,b} +explain update t1 set filler='fooo' where a<20 and b < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,b a 5 NULL 1 Using where +# This should use ALL + filesort +explain update t1 set filler='fooo' order by a+1 limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +# This should use range + using filesort +explain update t1 set filler='fooo' where a<20 order by b limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +# Try some subqueries: +explain update t1 set filler='fooo' where a < (select max(a) from t0); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 range a a 5 NULL 1 Using where +2 SUBQUERY t0 ALL NULL NULL NULL NULL 8 +explain update t1 set filler='fooo' where a < (select max(a) from t0 where a < t1.b); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 8 Using where +# +# Tests for multi-table UPDATE +# +explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +drop table t0, t1; diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 8baf4d40e7c..1152baaf835 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -186,9 +186,17 @@ set @show_explain_probe_select_id=2; set debug_dbug='+d,show_explain_probe_join_exec_start'; update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; set debug_dbug=@old_debug; # @@ -207,7 +215,7 @@ Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; @@ -228,13 +236,13 @@ Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2 show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2 a SUBQ diff --git a/mysql-test/r/show_explain_non_select.result b/mysql-test/r/show_explain_non_select.result index 5358981e6f2..0bd1e959405 100644 --- a/mysql-test/r/show_explain_non_select.result +++ b/mysql-test/r/show_explain_non_select.result @@ -1,19 +1,27 @@ drop table if exists t0, t1; +SET @old_debug= @@session.debug; +set debug_sync='RESET'; create table t0 (a int) engine=myisam; insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); create table t1 (a int, b int, filler char(100), key(a), key(b)); insert into t1 select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' from t0 A, t0 B, t0 C; -set debug_dbug='d,show_explain_probe_delete_exec_start'; +# +# Test SHOW EXPLAIN for single-table DELETE +# +set debug_dbug='+d,show_explain_probe_delete_exec_start'; delete from t1 where a<10 and b+1>1000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 1 Using where Warnings: Note 1003 delete from t1 where a<10 and b+1>1000 +# +# Test SHOW EXPLAIN for multi-table DELETE +# set @show_explain_probe_select_id=1; -set debug_dbug='d,show_explain_probe_do_select'; +set debug_dbug='+d,show_explain_probe_do_select'; delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -21,4 +29,16 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ref a a 5 test.t0.a 4 Using where Warnings: Note 1003 delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000 +# +# Test SHOW EXPLAIN for single-table UPDATE +# +set debug_dbug='+d,show_explain_probe_update_exec_start'; +update t1 set filler='filler-data-2' where a<10 and b+1>1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +Warnings: +Note 1003 update t1 set filler='filler-data-2' where a<10 and b+1>1000 drop table t0,t1; +set debug_dbug=@old_debug; +set debug_sync='RESET'; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index bc3c2bb6c16..66432a8e4a5 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -43,5 +43,44 @@ explain delete from t1 where a < (select max(a) from t0 where a < t1.b); --echo # Tests for multi-table DELETE --echo # explain delete t1 from t0, t1 where t0.a = t1.a; +drop table t0, t1; + +--echo # ################################################################### +--echo # ## EXPLAIN UPDATE tests +--echo # ################################################################### +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +explain update t0 set a=3 where a=4; + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +explain update t1 set a=a+1 where 3>4; +explain update t1 set a=a+1 where a=3 and a=4; + +--echo # This should use an index, possible_keys=NULL because there is no WHERE +explain update t1 set a=a+1 order by a limit 2; + +--echo # This should use range, possible_keys={a,b} +explain update t1 set filler='fooo' where a<20 and b < 10; + +--echo # This should use ALL + filesort +explain update t1 set filler='fooo' order by a+1 limit 2; + +--echo # This should use range + using filesort +explain update t1 set filler='fooo' where a<20 order by b limit 2; + +--echo # Try some subqueries: +explain update t1 set filler='fooo' where a < (select max(a) from t0); +explain update t1 set filler='fooo' where a < (select max(a) from t0 where a < t1.b); + +--echo # +--echo # Tests for multi-table UPDATE +--echo # +explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; + drop table t0, t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index d87bf2f14d4..0694160b151 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -238,10 +238,10 @@ set debug_dbug='+d,show_explain_probe_join_exec_start'; send update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; diff --git a/mysql-test/t/show_explain_non_select.test b/mysql-test/t/show_explain_non_select.test index da788779626..f71affd8a6e 100644 --- a/mysql-test/t/show_explain_non_select.test +++ b/mysql-test/t/show_explain_non_select.test @@ -9,6 +9,9 @@ drop table if exists t0, t1; --enable_warnings +SET @old_debug= @@session.debug; +set debug_sync='RESET'; + # # Setup two threads and their ids # @@ -31,11 +34,11 @@ from t0 A, t0 B, t0 C; let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; -# -# Test SHOW EXPLAIN for single-table DELETEs -# +--echo # +--echo # Test SHOW EXPLAIN for single-table DELETE +--echo # connection con2; -set debug_dbug='d,show_explain_probe_delete_exec_start'; +set debug_dbug='+d,show_explain_probe_delete_exec_start'; send delete from t1 where a<10 and b+1>1000; connection default; @@ -44,8 +47,11 @@ evalp show explain for $thr2; connection con2; reap; +--echo # +--echo # Test SHOW EXPLAIN for multi-table DELETE +--echo # set @show_explain_probe_select_id=1; -set debug_dbug='d,show_explain_probe_do_select'; +set debug_dbug='+d,show_explain_probe_do_select'; send delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; connection default; --source include/wait_condition.inc @@ -53,5 +59,20 @@ evalp show explain for $thr2; connection con2; reap; +--echo # +--echo # Test SHOW EXPLAIN for single-table UPDATE +--echo # +connection con2; +set debug_dbug='+d,show_explain_probe_update_exec_start'; +send update t1 set filler='filler-data-2' where a<10 and b+1>1000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; drop table t0,t1; + +set debug_dbug=@old_debug; +set debug_sync='RESET'; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index d7a612f3d56..df659871a64 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -54,9 +54,27 @@ int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { - if (deleting_all_rows || impossible_where) + if (deleting_all_rows) { - const char *msg= deleting_all_rows? "Deleting all rows": "Impossible where"; + const char *msg= "Deleting all rows"; + if (print_explain_message_line(output, explain_flags, 1/*select number*/, + "SIMPLE", msg)) + { + return 1; + } + *printed_anything= true; + return 0; + } + return Update_plan::print_explain(output, explain_flags, printed_anything); +} + + +int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (impossible_where) + { + const char *msg= "Impossible where"; if (print_explain_message_line(output, explain_flags, 1/*select number*/, "SIMPLE", msg)) { @@ -404,7 +422,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query_plan.select= select; query_plan.possible_keys= table->quick_keys; query_plan.table_rows= table->file->stats.records; - thd->lex->delete_plan= &query_plan; + thd->lex->upd_del_plan= &query_plan; /* Ok, we have generated a query plan for the DELETE. @@ -629,7 +647,7 @@ cleanup: /* Special exits */ exit_without_my_ok: - thd->lex->delete_plan= &query_plan; + thd->lex->upd_del_plan= &query_plan; select_send *result; bool printed_anything; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 74e4b3e1162..714ab373b17 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,7 +448,7 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; - lex->delete_plan= NULL; + lex->upd_del_plan= NULL; lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); @@ -2558,7 +2558,7 @@ LEX::LEX() INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); - delete_plan= NULL; + upd_del_plan= NULL; } @@ -4171,9 +4171,9 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { - if (delete_plan) + if (upd_del_plan) { - delete_plan->print_explain(output, explain_flags, printed_anything); + upd_del_plan->print_explain(output, explain_flags, printed_anything); return 0; } int res= unit.print_explain(output, explain_flags, printed_anything); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index af35ccb01b0..588ad172a3c 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2347,29 +2347,48 @@ protected: class Delete_plan; class SQL_SELECT; -/* Query plan of a single-table DELETE */ -class Delete_plan +/* + Query plan of a single-table UPDATE. + (This is actually a plan for single-table DELETE also) +*/ +class Update_plan { - bool deleting_all_rows; +protected: bool impossible_where; public: - TABLE *table; SQL_SELECT *select; uint index; ha_rows table_rows; /* Use if select==NULL */ - bool using_filesort; - key_map possible_keys; - /* Top-level select_lex. Most of its fields are not used, we need it only to get to the subqueries. */ SELECT_LEX *select_lex; + + key_map possible_keys; + bool using_filesort; + + /* Set this plan to be a plan to do nothing because of impossible WHRE*/ + void set_impossible_where() { impossible_where= true; } + + virtual int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); + virtual ~Update_plan() {} + + Update_plan() : impossible_where(false), using_filesort(false) {} +}; + + +/* Query plan of a single-table DELETE */ +class Delete_plan : public Update_plan +{ + bool deleting_all_rows; +public: /* Construction functions */ Delete_plan() : - deleting_all_rows(false), impossible_where(false), using_filesort(false) {} + deleting_all_rows(false) {} /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ void set_delete_all_rows(ha_rows rows_arg) @@ -2377,10 +2396,6 @@ public: deleting_all_rows= true; table_rows= rows_arg; } - - /* Set this plan to be a plan to do nothing because of impossible WHRE*/ - void set_impossible_where() { impossible_where= true; } - int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); }; @@ -2398,7 +2413,7 @@ struct LEX: public Query_tables_list SELECT_LEX *all_selects_list; /* For single-table DELETE: its query plan */ - Delete_plan *delete_plan; + Update_plan *upd_del_plan; char *length,*dec,*change; LEX_STRING name; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 230355c550e..d9177b9240e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10756,7 +10756,8 @@ void JOIN::cleanup(bool full) DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); - have_query_plan= QEP_DELETED; + if (full) + have_query_plan= QEP_DELETED; //psergey: this is a problem! if (table) { diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 84adfc2c3f5..11cb493da97 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -260,7 +260,7 @@ int mysql_update(THD *thd, bool can_compare_record; int res; int error, loc_error; - uint used_index, dup_key_found; + uint dup_key_found; bool need_sort= TRUE; bool reverse= FALSE; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -270,12 +270,15 @@ int mysql_update(THD *thd, ha_rows updated, found; key_map old_covering_keys; TABLE *table; - SQL_SELECT *select; + SQL_SELECT *select= NULL; READ_RECORD info; SELECT_LEX *select_lex= &thd->lex->select_lex; ulonglong id; List all_fields; killed_state killed_status= NOT_KILLED; + Update_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_update"); if (open_tables(thd, &table_list, &table_count, 0)) @@ -314,6 +317,8 @@ int mysql_update(THD *thd, table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Force privilege re-checking for views after they have been opened. */ want_privilege= (table_list->view ? UPDATE_ACL : @@ -370,7 +375,12 @@ int mysql_update(THD *thd, Item::cond_result cond_value; conds= remove_eq_conds(thd, conds, &cond_value); if (cond_value == Item::COND_FALSE) + { limit= 0; // Impossible WHERE + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } /* @@ -400,6 +410,10 @@ int mysql_update(THD *thd, if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -438,16 +452,16 @@ int mysql_update(THD *thd, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered"): Ok to use with key field UPDATE need_sort= FALSE; - used_index= MAX_KEY; + query_plan.index= MAX_KEY; used_key_is_modified= FALSE; } else { - used_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + query_plan.index= get_index_for_order(order, table, select, limit, + &need_sort, &reverse); if (select && select->quick) { - DBUG_ASSERT(need_sort || used_index == select->quick->index); + DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); used_key_is_modified= (!select->quick->unique_key_range() && select->quick->is_keys_used(table->write_set)); } @@ -455,14 +469,37 @@ int mysql_update(THD *thd, { if (need_sort) { // Assign table scan index to check below for modified key fields: - used_index= table->file->key_used_on_scan; + query_plan.index= table->file->key_used_on_scan; } - if (used_index != MAX_KEY) + if (query_plan.index != MAX_KEY) { // Check if we are modifying a key that we are used to search with: - used_key_is_modified= is_key_used(table, used_index, table->write_set); + used_key_is_modified= is_key_used(table, query_plan.index, table->write_set); } } } + + /* + Query optimization is finished at this point. + - Save the decisions in the query plan + - if we're running EXPLAIN UPDATE, get out + */ + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; + thd->lex->upd_del_plan= &query_plan; + + /* + Ok, we have generated a query plan for the UPDATE. + - if we're running EXPLAIN UPDATE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto exit_without_my_ok; + + thd->apc_target.enable(); + DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", + dbug_serve_apcs(thd, 1);); + if (used_key_is_modified || order || partition_key_modified(table, table->write_set)) @@ -476,8 +513,8 @@ int mysql_update(THD *thd, DBUG_ASSERT(table->read_set == &table->def_read_set); DBUG_ASSERT(table->write_set == &table->def_write_set); - if (used_index < MAX_KEY && old_covering_keys.is_set(used_index)) - table->add_read_columns_used_by_index(used_index); + if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index)) + table->add_read_columns_used_by_index(query_plan.index); else table->use_all_columns(); @@ -534,22 +571,22 @@ int mysql_update(THD *thd, /* When we get here, we have one of the following options: - A. used_index == MAX_KEY + A. query_plan.index == MAX_KEY This means we should use full table scan, and start it with init_read_record call - B. used_index != MAX_KEY + B. query_plan.index != MAX_KEY B.1 quick select is used, start the scan with init_read_record B.2 quick select is not used, this is full index scan (with LIMIT) Full index scan must be started with init_read_record_idx */ - if (used_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) goto err; } else - init_read_record_idx(&info, thd, table, 1, used_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); thd_proc_info(thd, "Searching rows for update"); ha_rows tmp_limit= limit; @@ -610,6 +647,7 @@ int mysql_update(THD *thd, select= new SQL_SELECT; select->head=table; } + //psergey-todo: disable SHOW EXPLAIN because the plan was deleted? if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) error=1; /* purecov: inspected */ select->file=tempfile; // Read row ptrs from this file @@ -884,6 +922,7 @@ int mysql_update(THD *thd, if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; + thd->apc_target.disable(); //psergey-todo. end_read_record(&info); delete select; thd_proc_info(thd, "end"); @@ -962,6 +1001,27 @@ err: table->disable_keyread(); thd->abort_on_warning= 0; DBUG_RETURN(1); + +exit_without_my_ok: + thd->lex->upd_del_plan= &query_plan; + + select_send *result; + bool printed_anything; + if (!(result= new select_send())) + return 1; /* purecov: inspected */ + List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ + result->prepare(dummy, &thd->lex->unit); + thd->send_explain_fields(result); + int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + + if (err2) + result->abort_result_set(); + else + result->send_eof(); + + delete select; + free_underlaid_joins(thd, select_lex); + DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); } /* @@ -1381,23 +1441,37 @@ bool mysql_multi_update(THD *thd, multi_update **result) { bool res; + select_result *output; + bool explain= test(thd->lex->describe); DBUG_ENTER("mysql_multi_update"); - - if (!(*result= new multi_update(table_list, - &thd->lex->select_lex.leaf_tables, - fields, values, - handle_duplicates, ignore))) + + if (explain) { - DBUG_RETURN(TRUE); + /* Handle EXPLAIN UPDATE */ + if (!(output= new select_send()) || + thd->send_explain_fields(output)) + { + delete output; + DBUG_RETURN(TRUE); + } + select_lex->set_explain_type(FALSE); + *result= NULL; /* no multi_update object */ + } + else + { + if (!(*result= new multi_update(table_list, + &thd->lex->select_lex.leaf_tables, + fields, values, + handle_duplicates, ignore))) + { + DBUG_RETURN(TRUE); + } + output= *result; } thd->abort_on_warning= test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); -/* - psergey-explain-todo: - This is the place where EXPLAIN should be handled. -*/ List total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, @@ -1407,12 +1481,20 @@ bool mysql_multi_update(THD *thd, (ORDER *)NULL, options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | OPTION_SETUP_TABLES_DONE, - *result, unit, select_lex); + output, unit, select_lex); DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); if (unlikely(res)) (*result)->abort_result_set(); + else + { + if (explain) + { + output->send_eof(); + delete output; + } + } thd->abort_on_warning= 0; DBUG_RETURN(res); } From 9718b9763deeaa41008aeca31c24548e556401cc Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 27 May 2013 13:53:18 +0400 Subject: [PATCH 04/64] SHOW EXPLAIN DELETE, post merge fixes - Fix asserts, make sure that mysql_delete() operates on thd->apc_target correctly* in all kinds of special cases * - correctly means that one must switch it OFF iff it was switched ON. - Added a few asserts to catch similar errors. --- sql/my_apc.cc | 1 + sql/sql_parse.cc | 3 +++ sql/sql_update.cc | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 3bad1331364..dcb1e3d99b1 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -70,6 +70,7 @@ void Apc_target::enable() void Apc_target::disable() { bool process= FALSE; + DBUG_ASSERT(enabled); mysql_mutex_lock(LOCK_thd_data_ptr); if (!(--enabled)) process= TRUE; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3b088616984..74981eb907b 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -807,7 +807,9 @@ bool do_command(THD *thd) my_net_set_read_timeout(net, thd->variables.net_read_timeout); DBUG_ASSERT(packet_length); + DBUG_ASSERT(!thd->apc_target.is_enabled()); return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1)); + DBUG_ASSERT(!thd->apc_target.is_enabled()); out: DBUG_RETURN(return_value); @@ -1109,6 +1111,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, ulong length= (ulong)(packet_end - beginning_of_next_stmt); log_slow_statement(thd); + DBUG_ASSERT(!thd->apc_target.is_enabled()); /* Remove garbage at start of query */ while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt)) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index ba3ee41909f..8b1b042e6df 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -279,6 +279,7 @@ int mysql_update(THD *thd, Update_plan query_plan; query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; + bool apc_target_enabled= false; // means was enabled *by code this function* DBUG_ENTER("mysql_update"); if (open_tables(thd, &table_list, &table_count, 0)) @@ -497,6 +498,7 @@ int mysql_update(THD *thd, goto exit_without_my_ok; thd->apc_target.enable(); + apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", dbug_serve_apcs(thd, 1);); @@ -923,6 +925,7 @@ int mysql_update(THD *thd, thd->transaction.stmt.modified_non_trans_table= TRUE; thd->apc_target.disable(); //psergey-todo. + apc_target_enabled= false; end_read_record(&info); delete select; thd_proc_info(thd, "end"); @@ -996,6 +999,9 @@ int mysql_update(THD *thd, DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); err: + if (apc_target_enabled) + thd->apc_target.disable(); + delete select; free_underlaid_joins(thd, select_lex); table->disable_keyread(); @@ -1003,6 +1009,7 @@ err: DBUG_RETURN(1); exit_without_my_ok: + DBUG_ASSERT(!apc_target_enabled); thd->lex->upd_del_plan= &query_plan; select_send *result; From 03691a77718c781469b5675c6b03d6064255debb Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 17 Jun 2013 11:59:38 +0400 Subject: [PATCH 05/64] SHOW EXPLAIN UPDATE/DELETE - Introduce "Query Plan Footprints" (abbrev. QPFs) QPF is a part of query plan that is 1. sufficient to produce EXPLAIN output, 2. can be used to produce EXPLAIN output even after its subquery/union was executed and deleted 3. is cheap to save so that we can always save query plans - This patch doesn't fully address #2, we make/save strings for a number of EXPLAIN's columns. This will be fixed. --- libmysqld/CMakeLists.txt | 1 + sql/CMakeLists.txt | 1 + sql/handler.h | 3 +- sql/opt_qpf.cc | 417 +++++++++++++++++++++++++++ sql/opt_qpf.h | 274 ++++++++++++++++++ sql/sql_delete.cc | 5 +- sql/sql_lex.cc | 135 ++++++++- sql/sql_lex.h | 12 +- sql/sql_parse.cc | 8 + sql/sql_select.cc | 608 ++++++++++++++++++++++++++++++++++++++- sql/sql_select.h | 15 +- support-files/build-tags | 2 +- 12 files changed, 1466 insertions(+), 15 deletions(-) create mode 100644 sql/opt_qpf.cc create mode 100644 sql/opt_qpf.h diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 07a0551b4ab..856063afd95 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -99,6 +99,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_expression_cache.cc ../sql/my_apc.cc ../sql/my_apc.h ../sql/rpl_gtid.cc + ../sql/opt_qpf.cc ../sql/opt_qpf.h ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 63e2df46bed..638c6cbca05 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -80,6 +80,7 @@ SET (SQL_SOURCE sql_reload.cc # added in MariaDB: + opt_qpf.h opt_qpf.cc sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc create_options.cc multi_range_read.cc opt_index_cond_pushdown.cc opt_subselect.cc diff --git a/sql/handler.h b/sql/handler.h index 18639b6a8a4..785b354deee 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -286,7 +286,8 @@ (yes, the sum is deliberately inaccurate) TODO remove the limit, use dynarrays */ -#define MAX_HA 15 +/*#define MAX_HA 15*/ +#define MAX_HA 32 /* Use this instead of 0 as the initial value for the slot number of diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc new file mode 100644 index 00000000000..12aef844328 --- /dev/null +++ b/sql/opt_qpf.cc @@ -0,0 +1,417 @@ +/* + TODO MP AB copyright +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "sql_priv.h" +#include "sql_select.h" + + +QPF_query::QPF_query() +{ + memset(&unions, 0, sizeof(unions)); + memset(&selects, 0, sizeof(selects)); +} + + +QPF_node *QPF_query::get_node(uint select_id) +{ + if (unions[select_id]) + return unions[select_id]; + else + return selects[select_id]; +} + + +QPF_select *QPF_query::get_select(uint select_id) +{ + return selects[select_id]; +} + + +void QPF_query::add_node(QPF_node *node) +{ + if (node->get_type() == QPF_node::QPF_UNION) + { + QPF_union *u= (QPF_union*)node; + unions[u->get_select_id()]= u; + } + else + { + QPF_select *sel= (QPF_select*)node; + if (sel->select_id == (int)UINT_MAX) + { + //TODO this is a "fake select" from a UNION. + DBUG_ASSERT(0); + } + else + selects[sel->select_id] = sel; + } +} + + +/* + The main entry point to print EXPLAIN of the entire query +*/ + +int QPF_query::print_explain(select_result_sink *output, + uint8 explain_flags) +{ + // Start with id=1 + QPF_node *node= get_node(1); + return node->print_explain(this, output, explain_flags); +} + + +void QPF_union::push_table_name(List *item_list) +{ +} + + +static void push_str(List *item_list, const char *str) +{ + item_list->push_back(new Item_string(str, + strlen(str), system_charset_info)); +} + + +static void push_string(List *item_list, String *str) +{ + item_list->push_back(new Item_string(str->ptr(), str->length(), + system_charset_info)); +} + + +int QPF_union::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + // print all children, in order + for (int i= 0; i < (int) children.elements(); i++) + { + QPF_select *sel= query->get_select(children.at(i)); + sel->print_explain(query, output, explain_flags); + } + + /* Print a line with "UNION RESULT" */ + List item_list; + Item *item_null= new Item_null(); + + /* `id` column */ + item_list.push_back(item_null); + + /* `select_type` column */ + push_str(&item_list, fake_select_type); + + /* `table` column: something like "" */ + // + { + char table_name_buffer[SAFE_NAME_LEN]; + uint childno= 0; + uint len= 6, lastop= 0; + memcpy(table_name_buffer, STRING_WITH_LEN("= NAME_LEN) + { + memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); + len+= 4; + } + else + { + len+= lastop; + table_name_buffer[len - 1]= '>'; // change ',' to '>' + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(table_name_buffer, len, cs)); + } + // + push_table_name(&item_list); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + + /* `type` column */ + push_str(&item_list, join_type_str[JT_ALL]); + + /* `possible_keys` column */ + item_list.push_back(item_null); + + /* `key` */ + item_list.push_back(item_null); + + /* `key_len` */ + item_list.push_back(item_null); + + /* `ref` */ + item_list.push_back(item_null); + + /* `rows` */ + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* `Extra` */ + StringBuffer<256> extra_buf; + if (using_filesort) + { + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + if (output->send_data(item_list)) + return 1; + return 0; +} + + +int QPF_select::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + if (message) + { + List item_list; + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + + item_list.push_back(new Item_int((int32) select_id)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + for (uint i=0 ; i < 7; i++) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + item_list.push_back(new Item_string(message,strlen(message),cs)); + + if (output->send_data(item_list)) + return 1; + return 0; + } + else + { + bool using_tmp= using_temporary; + bool using_fs= using_filesort; + for (uint i=0; i< n_join_tabs; i++) + { + join_tabs[i]->print_explain(output, explain_flags, select_id, + select_type, using_tmp, using_fs); + if (i == 0) + { + /* + "Using temporary; Using filesort" should only be shown near the 1st + table + */ + using_tmp= false; + using_fs= false; + } + } + } + return 0; +} + + +int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort) +{ + List item_list; + Item *item_null= new Item_null(); + //const CHARSET_INFO *cs= system_charset_info; + + /* `id` column */ + item_list.push_back(new Item_int((int32) select_id)); + + /* `select_type` column */ + push_str(&item_list, select_type); + + /* `table` column */ + push_string(&item_list, &table_name); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + { + if (used_partitions_set) + { + push_string(&item_list, &used_partitions); + } + else + item_list.push_back(item_null); + } + + /* `type` column */ + push_str(&item_list, join_type_str[type]); + + /* `possible_keys` column */ + //push_str(item_list, "TODO"); + item_list.push_back(item_null); + + /* `key` */ + if (key_set) + push_string(&item_list, &key); + else + item_list.push_back(item_null); + + /* `key_len` */ + if (key_len_set) + push_string(&item_list, &key_len); + else + item_list.push_back(item_null); + + /* `ref` */ + if (ref_set) + push_string(&item_list, &ref); + else + item_list.push_back(item_null); + + /* `rows` */ + if (rows_set) + { + item_list.push_back(new Item_int((longlong) (ulonglong) rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + { + if (filtered_set) + { + item_list.push_back(new Item_float(filtered, 2)); + } + else + item_list.push_back(item_null); + } + + /* `Extra` */ + StringBuffer<256> extra_buf; + bool first= true; + for (int i=0; i < (int)extra_tags.elements(); i++) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + append_tag_name(&extra_buf, extra_tags.at(i)); + } + + if (using_temporary) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using temporary")); + } + + if (using_filesort) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + if (output->send_data(item_list)) + return 1; + + return 0; +} + + +const char * extra_tag_text[]= +{ + "ET_none", + "Using index condition", + "Using index condition(BKA)", + "Using ", //special + "Range checked for each record (index map: 0x", //special + "Using where with pushed condition", + "Using where", + "Not exists", + + "Using index", + "Full scan on NULL key", + "Skip_open_table", + "Open_frm_only", + "Open_full_table", + + "Scanned 0 databases", + "Scanned 1 database", + "Scanned all databases", + + "Using index for group-by", // Special? + + "USING MRR: DONT PRINT ME", // Special! + + "Distinct", + "LooseScan", + "Start temporary", + "End temporary", + "FirstMatch", //TODO: also handle special variant! + + "Using join buffer", // Special!, + + "const row not found", + "unique row not found", + "Impossible ON condition" +}; + + +void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) +{ + switch (tag) { + case ET_USING: + { + // quick select + str->append(STRING_WITH_LEN("Using ")); + str->append(quick_info); + break; + } + case ET_RANGE_CHECKED_FOR_EACH_RECORD: + { + /* 4 bits per 1 hex digit + terminating '\0' */ + char buf[MAX_KEY / 4 + 1]; + str->append(STRING_WITH_LEN("Range checked for each " + "record (index map: 0x")); + str->append(range_checked_map.print(buf)); + str->append(')'); + break; + } + case ET_USING_MRR: + { + str->append(mrr_type); + break; + } + case ET_USING_JOIN_BUFFER: + { + str->append(extra_tag_text[tag]); + str->append(join_buffer_type); + break; + } + default: + str->append(extra_tag_text[tag]); + } +} + + diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h new file mode 100644 index 00000000000..86655f3ed3d --- /dev/null +++ b/sql/opt_qpf.h @@ -0,0 +1,274 @@ +/************************************************************************************** + + Query Plan Footprint (QPF) structures + + These structures + - Can be produced in-expensively from query plan. + - Store sufficient information to produce either a tabular or a json EXPLAIN + output + - Have methods that produce a tabular output. + +*************************************************************************************/ + +class QPF_query; + +/* + A node can be either a SELECT, or a UNION. +*/ +class QPF_node : public Sql_alloc +{ +public: + enum qpf_node_type {QPF_UNION, QPF_SELECT}; + + virtual enum qpf_node_type get_type()= 0; + virtual int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags)=0; + virtual ~QPF_node(){} +}; + + +/* + Nesting. + QPF_select may have children QPF_select-s. + (these can be FROM-subqueries, or subqueries from other clauses) + + As for unions, the standard approach is: + - UNION node can be where the select node can be; + - the union has a select that retrieves results from temptable (a special + kind of child) + - and it has regular children selects that are merged into the union. + +*/ + +class QPF_table_access; + +class QPF_select : public QPF_node +{ + /*Construction interface */ +public: + enum qpf_node_type get_type() { return QPF_SELECT; } + +#if 0 + /* Constructs a finished degenerate join plan */ + QPF_select(int select_id_arg, const char *select_type_arg, const char* msg) : + select_id(select_id_arg), + select_type(select_type_arg), + message(msg), + join_tabs(NULL), n_join_tabs(0) + {} + + /* Constructs an un-finished, non degenerate join plan. */ + QPF_select(int select_id_arg, const char *select_type_arg) : + select_id(select_id_arg), + select_type(select_type_arg), + message(NULL), + join_tabs(NULL), n_join_tabs(0) + {} +#endif + QPF_select() : + message(NULL), join_tabs(NULL), + using_temporary(false), using_filesort(false) + {} + + bool add_table(QPF_table_access *tab) + { + if (!join_tabs) + { + join_tabs= (QPF_table_access**) malloc(sizeof(QPF_table_access*) * MAX_TABLES); + n_join_tabs= 0; + } + join_tabs[n_join_tabs++]= tab; + return false; + } + +public: + int select_id; /* -1 means NULL. */ + const char *select_type; + + /* + If message != NULL, this is a degenerate join plan, and all subsequent + members have no info + */ + const char *message; + + /* + According to the discussion: this should be an array of "table + descriptors". + + As for SJ-Materialization. Start_materialize/end_materialize markers? + */ + QPF_table_access** join_tabs; + uint n_join_tabs; + + /* Global join attributes. In tabular form, they are printed on the first row */ + bool using_temporary; + bool using_filesort; + + void print_tabular(select_result_sink *output, uint8 explain_flags//, + //bool *printed_anything + ); + + int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + +class QPF_union : public QPF_node +{ +public: + enum qpf_node_type get_type() { return QPF_UNION; } + + int get_select_id() + { + DBUG_ASSERT(children.elements() > 0); + return children.at(0); + } + // This has QPF_select children + Dynamic_array children; + + void add_select(int select_no) + { + children.append(select_no); + } + void push_table_name(List *item_list); + int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); + + const char *fake_select_type; + bool using_filesort; +}; + + +/* + This is the whole query. +*/ + +class QPF_query +{ +public: + QPF_query(); + void add_node(QPF_node *node); + int print_explain(select_result_sink *output, uint8 explain_flags); + + /* This will return a select, or a union */ + QPF_node *get_node(uint select_id); + + /* This will return a select (even if there is a union with this id) */ + QPF_select *get_select(uint select_id); + +private: + QPF_union *unions[MAX_TABLES]; + QPF_select *selects[MAX_TABLES]; +}; + + +enum Extra_tag +{ + ET_none= 0, /* not-a-tag */ + ET_USING_INDEX_CONDITION, + ET_USING_INDEX_CONDITION_BKA, + ET_USING, /* For quick selects of various kinds */ + ET_RANGE_CHECKED_FOR_EACH_RECORD, + ET_USING_WHERE_WITH_PUSHED_CONDITION, + ET_USING_WHERE, + ET_NOT_EXISTS, + + ET_USING_INDEX, + ET_FULL_SCAN_ON_NULL_KEY, + ET_SKIP_OPEN_TABLE, + ET_OPEN_FRM_ONLY, + ET_OPEN_FULL_TABLE, + + ET_SCANNED_0_DATABASES, + ET_SCANNED_1_DATABASE, + ET_SCANNED_ALL_DATABASES, + + ET_USING_INDEX_FOR_GROUP_BY, + + ET_USING_MRR, // does not print "Using mrr". + + ET_DISTINCT, + ET_LOOSESCAN, + ET_START_TEMPORARY, + ET_END_TEMPORARY, + ET_FIRST_MATCH, + + ET_USING_JOIN_BUFFER, + + ET_CONST_ROW_NOT_FOUND, + ET_UNIQUE_ROW_NOT_FOUND, + ET_IMPOSSIBLE_ON_CONDITION, + + ET_total +}; + + +class QPF_table_access +{ +public: + void push_extra(enum Extra_tag extra_tag); + + /* Internals */ +public: + /* id and 'select_type' are cared-of by the parent QPF_select */ + TABLE *table; + StringBuffer<256> table_name; + + enum join_type type; + + StringBuffer<256> used_partitions; + bool used_partitions_set; + + key_map possible_keys; + + uint key_no; + uint key_length; + + Dynamic_array extra_tags; + + //temporary: + StringBuffer<256> key; + StringBuffer<256> key_len; + StringBuffer<256> ref; + bool key_set; + bool key_len_set; + bool ref_set; + + bool rows_set; + ha_rows rows; + + double filtered; + bool filtered_set; + + /* Various stuff for 'Extra' column*/ + uint join_cache_level; + + // Valid if ET_USING tag is present + StringBuffer<256> quick_info; + + // Valid if ET_USING_INDEX_FOR_GROUP_BY is present + StringBuffer<256> loose_scan_type; + + // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD + key_map range_checked_map; + + // valid with ET_USING_MRR + StringBuffer <256> mrr_type; + + // valid with ET_USING_JOIN_BUFFER + StringBuffer <256> join_buffer_type; + + TABLE *firstmatch_table; + + int print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort); +private: + void append_tag_name(String *str, enum Extra_tag tag); +}; + +// Update_plan and Delete_plan belong to this kind of structures, too. + +// TODO: should Update_plan inherit from QPF_table_access? + + diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index df659871a64..390d9e3945a 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -169,7 +169,9 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, extra_str.c_ptr()); *printed_anything= true; - + /* + psergey-todo: handle all this through saving QPF. + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); unit; unit= unit->next_unit()) @@ -177,6 +179,7 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, if (unit->print_explain(output, explain_flags, printed_anything)) return 1; } + */ return 0; } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1d2490666ea..b2d4ca13823 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4172,6 +4172,7 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } + int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { @@ -4180,11 +4181,87 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, upd_del_plan->print_explain(output, explain_flags, printed_anything); return 0; } - int res= unit.print_explain(output, explain_flags, printed_anything); - return res; + //int res= unit.print_explain(output, explain_flags, printed_anything); + + //psergey-todo: here, we should make Query Plan Footprint, and then produce + // an EXPLAIN output from it. + /* + The new, QueryPlanFootprint way: + */ + QPF_query qpf; + unit.save_qpf(&qpf); + //return res; + return 0; } +void st_select_lex::save_qpf(QPF_query *output) +{ + int res; + if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) + { + /* + There is a number of reasons join can be marked as degenerate, so all + three conditions below can happen simultaneously, or individually: + */ + if (!join->table_count || !join->tables_list || join->zero_result_cause) + { + /* It's a degenerate join */ + const char *cause= join->zero_result_cause ? join-> zero_result_cause : + "No tables used"; + res= join->save_qpf(output, FALSE, FALSE, FALSE, cause); + } + else + { + join->save_qpf(output, join->need_tmp, // need_tmp_table + !join->skip_sort_order && !join->no_order && + (join->order || join->group_list), // bool need_order + join->select_distinct, // bool distinct + NULL); //const char *message + } + if (res) + goto err; + + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + { + unit->save_qpf(output); + } + } + } + else + { + const char *msg; + if (!join) + DBUG_ASSERT(0); /* Seems not to be possible */ + + /* Not printing anything useful, don't touch *printed_anything here */ + if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET) + msg= "Not yet optimized"; + else + { + DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); + msg= "Query plan already deleted"; + } + set_explain_type(TRUE/* on_the_fly */); + QPF_select *qp_sel= new QPF_select; + qp_sel->select_id= select_number; + qp_sel->select_type= type; + qp_sel->message= msg; + output->add_node(qp_sel); + } +err: + return ;//res; +} + +#if 0 int st_select_lex::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) @@ -4253,8 +4330,60 @@ int st_select_lex::print_explain(select_result_sink *output, err: return res; } +#endif +int st_select_lex_unit::save_qpf(QPF_query *output) +{ + //int res= 0; + SELECT_LEX *first= first_select(); + + QPF_union *qpfu= new QPF_union; + /* + TODO: The following code should be eliminated. If we have a capability to + save Query Plan Footprints, we should just save them, and never need to + print "query plan already deleted". + */ + if (first && !first->next_select() && !first->join) + { + /* + If there is only one child, 'first', and it has join==NULL, emit "not in + EXPLAIN state" error. + */ + const char *msg="Query plan already deleted"; + first->set_explain_type(TRUE/* on_the_fly */); + + QPF_select *qp_sel= new QPF_select; + qp_sel->select_id= first->select_number; + qp_sel->select_type= first->type; + qp_sel->message= msg; + output->add_node(qp_sel); + qpfu->add_select(qp_sel->select_id); + return 0; + } + + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) + { + sl->save_qpf(output); + qpfu->add_select(sl->select_number); + } + + // Save the UNION node + output->add_node(qpfu); + +#if 0 + /* Note: fake_select_lex->join may be NULL or non-NULL at this point */ + if (fake_select_lex) + { + res= print_fake_select_lex_join(output, TRUE /* on the fly */, + fake_select_lex, explain_flags); + } + return res; +#endif + return 0; +} + +#if 0 int st_select_lex_unit::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { @@ -4288,7 +4417,7 @@ int st_select_lex_unit::print_explain(select_result_sink *output, } return res; } - +#endif /** A routine used by the parser to decide whether we are specifying a full diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2b075b77000..d2a8b59a593 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -617,7 +617,7 @@ class select_result; class JOIN; class select_union; class Procedure; - +class QPF_query; class st_select_lex_unit: public st_select_lex_node { protected: @@ -728,8 +728,11 @@ public: friend int subselect_union_engine::exec(); List *get_unit_column_types(); +#if 0 int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); +#endif + int save_qpf(QPF_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -1048,8 +1051,11 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); +#if 0 int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); +#endif + void save_qpf(QPF_query *output); /* For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags: - Non-aggregated fields are used in this select. @@ -2360,6 +2366,8 @@ class SQL_SELECT; /* Query plan of a single-table UPDATE. (This is actually a plan for single-table DELETE also) + + TODO: this should be a query plan footprint, not a query plan. */ class Update_plan { @@ -2411,6 +2419,7 @@ public: }; +class QPF_query; /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2424,6 +2433,7 @@ struct LEX: public Query_tables_list /* For single-table DELETE: its query plan */ Update_plan *upd_del_plan; + QPF_query *query_plan_footprint; char *length,*dec,*change; LEX_STRING name; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 74981eb907b..aab26e72a65 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4811,7 +4811,15 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); + thd->lex->query_plan_footprint= new QPF_query; res= mysql_explain_union(thd, &thd->lex->unit, result); + + thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + + //psergey-todo: here, produce the EXPLAIN output. + // mysql_explain_union() itself is only responsible for calling + // optimize() for all parts of the query. + /* The code which prints the extended description is not robust against malformed queries, so skip it if we have an error. diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 74df58db214..45090a2cec5 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3856,7 +3856,7 @@ make_join_statistics(JOIN *join, List &tables_list, if (*s->on_expr_ref) { /* Generate empty row */ - s->info= "Impossible ON condition"; + s->info= ET_IMPOSSIBLE_ON_CONDITION; found_const_table_map|= s->table->map; s->type= JT_CONST; mark_as_null_row(s->table); // All fields are NULL @@ -17219,7 +17219,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { if ((error=join_read_system(tab))) { // Info for DESCRIBE - tab->info="const row not found"; + tab->info= ET_CONST_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -17245,7 +17245,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) table->disable_keyread(); if (error) { - tab->info="unique row not found"; + tab->info= ET_UNIQUE_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -22321,8 +22321,11 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) selects) @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make modifications to any select's data structures -*/ + psergey-todo: should this produce a data structure with a query plan? Or, the + data structure with the query plan should be produced in any way? +*/ +#if 0 int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, bool on_the_fly, bool need_tmp_table, bool need_order, @@ -22381,6 +22384,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; @@ -22392,7 +22396,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); printing_materialize_nest= TRUE; } - + TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; char buff[512]; @@ -22672,7 +22676,23 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, key_read=1; if (tab->info) - item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs)); + { + const char *reason; + switch (tab->info) { + case ET_CONST_ROW_NOT_FOUND: + reason= "const row not found"; + break; + case ET_UNIQUE_ROW_NOT_FOUND: + reason= "unique row not found"; + break; + case ET_IMPOSSIBLE_ON_CONDITION: + reason= "Impossible ON condition"; + break; + default: + DBUG_ASSERT(0); + } + item_list.push_back(new Item_string(reason,strlen(reason),cs)); + } else if (tab->packed_info & TAB_INFO_HAVE_VALUE) { if (tab->packed_info & TAB_INFO_USING_INDEX) @@ -22876,6 +22896,577 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, } DBUG_RETURN(error); } +#endif +///////////////////////////////////////////////////////////////////////////////////////////////// +void QPF_table_access::push_extra(enum Extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + +/* + Save Query Plan Footprint + push_extra + + P +*/ + +int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, + bool distinct, const char *message) +{ + QPF_select *qp_sel; + const bool on_the_fly= true; + + JOIN *join= this; /* Legacy: this code used to be a non-member function */ + THD *thd=join->thd; + const CHARSET_INFO *cs= system_charset_info; + int quick_type; + int error= 0; + DBUG_ENTER("JOIN::print_explain"); + DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", + (ulong)join->select_lex, join->select_lex->type, + message ? message : "NULL")); + DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); + /* Don't log this into the slow query log */ + + qp_sel= new QPF_select; + + /* + NOTE: the number/types of items pushed into item_list must be in sync with + EXPLAIN column types as they're "defined" in THD::send_explain_fields() + */ + if (message) + { + join->select_lex->set_explain_type(on_the_fly); + + qp_sel->select_id= join->select_lex->select_number; + qp_sel->select_type= join->select_lex->type; + qp_sel->message= message; + /* Setting qp_sel->message means that all other members are invalid */ + output->add_node(qp_sel); + } + else if (join->select_lex == join->unit->fake_select_lex) + { + select_lex->set_explain_type(on_the_fly); + QPF_union *qp_union= new QPF_union; + + SELECT_LEX *child; + for (child= select_lex->master_unit()->first_select(); child; + child=child->next_select()) + { + qp_union->add_select(child->select_number); + } + + qp_union->fake_select_type= select_lex->type; + qp_union->using_filesort= + test(select_lex->master_unit()->global_parameters->order_list.first); + + output->add_node(qp_union); + } + else if (!join->select_lex->master_unit()->derived || + join->select_lex->master_unit()->derived->is_materialized_derived()) + { + table_map used_tables=0; + + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); + + bool printing_materialize_nest= FALSE; + uint select_id= join->select_lex->select_number; + + qp_sel->select_id= join->select_lex->select_number; + qp_sel->select_type= join->select_lex->type; + + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); + + for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; + tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) + { + QPF_table_access *qpt= new QPF_table_access; + qp_sel->add_table(qpt); + + if (tab->bush_root_tab) + { + JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; + select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); + printing_materialize_nest= TRUE; + } + + TABLE *table=tab->table; + TABLE_LIST *table_list= tab->table->pos_in_table_list; + char buff2[512], buff3[512], buff4[512]; + char keylen_str_buf[64]; + my_bool key_read; + char table_name_buffer[SAFE_NAME_LEN]; + String tmp2(buff2,sizeof(buff2),cs); + String tmp3(buff3,sizeof(buff3),cs); + String tmp4(buff4,sizeof(buff4),cs); + char hash_key_prefix[]= "#hash#"; + KEY *key_info= 0; + uint key_len= 0; + bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT; + + tmp2.length(0); + tmp3.length(0); + tmp4.length(0); + quick_type= -1; + QUICK_SELECT_I *quick= NULL; + JOIN_TAB *saved_join_tab= NULL; + + /* Don't show eliminated tables */ + if (table->map & join->eliminated_tables) + { + used_tables|=table->map; + continue; + } + + + if (join->table_access_tabs == join->join_tab && + tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) + { + saved_join_tab= tab; + tab= pre_sort_join_tab; + } + + /* id */ + qp_sel->select_id= select_id; + + /* select_type */ + //const char* stype= printing_materialize_nest? "MATERIALIZED" : + // join->select_lex->type; + //item_list.push_back(new Item_string(stype, strlen(stype), cs)); + qp_sel->select_type= join->select_lex->type; + + /* table */ + if (table->derived_select_number) + { + /* Derived table name generation */ + int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, + "", + table->derived_select_number); + qpt->table_name.set(table_name_buffer, len, cs); + } + else if (tab->bush_children) + { + JOIN_TAB *ctab= tab->bush_children->start; + /* table */ + int len= my_snprintf(table_name_buffer, + sizeof(table_name_buffer)-1, + "", + ctab->emb_sj_nest->sj_subq_pred->get_identifier()); + qpt->table_name.set(table_name_buffer, len, cs); + } + else + { + TABLE_LIST *real_table= table->pos_in_table_list; + qpt->table_name.set(real_table->alias, strlen(real_table->alias), cs); + } + + /* "partitions" column */ + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; + if (!table->derived_select_number && + (part_info= table->part_info)) + { + make_used_partitions_str(part_info, &qpt->used_partitions); + qpt->used_partitions_set= true; + } + else + qpt->used_partitions_set= false; +#else + /* just produce empty column if partitioning is not compiled in */ + qpt->used_partitions_set= false; +#endif + } + + /* "type" column */ + enum join_type tab_type= tab->type; + if ((tab->type == JT_ALL || tab->type == JT_HASH) && + tab->select && tab->select->quick && tab->use_quick != 2) + { + quick= tab->select->quick; + quick_type= tab->select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE; + else + tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; + } + qpt->type= tab_type; + + /* Build "possible_keys" value */ + qpt->possible_keys= tab->keys; + + /* Build "key", "key_len", and "ref" */ + + // tmp2 holds key_name + // tmp3 holds key_length + // tmp4 holds ref? + if (tab_type == JT_NEXT) + { + key_info= table->key_info+tab->index; + key_len= key_info->key_length; + } + else if (tab->ref.key_parts) + { + key_info= tab->get_keyinfo_by_key_no(tab->ref.key); + key_len= tab->ref.key_length; + } + + if (key_info) + { + register uint length; + if (is_hj) + tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs); + tmp2.append(key_info->name, strlen(key_info->name), cs); + length= (longlong10_to_str(key_len, keylen_str_buf, 10) - + keylen_str_buf); + tmp3.append(keylen_str_buf, length, cs); + if (tab->ref.key_parts && tab_type != JT_FT) + { + store_key **ref=tab->ref.key_copy; + for (uint kp= 0; kp < tab->ref.key_parts; kp++) + { + if (tmp4.length()) + tmp4.append(','); + + if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map) + tmp4.append("const"); + else + { + tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); + ref++; + } + } + } + } + + if (is_hj && tab_type != JT_HASH) + { + tmp2.append(':'); + tmp3.append(':'); + } + + if (tab_type == JT_HASH_NEXT) + { + register uint length; + key_info= table->key_info+tab->index; + key_len= key_info->key_length; + tmp2.append(key_info->name, strlen(key_info->name), cs); + length= (longlong10_to_str(key_len, keylen_str_buf, 10) - + keylen_str_buf); + tmp3.append(keylen_str_buf, length, cs); + } + + if (tab->type != JT_CONST && tab->select && quick) + tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); + + if (key_info || (tab->select && quick)) + { + if (tmp2.length()) + { + qpt->key.copy(tmp2); + qpt->key_set= true; + } + else + qpt->key_set= false; + + if (tmp3.length()) + { + qpt->key_len.copy(tmp3); + qpt->key_len_set= true; + } + else + qpt->key_len_set= false; + + if (key_info && tab_type != JT_NEXT) + { + qpt->ref.copy(tmp4); + qpt->ref_set= true; + } + else + qpt->ref_set= false; + } + else + { + if (table_list && /* SJM bushes don't have table_list */ + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + const char *tmp_buff; + int f_idx; + if (table_list->has_db_lookup_value) + { + f_idx= table_list->schema_table->idx_field1; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + tmp2.append(tmp_buff, strlen(tmp_buff), cs); + } + if (table_list->has_table_lookup_value) + { + if (table_list->has_db_lookup_value) + tmp2.append(','); + f_idx= table_list->schema_table->idx_field2; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + tmp2.append(tmp_buff, strlen(tmp_buff), cs); + } + if (tmp2.length()) + { + qpt->key.copy(tmp2); + qpt->key_set= true; + } + else + qpt->key_set= false; + } + else + qpt->key_set= false; + + qpt->key_len_set= false; + qpt->ref_set= false; + } + + /* "rows" */ + + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table) + { + /* I_S tables have rows=extra=NULL */ + qpt->rows_set= false; + qpt->filtered_set= false; + } + else + { + ha_rows examined_rows= tab->get_examined_rows(); + + qpt->rows_set= true; + qpt->rows= examined_rows; + + /* "filtered" */ + float f= 0.0; + if (examined_rows) + { + double pushdown_cond_selectivity= tab->cond_selectivity; + if (pushdown_cond_selectivity == 1.0) + f= (float) (100.0 * tab->records_read / examined_rows); + else + f= (float) (100.0 * pushdown_cond_selectivity); + } + set_if_smaller(f, 100.0); + qpt->filtered_set= true; + qpt->filtered= f; + } + + /* Build "Extra" field and save it */ + key_read=table->key_read; + if ((tab_type == JT_NEXT || tab_type == JT_CONST) && + table->covering_keys.is_set(tab->index)) + key_read=1; + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && + !((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row) + key_read=1; + + if (tab->info) + { + qpt->push_extra(tab->info); + } + else if (tab->packed_info & TAB_INFO_HAVE_VALUE) + { + if (tab->packed_info & TAB_INFO_USING_INDEX) + qpt->push_extra(ET_USING_INDEX); + if (tab->packed_info & TAB_INFO_USING_WHERE) + qpt->push_extra(ET_USING_WHERE); + if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL) + qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + } + else + { + uint keyno= MAX_KEY; + if (tab->ref.key_parts) + keyno= tab->ref.key; + else if (tab->select && quick) + keyno = quick->index; + + if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && + table->file->pushed_idx_cond) + qpt->push_extra(ET_USING_INDEX_CONDITION); + else if (tab->cache_idx_cond) + qpt->push_extra(ET_USING_INDEX_CONDITION_BKA); + + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || + quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + qpt->push_extra(ET_USING); + tab->select->quick->add_info_string(&qpt->quick_info); + } + if (tab->select) + { + if (tab->use_quick == 2) + { + qpt->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); + qpt->range_checked_map= tab->keys; + } + else if (tab->select->cond) + { + const COND *pushed_cond= tab->table->file->pushed_cond; + + if (((thd->variables.optimizer_switch & + OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) || + (tab->table->file->ha_table_flags() & + HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && + pushed_cond) + { + qpt->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); + /* + psergey-todo: what to do? This was useful with NDB only. + + if (explain_flags & DESCRIBE_EXTENDED) + { + extra.append(STRING_WITH_LEN(": ")); + ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); + } + */ + } + else + qpt->push_extra(ET_USING_WHERE); + } + } + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + if (!table_list->table_open_method) + qpt->push_extra(ET_SKIP_OPEN_TABLE); + else if (table_list->table_open_method == OPEN_FRM_ONLY) + qpt->push_extra(ET_OPEN_FRM_ONLY); + else + qpt->push_extra(ET_OPEN_FULL_TABLE); + /* psergey-note: the following has a bug.*/ + if (table_list->has_db_lookup_value && + table_list->has_table_lookup_value) + qpt->push_extra(ET_SCANNED_0_DATABASES); + else if (table_list->has_db_lookup_value || + table_list->has_table_lookup_value) + qpt->push_extra(ET_SCANNED_1_DATABASE); + else + qpt->push_extra(ET_SCANNED_ALL_DATABASES); + } + if (key_read) + { + if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + QUICK_GROUP_MIN_MAX_SELECT *qgs= + (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; + qpt->push_extra(ET_USING_INDEX_FOR_GROUP_BY); + qgs->append_loose_scan_type(&qpt->loose_scan_type); + } + else + qpt->push_extra(ET_USING_INDEX); + } + if (table->reginfo.not_exists_optimize) + qpt->push_extra(ET_NOT_EXISTS); + + /* + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE && + !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags & + HA_MRR_USE_DEFAULT_IMPL)) + { + extra.append(STRING_WITH_LEN("; Using MRR")); + } + */ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) + { + qpt->push_extra(ET_USING_MRR); + explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), + &qpt->mrr_type); + } + + if (need_tmp_table) + { + need_tmp_table=0; + qp_sel->using_temporary= true; + ///extra.append(STRING_WITH_LEN("; Using temporary")); + } + if (need_order) + { + need_order=0; + qp_sel->using_filesort= true; + ///extra.append(STRING_WITH_LEN("; Using filesort")); + } + if (distinct & test_all_bits(used_tables, + join->select_list_used_tables)) + qpt->push_extra(ET_DISTINCT); + if (tab->loosescan_match_tab) + { + qpt->push_extra(ET_LOOSESCAN); + } + + if (tab->first_weedout_table) + qpt->push_extra(ET_START_TEMPORARY); + if (tab->check_weed_out_table) + qpt->push_extra(ET_END_TEMPORARY); + else if (tab->do_firstmatch) + { + if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) + qpt->push_extra(ET_FIRST_MATCH); + else + { + qpt->push_extra(ET_FIRST_MATCH); + //TABLE *prev_table=tab->do_firstmatch->table; + /* + TODO: qpt->firstmatch_table... + This must be a reference to another QPF element. Or, its index. + */ +#if 0 + extra.append(STRING_WITH_LEN("; FirstMatch(")); + if (prev_table->derived_select_number) + { + char namebuf[NAME_LEN]; + /* Derived table name generation */ + int len= my_snprintf(namebuf, sizeof(namebuf)-1, + "", + prev_table->derived_select_number); + extra.append(namebuf, len); + } + else + extra.append(prev_table->pos_in_table_list->alias); + extra.append(STRING_WITH_LEN(")")); +#endif + } + } + + for (uint part= 0; part < tab->ref.key_parts; part++) + { + if (tab->ref.cond_guards[part]) + { + qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + break; + } + } + + if (tab->cache) + { + qpt->push_extra(ET_USING_JOIN_BUFFER); + tab->cache->print_explain_comment(&qpt->join_buffer_type); + } + + } + + if (saved_join_tab) + tab= saved_join_tab; + + // For next iteration + used_tables|=table->map; + } + output->add_node(qp_sel); + } + DBUG_RETURN(error); +} + +////////////////////////////////////////////////////////////////////////////////////////////// /* @@ -22888,10 +23479,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, THD *thd=join->thd; select_result *result=join->result; DBUG_ENTER("select_describe"); +#if 0 join->error= join->print_explain(result, thd->lex->describe, FALSE, /* Not on-the-fly */ need_tmp_table, need_order, distinct, message); +#endif + //psergey-todo: save QPF here, too. + join->save_qpf(thd->lex->query_plan_footprint, need_tmp_table, need_order, + distinct, message); for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; diff --git a/sql/sql_select.h b/sql/sql_select.h index 5a3dbccb97f..5ec09fcfcb8 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -198,6 +198,12 @@ int rr_sequential(READ_RECORD *info); int rr_sequential_and_unpack(READ_RECORD *info); +#include "opt_qpf.h" + +/************************************************************************************** + * New EXPLAIN structures END + *************************************************************************************/ + class JOIN_CACHE; class SJ_TMP_TABLE; class JOIN_TAB_RANGE; @@ -252,7 +258,9 @@ typedef struct st_join_table { JOIN_TAB_RANGE *bush_children; /* Special content for EXPLAIN 'Extra' column or NULL if none */ - const char *info; + enum Extra_tag info; + //const char *info; + /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' column, or 0 if there is no info. @@ -1454,11 +1462,14 @@ public: { return (unit->item && unit->item->is_in_predicate()); } - +/* int print_explain(select_result_sink *result, uint8 explain_flags, bool on_the_fly, bool need_tmp_table, bool need_order, bool distinct,const char *message); +*/ + int save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, + bool distinct, const char *message); private: /** TRUE if the query contains an aggregate function but has no GROUP diff --git a/support-files/build-tags b/support-files/build-tags index b5386dc79c3..31e67a4ebfe 100755 --- a/support-files/build-tags +++ b/support-files/build-tags @@ -6,7 +6,7 @@ filter='\.cc$\|\.c$\|\.h$\|\.yy$' list="find . -type f" bzr root >/dev/null 2>/dev/null && list="bzr ls --from-root -R --kind=file --versioned" -$list |grep $filter |while read f; +$list |grep $filter | grep -v gen-cpp |while read f; do etags -o TAGS --append $f done From d97ca5f56c400bb675d789d718f52eb1a21d2432 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 17 Jun 2013 19:39:55 +0400 Subject: [PATCH 06/64] [SHOW] EXPLAIN UPDATE/DELETE, code reordering - Add further details, the goal is to pass the testsuite - SJM-nests are not printed correctly yet. --- sql/opt_qpf.cc | 33 ++++++++++++++++++++++++++++++++- sql/opt_qpf.h | 35 +++++++++++++++-------------------- sql/sql_parse.cc | 7 ++++++- sql/sql_select.cc | 46 ++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 93 insertions(+), 28 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 12aef844328..90d749ea4b4 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -17,6 +17,16 @@ QPF_query::QPF_query() } +QPF_query::~QPF_query() +{ + uint i; + for (i=0 ; i < MAX_TABLES; i++) + delete unions[i]; + for (i=0 ; i < MAX_TABLES; i++) + delete selects[i]; +} + + QPF_node *QPF_query::get_node(uint select_id) { if (unions[select_id]) @@ -178,6 +188,17 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, } +QPF_select::~QPF_select() +{ + if (join_tabs) + { + for (uint i= 0; i< n_join_tabs; i++) + delete join_tabs[i]; + my_free(join_tabs); + } +} + + int QPF_select::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { @@ -222,6 +243,13 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, } } } + + //psergey-TODO: print children here... + for (int i= 0; i < (int) children.elements(); i++) + { + QPF_node *node= query->get_node(children.at(i)); + node->print_explain(query, output, explain_flags); + } return 0; } @@ -259,7 +287,10 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl /* `possible_keys` column */ //push_str(item_list, "TODO"); - item_list.push_back(item_null); + if (possible_keys_str.length() > 0) + push_string(&item_list, &possible_keys_str); + else + item_list.push_back(item_null); /* `key` */ if (key_set) diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 86655f3ed3d..6aa0a5327da 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -48,33 +48,19 @@ class QPF_select : public QPF_node public: enum qpf_node_type get_type() { return QPF_SELECT; } -#if 0 - /* Constructs a finished degenerate join plan */ - QPF_select(int select_id_arg, const char *select_type_arg, const char* msg) : - select_id(select_id_arg), - select_type(select_type_arg), - message(msg), - join_tabs(NULL), n_join_tabs(0) - {} - - /* Constructs an un-finished, non degenerate join plan. */ - QPF_select(int select_id_arg, const char *select_type_arg) : - select_id(select_id_arg), - select_type(select_type_arg), - message(NULL), - join_tabs(NULL), n_join_tabs(0) - {} -#endif QPF_select() : message(NULL), join_tabs(NULL), using_temporary(false), using_filesort(false) {} + + ~QPF_select(); bool add_table(QPF_table_access *tab) { if (!join_tabs) { - join_tabs= (QPF_table_access**) malloc(sizeof(QPF_table_access*) * MAX_TABLES); + join_tabs= (QPF_table_access**) my_malloc(sizeof(QPF_table_access*) * + MAX_TABLES, MYF(0)); n_join_tabs= 0; } join_tabs[n_join_tabs++]= tab; @@ -103,6 +89,13 @@ public: /* Global join attributes. In tabular form, they are printed on the first row */ bool using_temporary; bool using_filesort; + + /* Child selects. TODO: join this with QPF_union's children? */ + Dynamic_array children; + void add_child(int select_no) + { + children.append(select_no); + } void print_tabular(select_result_sink *output, uint8 explain_flags//, //bool *printed_anything @@ -143,10 +136,11 @@ public: This is the whole query. */ -class QPF_query +class QPF_query : public Sql_alloc { public: QPF_query(); + ~QPF_query(); void add_node(QPF_node *node); int print_explain(select_result_sink *output, uint8 explain_flags); @@ -203,7 +197,7 @@ enum Extra_tag }; -class QPF_table_access +class QPF_table_access : public Sql_alloc { public: void push_extra(enum Extra_tag extra_tag); @@ -220,6 +214,7 @@ public: bool used_partitions_set; key_map possible_keys; + StringBuffer<256> possible_keys_str; uint key_no; uint key_length; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index aab26e72a65..d6e31f4142c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4814,7 +4814,12 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) thd->lex->query_plan_footprint= new QPF_query; res= mysql_explain_union(thd, &thd->lex->unit, result); - thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + if (!res) + { + thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + } + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; //psergey-todo: here, produce the EXPLAIN output. // mysql_explain_union() itself is only responsible for calling diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 45090a2cec5..09c28680860 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22903,6 +22903,21 @@ void QPF_table_access::push_extra(enum Extra_tag extra_tag) extra_tags.append(extra_tag); } +void append_possible_keys(String *str, TABLE *table, key_map possible_keys) +{ + uint j; + for (j=0 ; j < table->s->keys ; j++) + { + if (possible_keys.is_set(j)) + { + if (str->length()) + str->append(','); + str->append(table->key_info[j].name, + strlen(table->key_info[j].name), + system_charset_info); + } + } +} /* Save Query Plan Footprint @@ -22982,9 +22997,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) { - QPF_table_access *qpt= new QPF_table_access; - qp_sel->add_table(qpt); - if (tab->bush_root_tab) { JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; @@ -23020,7 +23032,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, continue; } - if (join->table_access_tabs == join->join_tab && tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) { @@ -23028,8 +23039,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, tab= pre_sort_join_tab; } + QPF_table_access *qpt= new QPF_table_access; + qp_sel->add_table(qpt); + /* id */ - qp_sel->select_id= select_id; + // TODO: this can be '2' in case of SJM nests.. + //qp_sel->select_id= select_id; /* select_type */ //const char* stype= printing_materialize_nest? "MATERIALIZED" : @@ -23099,6 +23114,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, /* Build "possible_keys" value */ qpt->possible_keys= tab->keys; + append_possible_keys(&qpt->possible_keys_str, table, tab->keys); /* Build "key", "key_len", and "ref" */ @@ -23379,9 +23395,10 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, */ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) { - qpt->push_extra(ET_USING_MRR); explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), &qpt->mrr_type); + if (qpt->mrr_type.length() > 0) + qpt->push_extra(ET_USING_MRR); } if (need_tmp_table) @@ -23463,6 +23480,23 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } output->add_node(qp_sel); } + + + /// + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + { + qp_sel->add_child(unit->first_select()->select_number); + } + } + DBUG_RETURN(error); } From 1ec9993155899c4427deb999bff90321da731eb3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 18 Jun 2013 08:31:46 +0400 Subject: [PATCH 07/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-architecting - Fix more problems to pass the testsuite (not finished yet) --- sql/sql_select.cc | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 09c28680860..3432e42d32a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22929,7 +22929,7 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, bool distinct, const char *message) { - QPF_select *qp_sel; + QPF_select *qp_sel= NULL; const bool on_the_fly= true; JOIN *join= this; /* Legacy: this code used to be a non-member function */ @@ -22944,7 +22944,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); /* Don't log this into the slow query log */ - qp_sel= new QPF_select; /* NOTE: the number/types of items pushed into item_list must be in sync with @@ -22952,6 +22951,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, */ if (message) { + qp_sel= new QPF_select; join->select_lex->set_explain_type(on_the_fly); qp_sel->select_id= join->select_lex->select_number; @@ -22981,6 +22981,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { + qp_sel= new QPF_select; table_map used_tables=0; if (on_the_fly) @@ -23059,7 +23060,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, "", table->derived_select_number); - qpt->table_name.set(table_name_buffer, len, cs); + qpt->table_name.append(table_name_buffer, len, cs); } else if (tab->bush_children) { @@ -23074,7 +23075,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, else { TABLE_LIST *real_table= table->pos_in_table_list; - qpt->table_name.set(real_table->alias, strlen(real_table->alias), cs); + qpt->table_name.append(real_table->alias, strlen(real_table->alias), cs); } /* "partitions" column */ @@ -23481,19 +23482,22 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, output->add_node(qp_sel); } - - /// - for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) + //TODO: can a UNION have subquery children that are not union members? yes, + //perhaps... + if (qp_sel) { - /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. - */ - if (!(unit->item && unit->item->eliminated)) + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) { - qp_sel->add_child(unit->first_select()->select_number); + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + { + qp_sel->add_child(unit->first_select()->select_number); + } } } From d2ea53aaedad051ceaf59d61f6ae8654bfd5947a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 18 Jun 2013 10:57:36 +0400 Subject: [PATCH 08/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-architecting - Pass more tests - select with subselects is now shown with type=PRIMARY where it used to be (incorrectly) 'SIMPLE' --- mysql-test/r/derived_view.result | 14 ++--- mysql-test/r/selectivity.result | 4 +- mysql-test/r/selectivity_innodb.result | 4 +- mysql-test/r/subselect2.result | 4 +- mysql-test/r/subselect_no_semijoin.result | 14 ++--- sql/opt_qpf.cc | 39 +++++++++--- sql/opt_qpf.h | 76 +++++++++++++++-------- sql/sql_select.cc | 62 +++++++++--------- 8 files changed, 131 insertions(+), 86 deletions(-) diff --git a/mysql-test/r/derived_view.result b/mysql-test/r/derived_view.result index 40420a15c25..1ee88048d2b 100644 --- a/mysql-test/r/derived_view.result +++ b/mysql-test/r/derived_view.result @@ -386,7 +386,7 @@ materialized derived in merged derived explain extended select * from (select * from (select * from t1 where f1 < 7 group by f1) tt where f1 > 2) zz; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE ALL NULL NULL NULL NULL 11 100.00 Using where +1 PRIMARY ALL NULL NULL NULL NULL 11 100.00 Using where 3 DERIVED t1 ALL NULL NULL NULL NULL 11 100.00 Using where; Using temporary; Using filesort Warnings: Note 1003 select `tt`.`f1` AS `f1`,`tt`.`f11` AS `f11` from (select `test`.`t1`.`f1` AS `f1`,`test`.`t1`.`f11` AS `f11` from `test`.`t1` where (`test`.`t1`.`f1` < 7) group by `test`.`t1`.`f1`) `tt` where (`tt`.`f1` > 2) @@ -429,8 +429,8 @@ join (select * from (select * from t1 where f1 < 7 group by f1) tt where f1 > 2) z on x.f1 = z.f1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE ALL NULL NULL NULL NULL 11 100.00 Using where -1 SIMPLE ref key0 key0 5 tt.f1 2 100.00 +1 PRIMARY ALL NULL NULL NULL NULL 11 100.00 Using where +1 PRIMARY ref key0 key0 5 tt.f1 2 100.00 5 DERIVED t1 ALL NULL NULL NULL NULL 11 100.00 Using where; Using temporary; Using filesort 3 DERIVED t1 ALL NULL NULL NULL NULL 11 100.00 Using where; Using temporary; Using filesort Warnings: @@ -522,7 +522,7 @@ materialized view in merged derived explain extended select * from ( select * from v1 where f1 < 7) tt; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE ALL NULL NULL NULL NULL 11 100.00 Using where +1 PRIMARY ALL NULL NULL NULL NULL 11 100.00 Using where 3 DERIVED t1 ALL NULL NULL NULL NULL 11 100.00 Using temporary; Using filesort Warnings: Note 1003 select `v1`.`f1` AS `f1`,`v1`.`f11` AS `f11` from `test`.`v1` where (`v1`.`f1` < 7) @@ -568,8 +568,8 @@ f1 f11 join of above two explain extended select * from v6 join v7 on f2=f1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE t2 ALL NULL NULL NULL NULL 11 100.00 Using where -1 SIMPLE ref key0 key0 5 test.t2.f2 2 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 11 100.00 Using where +1 PRIMARY ref key0 key0 5 test.t2.f2 2 100.00 5 DERIVED t1 ALL NULL NULL NULL NULL 11 100.00 Using temporary; Using filesort Warnings: Note 1003 select `test`.`t2`.`f2` AS `f2`,`test`.`t2`.`f22` AS `f22`,`v1`.`f1` AS `f1`,`v1`.`f11` AS `f11` from `test`.`t2` join `test`.`v1` where ((`v1`.`f1` = `test`.`t2`.`f2`) and (`test`.`t2`.`f2` < 7) and (`test`.`t2`.`f2` in (2,3))) @@ -1220,7 +1220,7 @@ Note 1003 select `test`.`t1`.`b` AS `b` from `test`.`t1` where 0 EXPLAIN EXTENDED SELECT * FROM (SELECT b FROM v2 WHERE b = 0) t WHERE b; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE 3 DERIVED t1 ALL NULL NULL NULL NULL 5 100.00 Using temporary; Using filesort Warnings: Note 1003 select `v2`.`b` AS `b` from `test`.`v2` where 0 diff --git a/mysql-test/r/selectivity.result b/mysql-test/r/selectivity.result index e59f4310431..4a2ebef8504 100644 --- a/mysql-test/r/selectivity.result +++ b/mysql-test/r/selectivity.result @@ -393,7 +393,7 @@ and not exists (select * from orders where o_custkey = c_custkey) group by cntrycode order by cntrycode; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort +1 PRIMARY customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort 4 DEPENDENT SUBQUERY orders ref i_o_custkey i_o_custkey 5 dbt3_s001.customer.c_custkey 15 100.00 Using index 3 SUBQUERY customer ALL NULL NULL NULL NULL 150 100.00 Using where Warnings: @@ -434,7 +434,7 @@ and not exists (select * from orders where o_custkey = c_custkey) group by cntrycode order by cntrycode; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort +1 PRIMARY customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort 4 DEPENDENT SUBQUERY orders ref i_o_custkey i_o_custkey 5 dbt3_s001.customer.c_custkey 15 100.00 Using index 3 SUBQUERY customer ALL NULL NULL NULL NULL 150 91.00 Using where Warnings: diff --git a/mysql-test/r/selectivity_innodb.result b/mysql-test/r/selectivity_innodb.result index 20efb16914b..9607bb98765 100644 --- a/mysql-test/r/selectivity_innodb.result +++ b/mysql-test/r/selectivity_innodb.result @@ -396,7 +396,7 @@ and not exists (select * from orders where o_custkey = c_custkey) group by cntrycode order by cntrycode; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort +1 PRIMARY customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort 4 DEPENDENT SUBQUERY orders ref i_o_custkey i_o_custkey 5 dbt3_s001.customer.c_custkey 15 100.00 Using index 3 SUBQUERY customer ALL NULL NULL NULL NULL 150 100.00 Using where Warnings: @@ -437,7 +437,7 @@ and not exists (select * from orders where o_custkey = c_custkey) group by cntrycode order by cntrycode; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort +1 PRIMARY customer ALL NULL NULL NULL NULL 150 100.00 Using where; Using temporary; Using filesort 4 DEPENDENT SUBQUERY orders ref i_o_custkey i_o_custkey 5 dbt3_s001.customer.c_custkey 15 100.00 Using index 3 SUBQUERY customer ALL NULL NULL NULL NULL 150 91.00 Using where Warnings: diff --git a/mysql-test/r/subselect2.result b/mysql-test/r/subselect2.result index 4fd303dfd44..2d0bde823f5 100644 --- a/mysql-test/r/subselect2.result +++ b/mysql-test/r/subselect2.result @@ -258,7 +258,7 @@ WHERE date < '2012-12-12 12:12:12' ORDER BY mirror_date ASC ) AS calculated_result; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE ALL NULL NULL NULL NULL 2 +1 PRIMARY ALL NULL NULL NULL NULL 2 2 DERIVED t1 range date date 9 NULL 2 Using index condition; Using where; Rowid-ordered scan; Using filesort SELECT * FROM ( SELECT node_uid, date, mirror_date, @result := 0 AS result @@ -281,7 +281,7 @@ WHERE date < '2012-12-12 12:12:12' ORDER BY mirror_date ASC ) AS calculated_result; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE ALL NULL NULL NULL NULL 2 +1 PRIMARY ALL NULL NULL NULL NULL 2 2 DERIVED t1 range date date 9 NULL 2 Using index condition; Using where; Using filesort SELECT * FROM ( SELECT node_uid, date, mirror_date, @result := 0 AS result diff --git a/mysql-test/r/subselect_no_semijoin.result b/mysql-test/r/subselect_no_semijoin.result index eb92936e85d..19a67578461 100644 --- a/mysql-test/r/subselect_no_semijoin.result +++ b/mysql-test/r/subselect_no_semijoin.result @@ -13,7 +13,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -737,7 +737,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -749,7 +749,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -883,7 +883,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4552,7 +4552,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6107,7 +6107,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6152,7 +6152,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 90d749ea4b4..8f42809ec85 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -99,9 +99,9 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { // print all children, in order - for (int i= 0; i < (int) children.elements(); i++) + for (int i= 0; i < (int) union_members.elements(); i++) { - QPF_select *sel= query->get_select(children.at(i)); + QPF_select *sel= query->get_select(union_members.at(i)); sel->print_explain(query, output, explain_flags); } @@ -123,15 +123,15 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, uint len= 6, lastop= 0; memcpy(table_name_buffer, STRING_WITH_LEN("= NAME_LEN) + if (childno < union_members.elements() || len + lastop >= NAME_LEN) { memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); len+= 4; @@ -184,6 +184,13 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, if (output->send_data(item_list)) return 1; + + for (int i= 0; i < (int) children.elements(); i++) + { + QPF_node *node= query->get_node(children.at(i)); + node->print_explain(query, output, explain_flags); + } + return 0; } @@ -222,7 +229,6 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, if (output->send_data(item_list)) return 1; - return 0; } else { @@ -244,7 +250,6 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, } } - //psergey-TODO: print children here... for (int i= 0; i < (int) children.elements(); i++) { QPF_node *node= query->get_node(children.at(i)); @@ -262,11 +267,17 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl Item *item_null= new Item_null(); //const CHARSET_INFO *cs= system_charset_info; + if (sjm_nest_select_id) + select_id= sjm_nest_select_id; + /* `id` column */ item_list.push_back(new Item_int((int32) select_id)); /* `select_type` column */ - push_str(&item_list, select_type); + if (sjm_nest_select_id) + push_str(&item_list, "MATERIALIZED"); + else + push_str(&item_list, select_type); /* `table` column */ push_string(&item_list, &table_name); @@ -440,6 +451,18 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) str->append(join_buffer_type); break; } + case ET_FIRST_MATCH: + { + if (firstmatch_table_name.length()) + { + str->append("FirstMatch("); + str->append(firstmatch_table_name); + str->append(")"); + } + else + str->append(extra_tag_text[tag]); + break; + } default: str->append(extra_tag_text[tag]); } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 6aa0a5327da..9fb2324bef6 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -23,6 +23,14 @@ public: virtual enum qpf_node_type get_type()= 0; virtual int print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags)=0; + + + Dynamic_array children; + void add_child(int select_no) + { + children.append(select_no); + } + virtual ~QPF_node(){} }; @@ -90,13 +98,6 @@ public: bool using_temporary; bool using_filesort; - /* Child selects. TODO: join this with QPF_union's children? */ - Dynamic_array children; - void add_child(int select_no) - { - children.append(select_no); - } - void print_tabular(select_result_sink *output, uint8 explain_flags//, //bool *printed_anything ); @@ -113,15 +114,26 @@ public: int get_select_id() { - DBUG_ASSERT(children.elements() > 0); - return children.at(0); + DBUG_ASSERT(union_members.elements() > 0); + return union_members.at(0); } - // This has QPF_select children - Dynamic_array children; + /* + Members of the UNION. Note: these are disjoint from UNION's "children". + Example: + + (select * from t1) union + (select * from t2) order by (select col1 from t3 ...) + + here + - select-from-t1 and select-from-t2 are "union members" + - select-from-t3 is the only "child". + + */ + Dynamic_array union_members; void add_select(int select_no) { - children.append(select_no); + union_members.append(select_no); } void push_table_name(List *item_list); int print_explain(QPF_query *query, select_result_sink *output, @@ -204,56 +216,66 @@ public: /* Internals */ public: + /* + 0 means this tab is not inside SJM nest and should use QPF_select's id + other value means the tab is inside an SJM nest. + */ + int sjm_nest_select_id; + /* id and 'select_type' are cared-of by the parent QPF_select */ TABLE *table; - StringBuffer<256> table_name; + StringBuffer<64> table_name; enum join_type type; - StringBuffer<256> used_partitions; + StringBuffer<64> used_partitions; bool used_partitions_set; key_map possible_keys; - StringBuffer<256> possible_keys_str; + StringBuffer<64> possible_keys_str; + /* Not used? */ uint key_no; uint key_length; Dynamic_array extra_tags; //temporary: - StringBuffer<256> key; - StringBuffer<256> key_len; - StringBuffer<256> ref; - bool key_set; - bool key_len_set; - bool ref_set; + bool key_set; /* not set means 'NULL' should be printed */ + StringBuffer<64> key; + + bool key_len_set; /* not set means 'NULL' should be printed */ + StringBuffer<64> key_len; + + bool ref_set; /* not set means 'NULL' should be printed */ + StringBuffer<64> ref; bool rows_set; ha_rows rows; - double filtered; bool filtered_set; + double filtered; /* Various stuff for 'Extra' column*/ uint join_cache_level; // Valid if ET_USING tag is present - StringBuffer<256> quick_info; + StringBuffer<64> quick_info; // Valid if ET_USING_INDEX_FOR_GROUP_BY is present - StringBuffer<256> loose_scan_type; + StringBuffer<64> loose_scan_type; // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD key_map range_checked_map; // valid with ET_USING_MRR - StringBuffer <256> mrr_type; + StringBuffer <64> mrr_type; // valid with ET_USING_JOIN_BUFFER - StringBuffer <256> join_buffer_type; + StringBuffer <64> join_buffer_type; - TABLE *firstmatch_table; + //TABLE *firstmatch_table; + StringBuffer<64> firstmatch_table_name; int print_explain(select_result_sink *output, uint8 explain_flags, uint select_id, const char *select_type, diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 3432e42d32a..91da7526c72 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22308,7 +22308,7 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) sizeof(mrr_str_buf)); if (len > 0) { - res->append(STRING_WITH_LEN("; ")); + //res->append(STRING_WITH_LEN("; ")); res->append(mrr_str_buf, len); } } @@ -22929,7 +22929,7 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, bool distinct, const char *message) { - QPF_select *qp_sel= NULL; + QPF_node *qp_node; const bool on_the_fly= true; JOIN *join= this; /* Legacy: this code used to be a non-member function */ @@ -22951,7 +22951,8 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, */ if (message) { - qp_sel= new QPF_select; + QPF_select *qp_sel; + qp_node= qp_sel= new QPF_select; join->select_lex->set_explain_type(on_the_fly); qp_sel->select_id= join->select_lex->select_number; @@ -22964,6 +22965,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { select_lex->set_explain_type(on_the_fly); QPF_union *qp_union= new QPF_union; + qp_node= qp_union; SELECT_LEX *child; for (child= select_lex->master_unit()->first_select(); child; @@ -22981,13 +22983,14 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { - qp_sel= new QPF_select; + QPF_select *qp_sel; + qp_node= qp_sel= new QPF_select; table_map used_tables=0; if (on_the_fly) join->select_lex->set_explain_type(on_the_fly); - bool printing_materialize_nest= FALSE; + //bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; qp_sel->select_id= join->select_lex->select_number; @@ -23002,7 +23005,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); - printing_materialize_nest= TRUE; + //printing_materialize_nest= TRUE; } TABLE *table=tab->table; @@ -23042,10 +23045,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, QPF_table_access *qpt= new QPF_table_access; qp_sel->add_table(qpt); - + /* id */ - // TODO: this can be '2' in case of SJM nests.. - //qp_sel->select_id= select_id; + if (tab->bush_root_tab) + qpt->sjm_nest_select_id= select_id; + else + qpt->sjm_nest_select_id= 0; /* select_type */ //const char* stype= printing_materialize_nest? "MATERIALIZED" : @@ -23060,7 +23065,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, "", table->derived_select_number); - qpt->table_name.append(table_name_buffer, len, cs); + qpt->table_name.copy(table_name_buffer, len, cs); } else if (tab->bush_children) { @@ -23070,12 +23075,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, sizeof(table_name_buffer)-1, "", ctab->emb_sj_nest->sj_subq_pred->get_identifier()); - qpt->table_name.set(table_name_buffer, len, cs); + qpt->table_name.copy(table_name_buffer, len, cs); } else { TABLE_LIST *real_table= table->pos_in_table_list; - qpt->table_name.append(real_table->alias, strlen(real_table->alias), cs); + qpt->table_name.copy(real_table->alias, strlen(real_table->alias), cs); } /* "partitions" column */ @@ -23433,13 +23438,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, else { qpt->push_extra(ET_FIRST_MATCH); - //TABLE *prev_table=tab->do_firstmatch->table; + TABLE *prev_table=tab->do_firstmatch->table; /* TODO: qpt->firstmatch_table... This must be a reference to another QPF element. Or, its index. */ -#if 0 - extra.append(STRING_WITH_LEN("; FirstMatch(")); + // extra.append(STRING_WITH_LEN("; FirstMatch(")); if (prev_table->derived_select_number) { char namebuf[NAME_LEN]; @@ -23447,12 +23451,11 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, int len= my_snprintf(namebuf, sizeof(namebuf)-1, "", prev_table->derived_select_number); - extra.append(namebuf, len); + qpt->firstmatch_table_name.append(namebuf, len); } else - extra.append(prev_table->pos_in_table_list->alias); - extra.append(STRING_WITH_LEN(")")); -#endif + qpt->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); + //extra.append(STRING_WITH_LEN(")")); } } @@ -23484,20 +23487,17 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, //TODO: can a UNION have subquery children that are not union members? yes, //perhaps... - if (qp_sel) + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) { - for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) { - /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. - */ - if (!(unit->item && unit->item->eliminated)) - { - qp_sel->add_child(unit->first_select()->select_number); - } + qp_node->add_child(unit->first_select()->select_number); } } From 1c6fc3f6b9f7e79331053b5675793d70d0e70af0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 18 Jun 2013 19:21:00 +0400 Subject: [PATCH 09/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring Part 2 of: - Pass more tests - select with subselects is now shown with type=PRIMARY where it used to be (incorrectly) 'SIMPLE' --- mysql-test/r/derived_opt.result | 4 ++-- mysql-test/r/limit_rows_examined.result | 2 +- mysql-test/r/subselect.result | 14 ++++++------- mysql-test/r/subselect4.result | 16 +++++++-------- mysql-test/r/subselect_exists_to_in.result | 14 ++++++------- mysql-test/r/subselect_no_mat.result | 14 ++++++------- mysql-test/r/subselect_no_opts.result | 14 ++++++------- mysql-test/r/subselect_no_scache.result | 14 ++++++------- mysql-test/r/subselect_sj.result | 6 +++--- mysql-test/r/subselect_sj_jcl6.result | 6 +++--- mysql-test/r/table_elim.result | 24 +++++++++++----------- mysql-test/r/view.result | 6 +++--- mysql-test/t/explain_non_select.test | 2 +- sql/opt_qpf.cc | 3 +++ sql/sql_class.h | 5 +++++ sql/sql_parse.cc | 13 ++++++++++++ sql/sql_update.cc | 11 ++++++++++ 17 files changed, 100 insertions(+), 68 deletions(-) diff --git a/mysql-test/r/derived_opt.result b/mysql-test/r/derived_opt.result index 22e2ab8d676..f5e7393591c 100644 --- a/mysql-test/r/derived_opt.result +++ b/mysql-test/r/derived_opt.result @@ -109,8 +109,8 @@ count(*) 2 explain select count(*) from t1 INNER JOIN (SELECT A.E1, A.E2, A.E3 FROM t1 AS A WHERE A.E3 = (SELECT MAX(B.E3) FROM t1 AS B WHERE A.E2 = B.E2)) AS THEMAX ON t1.E1 = THEMAX.E2 AND t1.E1 = t1.E2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE A ALL NULL NULL NULL NULL 2 Using where -1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.A.E2 1 Using where +1 PRIMARY A ALL NULL NULL NULL NULL 2 Using where +1 PRIMARY t1 eq_ref PRIMARY PRIMARY 4 test.A.E2 1 Using where 3 DEPENDENT SUBQUERY B ALL NULL NULL NULL NULL 2 Using where drop table t1; create table t1 (a int); diff --git a/mysql-test/r/limit_rows_examined.result b/mysql-test/r/limit_rows_examined.result index a51798a5883..5dbe01eef4f 100644 --- a/mysql-test/r/limit_rows_examined.result +++ b/mysql-test/r/limit_rows_examined.result @@ -439,7 +439,7 @@ from (select * from t1 where c1 IN (select * from t2 where c2 > ' ' LIMIT ROWS EXAMINED 0)) as tmp LIMIT ROWS EXAMINED 11; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 4 Using where +1 PRIMARY t1 ALL NULL NULL NULL NULL 4 Using where 3 MATERIALIZED t2 ALL NULL NULL NULL NULL 4 Using where select * from (select * from t1 diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result index b3536661125..43c65f779b4 100644 --- a/mysql-test/r/subselect.result +++ b/mysql-test/r/subselect.result @@ -10,7 +10,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -734,7 +734,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -746,7 +746,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -880,7 +880,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4554,7 +4554,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6112,7 +6112,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6157,7 +6157,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/mysql-test/r/subselect4.result b/mysql-test/r/subselect4.result index ff768886434..735ad0d7443 100644 --- a/mysql-test/r/subselect4.result +++ b/mysql-test/r/subselect4.result @@ -93,12 +93,12 @@ DROP TABLE t1,t2; # EXPLAIN SELECT 1 LIKE ( 1 IN ( SELECT 1 ) ); id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization DESCRIBE SELECT 1 LIKE ( 1 IN ( SELECT 1 ) ); id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization # None of the below should crash @@ -520,8 +520,8 @@ id select_type table type possible_keys key key_len ref rows Extra 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) EXECUTE stmt; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL t1_IDX NULL NULL NULL 5 -1 SIMPLE eq_ref distinct_key distinct_key 3 func 1 +1 PRIMARY t1 ALL t1_IDX NULL NULL NULL 5 +1 PRIMARY eq_ref distinct_key distinct_key 3 func 1 2 MATERIALIZED t2 ALL NULL NULL NULL NULL 6 Using where 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) DEALLOCATE PREPARE stmt; @@ -558,8 +558,8 @@ id select_type table type possible_keys key key_len ref rows Extra 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) EXECUTE stmt; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL t1_IDX NULL NULL NULL 5 -1 SIMPLE eq_ref distinct_key distinct_key 3 func 1 +1 PRIMARY t1 ALL t1_IDX NULL NULL NULL 5 +1 PRIMARY eq_ref distinct_key distinct_key 3 func 1 2 MATERIALIZED t2 ALL NULL NULL NULL NULL 6 Using where 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) DEALLOCATE PREPARE stmt; @@ -595,8 +595,8 @@ id select_type table type possible_keys key key_len ref rows Extra 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) EXECUTE stmt; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 5 -1 SIMPLE eq_ref distinct_key distinct_key 3 func 1 +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 +1 PRIMARY eq_ref distinct_key distinct_key 3 func 1 2 MATERIALIZED t2 ALL NULL NULL NULL NULL 6 Using where 2 MATERIALIZED t3 ALL NULL NULL NULL NULL 12 Using where; Using join buffer (flat, BNL join) DEALLOCATE PREPARE stmt; diff --git a/mysql-test/r/subselect_exists_to_in.result b/mysql-test/r/subselect_exists_to_in.result index 579277f11f9..e6a36cc2404 100644 --- a/mysql-test/r/subselect_exists_to_in.result +++ b/mysql-test/r/subselect_exists_to_in.result @@ -14,7 +14,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -738,7 +738,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -750,7 +750,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -884,7 +884,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4560,7 +4560,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6120,7 +6120,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6165,7 +6165,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/mysql-test/r/subselect_no_mat.result b/mysql-test/r/subselect_no_mat.result index ce9a4ad444f..cfdf8585ff9 100644 --- a/mysql-test/r/subselect_no_mat.result +++ b/mysql-test/r/subselect_no_mat.result @@ -17,7 +17,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -741,7 +741,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -753,7 +753,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -887,7 +887,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4556,7 +4556,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6111,7 +6111,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6156,7 +6156,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/mysql-test/r/subselect_no_opts.result b/mysql-test/r/subselect_no_opts.result index e017e67cfe6..701926f2209 100644 --- a/mysql-test/r/subselect_no_opts.result +++ b/mysql-test/r/subselect_no_opts.result @@ -13,7 +13,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -737,7 +737,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -749,7 +749,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -883,7 +883,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4552,7 +4552,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6107,7 +6107,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6152,7 +6152,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/mysql-test/r/subselect_no_scache.result b/mysql-test/r/subselect_no_scache.result index 492321c5d81..56d840abe69 100644 --- a/mysql-test/r/subselect_no_scache.result +++ b/mysql-test/r/subselect_no_scache.result @@ -16,7 +16,7 @@ select (select 2); 2 explain extended select (select 2); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select 2 AS `(select 2)` @@ -740,7 +740,7 @@ id 1 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using index Warnings: Note 1249 Select 2 was reduced during optimization Note 1003 select `test`.`t2`.`id` AS `id` from `test`.`t2` where (`test`.`t2`.`id` = 1) @@ -752,7 +752,7 @@ id 2 EXPLAIN EXTENDED SELECT * FROM t2 WHERE id IN (SELECT 1+(select 1)); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t2 ref id id 5 const 1 100.00 Using where; Using index +1 SIMPLE t2 ref id id 5 const 1 100.00 Using where; Using index Warnings: Note 1249 Select 3 was reduced during optimization Note 1249 Select 2 was reduced during optimization @@ -886,7 +886,7 @@ select 10.5 > ANY (SELECT * from t1); 1 explain extended select (select a+1) from t1; id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Warnings: Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 Note 1249 Select 2 was reduced during optimization @@ -4560,7 +4560,7 @@ int_nokey int_key 0 0 EXPLAIN EXTENDED SELECT * FROM C WHERE `int_key` IN (SELECT `int_nokey`); id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY C ALL NULL NULL NULL NULL 20 100.00 Using where +1 SIMPLE C ALL NULL NULL NULL NULL 20 100.00 Using where DROP TABLE C; # End of test for bug#45061. # @@ -6118,7 +6118,7 @@ FROM t1 AS sq4_alias1 WHERE (sq4_alias1.col_varchar_key + NULL) IS NULL OR sq4_alias1.col_varchar_key = @var3 ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE system NULL NULL NULL NULL 0 const row not found +1 PRIMARY system NULL NULL NULL NULL 0 const row not found 2 DERIVED NULL NULL NULL NULL NULL NULL NULL no matching row in const table SELECT * FROM ( SELECT @var3:=12, sq4_alias1.* FROM t1 AS sq4_alias1 @@ -6163,7 +6163,7 @@ FROM t2 AS c_sq1_alias1 WHERE (c_sq1_alias1.col_int_nokey != @var2 OR c_sq1_alias1.pk != @var3)) ) AS alias3; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 3 DEPENDENT SUBQUERY c_sq1_alias1 system PRIMARY NULL NULL NULL 1 SELECT * FROM ( SELECT sq4_alias1.* FROM t1 AS sq4_alias1 diff --git a/mysql-test/r/subselect_sj.result b/mysql-test/r/subselect_sj.result index 92c626eca61..da09abd7316 100644 --- a/mysql-test/r/subselect_sj.result +++ b/mysql-test/r/subselect_sj.result @@ -941,9 +941,9 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index EXECUTE stmt; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 2 Using index -1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index -1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index +1 PRIMARY t1 index PRIMARY PRIMARY 4 NULL 2 Using index +1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index +1 PRIMARY t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index DROP TABLE t1, t2, t3; DROP VIEW v2, v3; # End of Bug#49198 diff --git a/mysql-test/r/subselect_sj_jcl6.result b/mysql-test/r/subselect_sj_jcl6.result index 04a54c9a371..c1fb3707cdb 100644 --- a/mysql-test/r/subselect_sj_jcl6.result +++ b/mysql-test/r/subselect_sj_jcl6.result @@ -954,9 +954,9 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index EXECUTE stmt; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 2 Using index -1 SIMPLE t2 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index -1 SIMPLE t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index +1 PRIMARY t1 index PRIMARY PRIMARY 4 NULL 2 Using index +1 PRIMARY t2 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index +1 PRIMARY t3 eq_ref PRIMARY PRIMARY 4 test.t1.t1field 1 Using index DROP TABLE t1, t2, t3; DROP VIEW v2, v3; # End of Bug#49198 diff --git a/mysql-test/r/table_elim.result b/mysql-test/r/table_elim.result index 63b3304641b..05fc1e0fd77 100644 --- a/mysql-test/r/table_elim.result +++ b/mysql-test/r/table_elim.result @@ -119,27 +119,27 @@ t2 where id=f.id); This should use one table: explain select id from v1 where id=2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE f const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY f const PRIMARY PRIMARY 4 const 1 Using index This should use one table: explain extended select id from v1 where id in (1,2,3,4); id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE f range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index +1 PRIMARY f range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index Warnings: Note 1276 Field or reference 'test.a2.id' of SELECT #3 was resolved in SELECT #2 Note 1003 select `f`.`id` AS `id` from `test`.`t0` `f` where (`f`.`id` in (1,2,3,4)) This should use facts and a1 tables: explain extended select id from v1 where attr1 between 12 and 14; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE a1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using index condition -1 SIMPLE f eq_ref PRIMARY PRIMARY 4 test.a1.id 1 100.00 Using index +1 PRIMARY a1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using index condition +1 PRIMARY f eq_ref PRIMARY PRIMARY 4 test.a1.id 1 100.00 Using index Warnings: Note 1276 Field or reference 'test.a2.id' of SELECT #3 was resolved in SELECT #2 Note 1003 select `f`.`id` AS `id` from `test`.`t0` `f` join `test`.`t1` `a1` where ((`f`.`id` = `a1`.`id`) and (`a1`.`attr1` between 12 and 14)) This should use facts, a2 and its subquery: explain extended select id from v1 where attr2 between 12 and 14; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE a2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using index condition; Using where -1 SIMPLE f eq_ref PRIMARY PRIMARY 4 test.a2.id 1 100.00 Using index +1 PRIMARY a2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using index condition; Using where +1 PRIMARY f eq_ref PRIMARY PRIMARY 4 test.a2.id 1 100.00 Using index 3 DEPENDENT SUBQUERY t2 ref PRIMARY PRIMARY 4 test.a2.id 2 100.00 Using index Warnings: Note 1276 Field or reference 'test.a2.id' of SELECT #3 was resolved in SELECT #2 @@ -147,27 +147,27 @@ Note 1003 select `f`.`id` AS `id` from `test`.`t0` `f` join `test`.`t2` `a2` whe This should use one table: explain select id from v2 where id=2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE f const PRIMARY PRIMARY 4 const 1 Using index +1 PRIMARY f const PRIMARY PRIMARY 4 const 1 Using index This should use one table: explain extended select id from v2 where id in (1,2,3,4); id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE f range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index +1 PRIMARY f range PRIMARY PRIMARY 4 NULL 4 100.00 Using where; Using index Warnings: Note 1276 Field or reference 'test.f.id' of SELECT #3 was resolved in SELECT #2 Note 1003 select `f`.`id` AS `id` from `test`.`t0` `f` where (`f`.`id` in (1,2,3,4)) This should use facts and a1 tables: explain extended select id from v2 where attr1 between 12 and 14; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE a1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using index condition -1 SIMPLE f eq_ref PRIMARY PRIMARY 4 test.a1.id 1 100.00 Using index +1 PRIMARY a1 range PRIMARY,attr1 attr1 5 NULL 2 100.00 Using index condition +1 PRIMARY f eq_ref PRIMARY PRIMARY 4 test.a1.id 1 100.00 Using index Warnings: Note 1276 Field or reference 'test.f.id' of SELECT #3 was resolved in SELECT #2 Note 1003 select `f`.`id` AS `id` from `test`.`t0` `f` join `test`.`t1` `a1` where ((`f`.`id` = `a1`.`id`) and (`a1`.`attr1` between 12 and 14)) This should use facts, a2 and its subquery: explain extended select id from v2 where attr2 between 12 and 14; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE a2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using index condition -1 SIMPLE f eq_ref PRIMARY PRIMARY 4 test.a2.id 1 100.00 Using where; Using index +1 PRIMARY a2 range PRIMARY,attr2 attr2 5 NULL 5 100.00 Using index condition +1 PRIMARY f eq_ref PRIMARY PRIMARY 4 test.a2.id 1 100.00 Using where; Using index 3 DEPENDENT SUBQUERY t2 ref PRIMARY PRIMARY 4 test.f.id 2 100.00 Using index Warnings: Note 1276 Field or reference 'test.f.id' of SELECT #3 was resolved in SELECT #2 diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 576752f65c9..7fc2fa72805 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -119,7 +119,7 @@ c 12 explain extended select c from v5; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE ALL NULL NULL NULL NULL 5 100.00 +1 PRIMARY ALL NULL NULL NULL NULL 5 100.00 3 DERIVED t1 ALL NULL NULL NULL NULL 5 100.00 Warnings: Note 1003 select (`v2`.`c` + 1) AS `c` from `test`.`v2` @@ -4475,8 +4475,8 @@ f1 f1 1 1 EXPLAIN EXTENDED SELECT * FROM v2 AS a1, v2 AS a2; id select_type table type possible_keys key key_len ref rows filtered Extra -1 SIMPLE ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort -1 SIMPLE ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) +1 PRIMARY ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort +1 PRIMARY ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) 5 DERIVED t1 ALL NULL NULL NULL NULL 2 100.00 3 DERIVED t1 ALL NULL NULL NULL NULL 2 100.00 Warnings: diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 66432a8e4a5..523e041f620 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -2,7 +2,7 @@ # MariaDB tests for EXPLAIN UPDATE/DELETE. # --disable_warnings -drop table if exists t0; +drop table if exists t0, t1; --enable_warnings create table t0 (a int) engine=myisam; diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 8f42809ec85..8e8ca06ed24 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -182,6 +182,7 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, const CHARSET_INFO *cs= system_charset_info; item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + //output->unit.offset_limit_cnt= 0; if (output->send_data(item_list)) return 1; @@ -209,6 +210,8 @@ QPF_select::~QPF_select() int QPF_select::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { + //output->unit.offset_limit_cnt= 0; + if (message) { List item_list; diff --git a/sql/sql_class.h b/sql/sql_class.h index bb5b2c4e775..d84f511e5d3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3401,6 +3401,11 @@ public: void begin_dataset() {} #endif virtual void update_used_tables() {} + + void reset_offset_limit() + { + unit->offset_limit_cnt= 0; + } }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index d6e31f4142c..1513ce61886 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3273,6 +3273,7 @@ end_with_restore_list: result= NULL; } select_lex->set_explain_type(FALSE); + thd->lex->query_plan_footprint= new QPF_query; } else result= new multi_delete(aux_tables, lex->table_count); @@ -3296,6 +3297,13 @@ end_with_restore_list: { MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); } + else + { + result->reset_offset_limit(); + thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + } if (res) result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ @@ -4816,6 +4824,11 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!res) { + /* + Do like the original select_describe did: remove OFFSET from the + top-level LIMIT + */ + result->reset_offset_limit(); thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); } delete thd->lex->query_plan_footprint; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 8b1b042e6df..c6bf984c70c 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1463,6 +1463,8 @@ bool mysql_multi_update(THD *thd, } select_lex->set_explain_type(FALSE); *result= NULL; /* no multi_update object */ + + thd->lex->query_plan_footprint= new QPF_query; } else { @@ -1492,6 +1494,15 @@ bool mysql_multi_update(THD *thd, DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); + + if (explain) + { + //result->reset_offset_limit(); + thd->lex->query_plan_footprint->print_explain(output, thd->lex->describe); + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + } + if (unlikely(res)) (*result)->abort_result_set(); else From 1951d40a88e3b3fc0d2fac2ba3358d887e738485 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 18 Jun 2013 21:08:34 +0400 Subject: [PATCH 10/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Make EXPLAIN UPDATE/DELETE use "Query Plan Footprints", too. --- sql/opt_qpf.cc | 16 ++++- sql/opt_qpf.h | 7 ++- sql/sql_delete.cc | 152 ++++++++++++++++++++++++---------------------- sql/sql_lex.cc | 6 +- sql/sql_lex.h | 18 ++++-- sql/sql_update.cc | 68 ++++++++++++--------- 6 files changed, 152 insertions(+), 115 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 8e8ca06ed24..b3d15a74984 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -12,6 +12,7 @@ QPF_query::QPF_query() { + upd_del_plan= NULL; memset(&unions, 0, sizeof(unions)); memset(&selects, 0, sizeof(selects)); } @@ -19,6 +20,7 @@ QPF_query::QPF_query() QPF_query::~QPF_query() { + delete upd_del_plan; uint i; for (i=0 ; i < MAX_TABLES; i++) delete unions[i]; @@ -70,9 +72,17 @@ void QPF_query::add_node(QPF_node *node) int QPF_query::print_explain(select_result_sink *output, uint8 explain_flags) { - // Start with id=1 - QPF_node *node= get_node(1); - return node->print_explain(this, output, explain_flags); + if (upd_del_plan) + { + upd_del_plan->print_explain(output, explain_flags); + return 0; + } + else + { + // Start with id=1 + QPF_node *node= get_node(1); + return node->print_explain(this, output, explain_flags); + } } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 9fb2324bef6..82d305261e7 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -161,6 +161,9 @@ public: /* This will return a select (even if there is a union with this id) */ QPF_select *get_select(uint select_id); + + /* Delete_plan inherits from Update_plan */ + Update_plan *upd_del_plan; private: QPF_union *unions[MAX_TABLES]; @@ -269,10 +272,10 @@ public: key_map range_checked_map; // valid with ET_USING_MRR - StringBuffer <64> mrr_type; + StringBuffer<64> mrr_type; // valid with ET_USING_JOIN_BUFFER - StringBuffer <64> join_buffer_type; + StringBuffer<64> join_buffer_type; //TABLE *firstmatch_table; StringBuffer<64> firstmatch_table_name; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 390d9e3945a..f6719d3a91b 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -51,8 +51,7 @@ invoked on a running DELETE statement. */ -int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything) +int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags) { if (deleting_all_rows) { @@ -62,37 +61,15 @@ int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, { return 1; } - *printed_anything= true; return 0; } - return Update_plan::print_explain(output, explain_flags, printed_anything); + return Update_plan::print_explain(output, explain_flags); } - -int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything) +void Update_plan::save_query_plan_footprint() { - if (impossible_where) - { - const char *msg= "Impossible where"; - if (print_explain_message_line(output, explain_flags, 1/*select number*/, - "SIMPLE", msg)) - { - return 1; - } - *printed_anything= true; - return 0; - } + select_lex->set_explain_type(TRUE); - select_lex->set_explain_type(FALSE); - /* - Print an EXPLAIN line. We dont have join, so we can't directly use - JOIN::print_explain. - We do have a SELECT_LEX (TODO but how is it useful? it has select_type.. - and that's it?) - */ - - enum join_type jtype; if (select && select->quick) { int quick_type= select->quick->get_type(); @@ -112,35 +89,53 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, jtype= JT_NEXT; } - StringBuffer<128> possible_keys_line; + using_where= test(select && select->cond); + //using_filesort is already set make_possible_keys_line(table, possible_keys, &possible_keys_line); - const char *key_name; - const char *key_len; - - StringBuffer<128> key_str; - StringBuffer<128> key_len_str; - StringBuffer<128> extra_str; - /* Calculate key_len */ if (select && select->quick) { select->quick->add_keys_and_lengths(&key_str, &key_len_str); - key_name= key_str.c_ptr(); - key_len= key_len_str.c_ptr(); } else { - key_name= (index == MAX_KEY)? NULL : table->key_info[index].name; - key_len= NULL; + if (index != MAX_KEY) + { + key_str.append(table->key_info[index].name); + } + // key_len stays NULL } - - if (select && select->cond) - extra_str.append(STRING_WITH_LEN("Using where")); + if (select && select->quick && select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) { - explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &extra_str); + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &mrr_type); + } +} + +int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags) +{ + StringBuffer<64> extra_str; + if (impossible_where) + { + const char *msg= "Impossible where"; + if (print_explain_message_line(output, explain_flags, 1/*select number*/, + "SIMPLE", msg)) + { + return 1; + } + return 0; + } + + if (using_where) + extra_str.append(STRING_WITH_LEN("Using where")); + + if (mrr_type.length() != 0) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(mrr_type); } if (using_filesort) @@ -160,15 +155,14 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, select_lex->type, table->pos_in_table_list->alias, // partitions, - jtype, + (enum join_type) jtype, possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, - key_name, - key_len, + key_str.length()? key_str.c_ptr() : NULL, + key_len_str.length() ? key_len_str.c_ptr() : NULL, NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ select ? select->records : table_rows, extra_str.c_ptr()); - *printed_anything= true; /* psergey-todo: handle all this through saving QPF. @@ -211,10 +205,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; - - Delete_plan query_plan; - query_plan.index= MAX_KEY; - query_plan.using_filesort= FALSE; + + Delete_plan *query_plan = new Delete_plan; + query_plan->index= MAX_KEY; + query_plan->using_filesort= FALSE; DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -238,8 +232,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } thd_proc_info(thd, "init"); table->map=1; - query_plan.select_lex= &thd->lex->select_lex; - query_plan.table= table; + query_plan->select_lex= &thd->lex->select_lex; + query_plan->table= table; if (mysql_prepare_delete(thd, table_list, &conds)) DBUG_RETURN(TRUE); @@ -314,7 +308,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ha_rows const maybe_deleted= table->file->stats.records; DBUG_PRINT("debug", ("Trying to use delete_all_rows()")); - query_plan.set_delete_all_rows(maybe_deleted); + query_plan->set_delete_all_rows(maybe_deleted); if (thd->lex->describe) goto exit_without_my_ok; @@ -344,7 +338,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (result == Item::COND_FALSE) // Impossible where { limit= 0; - query_plan.set_impossible_where(); + query_plan->set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; } @@ -372,7 +366,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); if ((select && select->check_quick(thd, safe_update, limit)) || !limit) { - query_plan.set_impossible_where(); + query_plan->set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; @@ -413,20 +407,22 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered") - query_plan.using_filesort= FALSE; - query_plan.index= MAX_KEY; + query_plan->using_filesort= FALSE; + query_plan->index= MAX_KEY; } else - query_plan.index= get_index_for_order(order, table, select, limit, - &query_plan.using_filesort, - &reverse); + query_plan->index= get_index_for_order(order, table, select, limit, + &query_plan->using_filesort, + &reverse); } - query_plan.select= select; - query_plan.possible_keys= table->quick_keys; - query_plan.table_rows= table->file->stats.records; - thd->lex->upd_del_plan= &query_plan; + query_plan->select= select; + query_plan->possible_keys= table->quick_keys; + query_plan->table_rows= table->file->stats.records; + thd->lex->query_plan_footprint= new QPF_query; + thd->lex->query_plan_footprint->upd_del_plan= query_plan; + /* Ok, we have generated a query plan for the DELETE. - if we're running EXPLAIN DELETE, goto produce explain output @@ -435,11 +431,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->lex->describe) goto exit_without_my_ok; + query_plan->save_query_plan_footprint(); thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", dbug_serve_apcs(thd, 1);); - if (query_plan.using_filesort) + if (query_plan->using_filesort) { ha_rows examined_rows; ha_rows found_rows; @@ -447,7 +444,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SORT_FIELD *sortorder; { - DBUG_ASSERT(query_plan.index == MAX_KEY); + DBUG_ASSERT(query_plan->index == MAX_KEY); table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE), MYF(MY_FAE | MY_ZEROFILL | MY_THREAD_SPECIFIC)); @@ -483,7 +480,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, thd->apc_target.disable(); DBUG_RETURN(TRUE); } - if (query_plan.index == MAX_KEY || (select && select->quick)) + if (query_plan->index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) { @@ -494,7 +491,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } } else - init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan->index, reverse); init_ftfuncs(thd, select_lex, 1); thd_proc_info(thd, "updating"); @@ -587,6 +584,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, (void) table->file->extra(HA_EXTRA_NORMAL); thd->apc_target.disable(); + if (thd->lex->query_plan_footprint) + { + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + } cleanup: /* Invalidate the table in the query cache if something changed. This must @@ -650,21 +652,25 @@ cleanup: /* Special exits */ exit_without_my_ok: - thd->lex->upd_del_plan= &query_plan; - + query_plan->save_query_plan_footprint(); + thd->lex->query_plan_footprint->upd_del_plan= query_plan; + + select_send *result; - bool printed_anything; if (!(result= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + int err2= thd->lex->query_plan_footprint->print_explain(result, 0); if (err2) result->abort_result_set(); else result->send_eof(); + + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; delete select; free_underlaid_joins(thd, select_lex); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b2d4ca13823..bc16f61b77e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,7 +448,6 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; - lex->upd_del_plan= NULL; lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); @@ -2561,7 +2560,6 @@ LEX::LEX() INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); - upd_del_plan= NULL; } @@ -4176,11 +4174,11 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { - if (upd_del_plan) + /* if (upd_del_plan) { upd_del_plan->print_explain(output, explain_flags, printed_anything); return 0; - } + }*/ //int res= unit.print_explain(output, explain_flags, printed_anything); //psergey-todo: here, we should make Query Plan Footprint, and then produce diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d2a8b59a593..db944ab83ec 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2378,6 +2378,7 @@ public: SQL_SELECT *select; uint index; ha_rows table_rows; /* Use if select==NULL */ + /* Top-level select_lex. Most of its fields are not used, we need it only to get to the subqueries. @@ -2390,11 +2391,20 @@ public: /* Set this plan to be a plan to do nothing because of impossible WHRE*/ void set_impossible_where() { impossible_where= true; } - virtual int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); + virtual int print_explain(select_result_sink *output, uint8 explain_flags); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} + + void save_query_plan_footprint(); + /* Query Plan Footprint fields */ + // cant use it here: enum join_type + int jtype; + bool using_where; + StringBuffer<128> possible_keys_line; + StringBuffer<128> key_str; + StringBuffer<128> key_len_str; + StringBuffer<64> mrr_type; }; @@ -2414,8 +2424,7 @@ public: deleting_all_rows= true; table_rows= rows_arg; } - int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); + int print_explain(select_result_sink *output, uint8 explain_flags); }; @@ -2432,7 +2441,6 @@ struct LEX: public Query_tables_list SELECT_LEX *all_selects_list; /* For single-table DELETE: its query plan */ - Update_plan *upd_del_plan; QPF_query *query_plan_footprint; char *length,*dec,*change; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index c6bf984c70c..a694eb3d360 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -276,9 +276,7 @@ int mysql_update(THD *thd, ulonglong id; List all_fields; killed_state killed_status= NOT_KILLED; - Update_plan query_plan; - query_plan.index= MAX_KEY; - query_plan.using_filesort= FALSE; + Update_plan *query_plan; bool apc_target_enabled= false; // means was enabled *by code this function* DBUG_ENTER("mysql_update"); @@ -317,9 +315,12 @@ int mysql_update(THD *thd, /* Calculate "table->covering_keys" based on the WHERE */ table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); - - query_plan.select_lex= &thd->lex->select_lex; - query_plan.table= table; + + query_plan= new Update_plan; + query_plan->index= MAX_KEY; + query_plan->using_filesort= FALSE; + query_plan->select_lex= &thd->lex->select_lex; + query_plan->table= table; #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Force privilege re-checking for views after they have been opened. */ want_privilege= (table_list->view ? UPDATE_ACL : @@ -378,7 +379,7 @@ int mysql_update(THD *thd, if (cond_value == Item::COND_FALSE) { limit= 0; // Impossible WHERE - query_plan.set_impossible_where(); + query_plan->set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; } @@ -411,7 +412,7 @@ int mysql_update(THD *thd, if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { - query_plan.set_impossible_where(); + query_plan->set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; @@ -453,16 +454,16 @@ int mysql_update(THD *thd, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered"): Ok to use with key field UPDATE need_sort= FALSE; - query_plan.index= MAX_KEY; + query_plan->index= MAX_KEY; used_key_is_modified= FALSE; } else { - query_plan.index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + query_plan->index= get_index_for_order(order, table, select, limit, + &need_sort, &reverse); if (select && select->quick) { - DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); + DBUG_ASSERT(need_sort || query_plan->index == select->quick->index); used_key_is_modified= (!select->quick->unique_key_range() && select->quick->is_keys_used(table->write_set)); } @@ -470,11 +471,11 @@ int mysql_update(THD *thd, { if (need_sort) { // Assign table scan index to check below for modified key fields: - query_plan.index= table->file->key_used_on_scan; + query_plan->index= table->file->key_used_on_scan; } - if (query_plan.index != MAX_KEY) + if (query_plan->index != MAX_KEY) { // Check if we are modifying a key that we are used to search with: - used_key_is_modified= is_key_used(table, query_plan.index, table->write_set); + used_key_is_modified= is_key_used(table, query_plan->index, table->write_set); } } } @@ -484,10 +485,11 @@ int mysql_update(THD *thd, - Save the decisions in the query plan - if we're running EXPLAIN UPDATE, get out */ - query_plan.select= select; - query_plan.possible_keys= table->quick_keys; - query_plan.table_rows= table->file->stats.records; - thd->lex->upd_del_plan= &query_plan; + query_plan->select= select; + query_plan->possible_keys= table->quick_keys; + query_plan->table_rows= table->file->stats.records; + thd->lex->query_plan_footprint= new QPF_query; + thd->lex->query_plan_footprint->upd_del_plan= query_plan; /* Ok, we have generated a query plan for the UPDATE. @@ -496,7 +498,8 @@ int mysql_update(THD *thd, */ if (thd->lex->describe) goto exit_without_my_ok; - + + query_plan->save_query_plan_footprint(); thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", @@ -515,8 +518,8 @@ int mysql_update(THD *thd, DBUG_ASSERT(table->read_set == &table->def_read_set); DBUG_ASSERT(table->write_set == &table->def_write_set); - if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index)) - table->add_read_columns_used_by_index(query_plan.index); + if (query_plan->index < MAX_KEY && old_covering_keys.is_set(query_plan->index)) + table->add_read_columns_used_by_index(query_plan->index); else table->use_all_columns(); @@ -582,13 +585,13 @@ int mysql_update(THD *thd, Full index scan must be started with init_read_record_idx */ - if (query_plan.index == MAX_KEY || (select && select->quick)) + if (query_plan->index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) goto err; } else - init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan->index, reverse); thd_proc_info(thd, "Searching rows for update"); ha_rows tmp_limit= limit; @@ -1002,6 +1005,12 @@ err: if (apc_target_enabled) thd->apc_target.disable(); + if (thd->lex->query_plan_footprint) + { + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + } + delete select; free_underlaid_joins(thd, select_lex); table->disable_keyread(); @@ -1010,22 +1019,25 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); - thd->lex->upd_del_plan= &query_plan; - + query_plan->save_query_plan_footprint(); + thd->lex->query_plan_footprint->upd_del_plan= query_plan; + select_send *result; - bool printed_anything; if (!(result= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + int err2= thd->lex->query_plan_footprint->print_explain(result, 0); if (err2) result->abort_result_set(); else result->send_eof(); + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + delete select; free_underlaid_joins(thd, select_lex); DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); From 6efa1d8c248e1068829af9b530b6c47747ac3310 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 19 Jun 2013 18:47:31 +0400 Subject: [PATCH 11/64] Fixed comments --- sql/opt_qpf.h | 72 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 82d305261e7..d7e4caa1c97 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -19,37 +19,44 @@ class QPF_node : public Sql_alloc { public: enum qpf_node_type {QPF_UNION, QPF_SELECT}; - virtual enum qpf_node_type get_type()= 0; - virtual int print_explain(QPF_query *query, select_result_sink *output, - uint8 explain_flags)=0; + virtual int get_select_id()= 0; + + /* + A node may have children nodes. When a node's QPF (Query Plan Footprint) is + created, children nodes may not yet have QPFs. This is why we store ids. + */ Dynamic_array children; void add_child(int select_no) { children.append(select_no); } + virtual int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags)=0; + virtual ~QPF_node(){} }; -/* - Nesting. - QPF_select may have children QPF_select-s. - (these can be FROM-subqueries, or subqueries from other clauses) - - As for unions, the standard approach is: - - UNION node can be where the select node can be; - - the union has a select that retrieves results from temptable (a special - kind of child) - - and it has regular children selects that are merged into the union. - -*/ - class QPF_table_access; + +/* + Query Plan Footprint of a SELECT. + + A select can be: + - a degenerate case. In this case, message!=NULL, and it contains a + description of what kind of degenerate case it is (e.g. "Impossible + WHERE"). + - a join. Here join_tabs has an array of JOIN_TAB query plan footprints. + + In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation. + In both cases, a select may have children selects (see QPF_node) +*/ + class QPF_select : public QPF_node { /*Construction interface */ @@ -76,9 +83,11 @@ public: } public: - int select_id; /* -1 means NULL. */ + int select_id; const char *select_type; + int get_select_id() { return select_id; } + /* If message != NULL, this is a degenerate join plan, and all subsequent members have no info @@ -86,10 +95,8 @@ public: const char *message; /* - According to the discussion: this should be an array of "table - descriptors". - - As for SJ-Materialization. Start_materialize/end_materialize markers? + A flat array of Query Plan Footprints. The order is "just like EXPLAIN + would print them". */ QPF_table_access** join_tabs; uint n_join_tabs; @@ -107,6 +114,12 @@ public: }; +/* + Query Plan Footprint of a UNION. + + A UNION may or may not have "Using filesort". +*/ + class QPF_union : public QPF_node { public: @@ -118,16 +131,15 @@ public: return union_members.at(0); } /* - Members of the UNION. Note: these are disjoint from UNION's "children". + Members of the UNION. Note: these are different from UNION's "children". Example: (select * from t1) union (select * from t2) order by (select col1 from t3 ...) here - - select-from-t1 and select-from-t2 are "union members" + - select-from-t1 and select-from-t2 are "union members", - select-from-t3 is the only "child". - */ Dynamic_array union_members; @@ -145,7 +157,7 @@ public: /* - This is the whole query. + Query Plan Footprint for a query (i.e. a statement) */ class QPF_query : public Sql_alloc @@ -153,8 +165,8 @@ class QPF_query : public Sql_alloc public: QPF_query(); ~QPF_query(); + /* Add a new node */ void add_node(QPF_node *node); - int print_explain(select_result_sink *output, uint8 explain_flags); /* This will return a select, or a union */ QPF_node *get_node(uint select_id); @@ -165,6 +177,8 @@ public: /* Delete_plan inherits from Update_plan */ Update_plan *upd_del_plan; + /* Produce a tabular EXPLAIN output */ + int print_explain(select_result_sink *output, uint8 explain_flags); private: QPF_union *unions[MAX_TABLES]; QPF_select *selects[MAX_TABLES]; @@ -212,6 +226,9 @@ enum Extra_tag }; +/* + Query Plan Footprint for a JOIN_TAB. +*/ class QPF_table_access : public Sql_alloc { public: @@ -287,8 +304,5 @@ private: void append_tag_name(String *str, enum Extra_tag tag); }; -// Update_plan and Delete_plan belong to this kind of structures, too. - // TODO: should Update_plan inherit from QPF_table_access? - From 0a560289aaa8c17e3f1930871088f43efce641e8 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 20 Jun 2013 15:15:24 +0400 Subject: [PATCH 12/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Introduce back QueryPlan/QueryPlanFootprint separation for single-table UPDATEs/DELETEs - Create an empty QueryPlanFootprint for all kinds of queries --- sql/opt_qpf.cc | 96 ++++++++++++++++++++--- sql/opt_qpf.h | 92 ++++++++++++++++++++-- sql/sp_head.cc | 3 + sql/sql_delete.cc | 187 ++++++++++++++++----------------------------- sql/sql_lex.cc | 2 + sql/sql_lex.h | 35 ++++----- sql/sql_parse.cc | 24 ++++-- sql/sql_prepare.cc | 4 + sql/sql_update.cc | 79 +++++++------------ 9 files changed, 310 insertions(+), 212 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index b3d15a74984..819c0e6cc22 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -74,7 +74,7 @@ int QPF_query::print_explain(select_result_sink *output, { if (upd_del_plan) { - upd_del_plan->print_explain(output, explain_flags); + upd_del_plan->print_explain(this, output, explain_flags); return 0; } else @@ -196,12 +196,20 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, if (output->send_data(item_list)) return 1; + return print_explain_for_children(query, output, explain_flags); +} + + +int QPF_node::print_explain_for_children(QPF_query *query, + select_result_sink *output, + uint8 explain_flags) +{ for (int i= 0; i < (int) children.elements(); i++) { QPF_node *node= query->get_node(children.at(i)); - node->print_explain(query, output, explain_flags); + if (node->print_explain(query, output, explain_flags)) + return 1; } - return 0; } @@ -263,12 +271,7 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, } } - for (int i= 0; i < (int) children.elements(); i++) - { - QPF_node *node= query->get_node(children.at(i)); - node->print_explain(query, output, explain_flags); - } - return 0; + return print_explain_for_children(query, output, explain_flags); } @@ -482,3 +485,78 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) } +int QPF_delete::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + if (deleting_all_rows) + { + const char *msg= "Deleting all rows"; + int res= print_explain_message_line(output, explain_flags, + 1 /*select number*/, + "SIMPLE", msg); + return res; + + } + else + { + return QPF_update::print_explain(query, output, explain_flags); + } +} + + +int QPF_update::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + StringBuffer<64> extra_str; + if (impossible_where) + { + const char *msg= "Impossible where"; + int res= print_explain_message_line(output, explain_flags, + 1 /*select number*/, + "SIMPLE", msg); + return res; + } + + if (using_where) + extra_str.append(STRING_WITH_LEN("Using where")); + + if (mrr_type.length() != 0) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(mrr_type); + } + + if (using_filesort) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using filesort")); + } + + /* + Single-table DELETE commands do not do "Using temporary". + "Using index condition" is also not possible (which is an unjustified limitation) + */ + + print_explain_row(output, explain_flags, + 1, /* id */ + "SIMPLE", + table_name.c_ptr(), + // partitions, + jtype, + possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, + key_str.length()? key_str.c_ptr() : NULL, + key_len_str.length() ? key_len_str.c_ptr() : NULL, + NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ + rows, + extra_str.c_ptr()); + + return print_explain_for_children(query, output, explain_flags); +} + +void delete_qpf_query(QPF_query * query) +{ + delete query; +} + diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index d7e4caa1c97..6e0bb40c576 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -18,7 +18,7 @@ class QPF_query; class QPF_node : public Sql_alloc { public: - enum qpf_node_type {QPF_UNION, QPF_SELECT}; + enum qpf_node_type {QPF_UNION, QPF_SELECT, QPF_UPDATE, QPF_DELETE }; virtual enum qpf_node_type get_type()= 0; @@ -36,7 +36,9 @@ public: virtual int print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags)=0; - + + int print_explain_for_children(QPF_query *query, select_result_sink *output, + uint8 explain_flags); virtual ~QPF_node(){} }; @@ -155,9 +157,42 @@ public: bool using_filesort; }; +class QPF_delete; + /* - Query Plan Footprint for a query (i.e. a statement) + Query Plan Footprint (QPF) for a query (i.e. a statement). + + This should be able to survive when the query plan was deleted. Currently, + we do not intend for it survive until after query's MEM_ROOT is freed. It + does surivive freeing of query's items. + + For reference, the process of post-query cleanup is as follows: + + >dispatch_command + | >mysql_parse + | | ... + | | lex_end() + | | ... + | | >THD::cleanup_after_query + | | | ... + | | | free_items() + | | | ... + | | dispatch_command + + That is, the order of actions is: + - free query's Items + - write to slow query log + - free query's MEM_ROOT + */ class QPF_query : public Sql_alloc @@ -174,8 +209,8 @@ public: /* This will return a select (even if there is a union with this id) */ QPF_select *get_select(uint select_id); - /* Delete_plan inherits from Update_plan */ - Update_plan *upd_del_plan; + /* QPF_delete inherits from QPF_update */ + QPF_update *upd_del_plan; /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); @@ -304,5 +339,50 @@ private: void append_tag_name(String *str, enum Extra_tag tag); }; -// TODO: should Update_plan inherit from QPF_table_access? + +/* + Query Plan Footprint for an UPDATE statement +*/ + +class QPF_update : public QPF_node +{ +public: + virtual enum qpf_node_type get_type() { return QPF_UPDATE; } + virtual int get_select_id() { return 1; /* always root */ } + + bool impossible_where; + StringBuffer<64> table_name; + + enum join_type jtype; + StringBuffer<128> possible_keys_line; + StringBuffer<128> key_str; + StringBuffer<128> key_len_str; + StringBuffer<64> mrr_type; + + bool using_where; + ha_rows rows; + + bool using_filesort; + + virtual int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + +/* + Query Plan Footprint for a DELETE statement +*/ + +class QPF_delete: public QPF_update +{ +public: + bool deleting_all_rows; + + virtual enum qpf_node_type get_type() { return QPF_DELETE; } + virtual int get_select_id() { return 1; /* always root */ } + + virtual int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); +}; + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index f0a87673857..3cc3a3893bb 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3006,6 +3006,9 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, else if (! thd->in_sub_stmt) thd->mdl_context.release_statement_locks(); } + + delete_qpf_query(m_lex->query_plan_footprint); + m_lex->query_plan_footprint= NULL; if (m_lex->query_tables_own_last) { diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index f6719d3a91b..602831829d7 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -51,25 +51,44 @@ invoked on a running DELETE statement. */ -int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags) +void Delete_plan::save_query_plan_footprint(QPF_query *query) { + QPF_delete* qpf= new QPF_delete; + if (deleting_all_rows) { - const char *msg= "Deleting all rows"; - if (print_explain_message_line(output, explain_flags, 1/*select number*/, - "SIMPLE", msg)) - { - return 1; - } - return 0; + qpf->deleting_all_rows= true; } - return Update_plan::print_explain(output, explain_flags); + else + { + Update_plan::save_query_plan_footprint_intern(qpf); + } + + query->upd_del_plan= qpf; } -void Update_plan::save_query_plan_footprint() -{ - select_lex->set_explain_type(TRUE); +void Update_plan::save_query_plan_footprint(QPF_query *query) +{ + QPF_update* qpf= new QPF_update; + save_query_plan_footprint_intern(qpf); + query->upd_del_plan= qpf; +} + + +void Update_plan::save_query_plan_footprint_intern(QPF_update *qpf) +{ + qpf->table_name.append(table->pos_in_table_list->alias); + if (impossible_where) + { + qpf->impossible_where= true; + return; + } + + // TODO: do we need the following: select_type + //select_lex->set_explain_type(TRUE); + + /* Set jtype */ if (select && select->quick) { int quick_type= select->quick->get_type(); @@ -77,106 +96,46 @@ void Update_plan::save_query_plan_footprint() (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) - jtype= JT_INDEX_MERGE; + qpf->jtype= JT_INDEX_MERGE; else - jtype= JT_RANGE; + qpf->jtype= JT_RANGE; } else { if (index == MAX_KEY) - jtype= JT_ALL; + qpf->jtype= JT_ALL; else - jtype= JT_NEXT; + qpf->jtype= JT_NEXT; } - using_where= test(select && select->cond); + qpf->using_where= test(select && select->cond); + qpf->using_filesort= using_filesort; + //using_filesort is already set - make_possible_keys_line(table, possible_keys, &possible_keys_line); + make_possible_keys_line(table, possible_keys, &qpf->possible_keys_line); /* Calculate key_len */ if (select && select->quick) { - select->quick->add_keys_and_lengths(&key_str, &key_len_str); + select->quick->add_keys_and_lengths(&qpf->key_str, &qpf->key_len_str); } else { if (index != MAX_KEY) { - key_str.append(table->key_info[index].name); + qpf->key_str.append(table->key_info[index].name); } // key_len stays NULL } + qpf->rows= select ? select->records : table_rows; if (select && select->quick && select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) { - explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &mrr_type); + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &qpf->mrr_type); } } -int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags) -{ - StringBuffer<64> extra_str; - if (impossible_where) - { - const char *msg= "Impossible where"; - if (print_explain_message_line(output, explain_flags, 1/*select number*/, - "SIMPLE", msg)) - { - return 1; - } - return 0; - } - - if (using_where) - extra_str.append(STRING_WITH_LEN("Using where")); - - if (mrr_type.length() != 0) - { - if (extra_str.length() !=0) - extra_str.append(STRING_WITH_LEN("; ")); - extra_str.append(mrr_type); - } - - if (using_filesort) - { - if (extra_str.length() !=0) - extra_str.append(STRING_WITH_LEN("; ")); - extra_str.append(STRING_WITH_LEN("Using filesort")); - } - - /* - Single-table DELETE commands do not do "Using temporary". - "Using index condition" is also not possible (which is an unjustified limitation) - */ - - print_explain_row(output, explain_flags, - 1, /* id */ - select_lex->type, - table->pos_in_table_list->alias, - // partitions, - (enum join_type) jtype, - possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, - key_str.length()? key_str.c_ptr() : NULL, - key_len_str.length() ? key_len_str.c_ptr() : NULL, - NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ - select ? select->records : table_rows, - extra_str.c_ptr()); - - /* - psergey-todo: handle all this through saving QPF. - - for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) - { - if (unit->print_explain(output, explain_flags, printed_anything)) - return 1; - } - */ - return 0; -} - /** Implement DELETE SQL word. @@ -205,10 +164,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; - - Delete_plan *query_plan = new Delete_plan; - query_plan->index= MAX_KEY; - query_plan->using_filesort= FALSE; + + Delete_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -232,8 +191,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } thd_proc_info(thd, "init"); table->map=1; - query_plan->select_lex= &thd->lex->select_lex; - query_plan->table= table; + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; if (mysql_prepare_delete(thd, table_list, &conds)) DBUG_RETURN(TRUE); @@ -308,7 +267,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ha_rows const maybe_deleted= table->file->stats.records; DBUG_PRINT("debug", ("Trying to use delete_all_rows()")); - query_plan->set_delete_all_rows(maybe_deleted); + query_plan.set_delete_all_rows(maybe_deleted); if (thd->lex->describe) goto exit_without_my_ok; @@ -338,7 +297,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (result == Item::COND_FALSE) // Impossible where { limit= 0; - query_plan->set_impossible_where(); + query_plan.set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; } @@ -366,7 +325,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); if ((select && select->check_quick(thd, safe_update, limit)) || !limit) { - query_plan->set_impossible_where(); + query_plan.set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; @@ -407,22 +366,19 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered") - query_plan->using_filesort= FALSE; - query_plan->index= MAX_KEY; + query_plan.using_filesort= FALSE; + query_plan.index= MAX_KEY; } else - query_plan->index= get_index_for_order(order, table, select, limit, - &query_plan->using_filesort, - &reverse); + query_plan.index= get_index_for_order(order, table, select, limit, + &query_plan.using_filesort, + &reverse); } - query_plan->select= select; - query_plan->possible_keys= table->quick_keys; - query_plan->table_rows= table->file->stats.records; + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; - thd->lex->query_plan_footprint= new QPF_query; - thd->lex->query_plan_footprint->upd_del_plan= query_plan; - /* Ok, we have generated a query plan for the DELETE. - if we're running EXPLAIN DELETE, goto produce explain output @@ -431,12 +387,13 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->lex->describe) goto exit_without_my_ok; - query_plan->save_query_plan_footprint(); + query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); thd->apc_target.enable(); + DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", dbug_serve_apcs(thd, 1);); - if (query_plan->using_filesort) + if (query_plan.using_filesort) { ha_rows examined_rows; ha_rows found_rows; @@ -444,7 +401,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, SORT_FIELD *sortorder; { - DBUG_ASSERT(query_plan->index == MAX_KEY); + DBUG_ASSERT(query_plan.index == MAX_KEY); table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE), MYF(MY_FAE | MY_ZEROFILL | MY_THREAD_SPECIFIC)); @@ -480,7 +437,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, thd->apc_target.disable(); DBUG_RETURN(TRUE); } - if (query_plan->index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) { @@ -491,7 +448,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } } else - init_read_record_idx(&info, thd, table, 1, query_plan->index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); init_ftfuncs(thd, select_lex, 1); thd_proc_info(thd, "updating"); @@ -584,11 +541,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, (void) table->file->extra(HA_EXTRA_NORMAL); thd->apc_target.disable(); - if (thd->lex->query_plan_footprint) - { - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - } cleanup: /* Invalidate the table in the query cache if something changed. This must @@ -652,9 +604,7 @@ cleanup: /* Special exits */ exit_without_my_ok: - query_plan->save_query_plan_footprint(); - thd->lex->query_plan_footprint->upd_del_plan= query_plan; - + query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); select_send *result; if (!(result= new select_send())) @@ -669,9 +619,6 @@ exit_without_my_ok: else result->send_eof(); - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - delete select; free_underlaid_joins(thd, select_lex); //table->set_keyread(false); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index bc16f61b77e..6ad7b288a4b 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -447,6 +447,8 @@ void lex_start(THD *thd) DBUG_ENTER("lex_start"); lex->thd= lex->unit.thd= thd; + + lex->query_plan_footprint= NULL; lex->context_stack.empty(); lex->unit.init_query(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index db944ab83ec..47d01f2e137 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -619,6 +619,8 @@ class select_union; class Procedure; class QPF_query; +void delete_qpf_query(QPF_query * query); + class st_select_lex_unit: public st_select_lex_node { protected: TABLE_LIST result_table_list; @@ -2360,15 +2362,18 @@ protected: LEX *m_lex; }; + class Delete_plan; class SQL_SELECT; +class QPF_query; +class QPF_update; + /* Query plan of a single-table UPDATE. (This is actually a plan for single-table DELETE also) - - TODO: this should be a query plan footprint, not a query plan. */ + class Update_plan { protected: @@ -2378,10 +2383,9 @@ public: SQL_SELECT *select; uint index; ha_rows table_rows; /* Use if select==NULL */ - - /* + /* Top-level select_lex. Most of its fields are not used, we need it only to - get to the subqueries. + get to the subqueries. */ SELECT_LEX *select_lex; @@ -2391,20 +2395,11 @@ public: /* Set this plan to be a plan to do nothing because of impossible WHRE*/ void set_impossible_where() { impossible_where= true; } - virtual int print_explain(select_result_sink *output, uint8 explain_flags); + void save_query_plan_footprint(QPF_query *query); + void save_query_plan_footprint_intern(QPF_update *qpf); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} - - void save_query_plan_footprint(); - /* Query Plan Footprint fields */ - // cant use it here: enum join_type - int jtype; - bool using_where; - StringBuffer<128> possible_keys_line; - StringBuffer<128> key_str; - StringBuffer<128> key_len_str; - StringBuffer<64> mrr_type; }; @@ -2424,11 +2419,11 @@ public: deleting_all_rows= true; table_rows= rows_arg; } - int print_explain(select_result_sink *output, uint8 explain_flags); + + void save_query_plan_footprint(QPF_query *query); }; -class QPF_query; /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2439,8 +2434,8 @@ struct LEX: public Query_tables_list SELECT_LEX *current_select; /* list of all SELECT_LEX */ SELECT_LEX *all_selects_list; - - /* For single-table DELETE: its query plan */ + + /* Query Plan Footprint of a currently running select */ QPF_query *query_plan_footprint; char *length,*dec,*change; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 1513ce61886..474d3889b53 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -598,6 +598,10 @@ static void handle_bootstrap_impl(THD *thd) #if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif + // + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + // if (bootstrap_error) break; @@ -1484,6 +1488,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->update_all_stats(); log_slow_statement(thd); + /* psergey-todo: this is the place we could print EXPLAIN to slow query log */ thd_proc_info(thd, "cleaning up"); thd->reset_query(); @@ -1518,6 +1523,9 @@ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); + delete thd->lex->query_plan_footprint; + thd->lex->query_plan_footprint= NULL; + /* The following should never be true with our current code base, but better to keep this here so we don't accidently try to log a @@ -1526,6 +1534,7 @@ void log_slow_statement(THD *thd) if (unlikely(thd->in_sub_stmt)) DBUG_VOID_RETURN; // Don't set time for sub stmt + /* Follow the slow log filter configuration. */ if (!thd->enable_slow_log || !(thd->variables.log_slow_filter & thd->query_plan_flags)) @@ -2178,6 +2187,9 @@ mysql_execute_command(THD *thd) /* Release metadata locks acquired in this transaction. */ thd->mdl_context.release_transactional_locks(); } + + DBUG_ASSERT(!thd->lex->query_plan_footprint); + thd->lex->query_plan_footprint= new QPF_query; #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) @@ -3273,7 +3285,7 @@ end_with_restore_list: result= NULL; } select_lex->set_explain_type(FALSE); - thd->lex->query_plan_footprint= new QPF_query; + //thd->lex->query_plan_footprint= new QPF_query; } else result= new multi_delete(aux_tables, lex->table_count); @@ -3301,8 +3313,8 @@ end_with_restore_list: { result->reset_offset_limit(); thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; + //delete thd->lex->query_plan_footprint; + //thd->lex->query_plan_footprint= NULL; } if (res) @@ -4819,7 +4831,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); - thd->lex->query_plan_footprint= new QPF_query; + //thd->lex->query_plan_footprint= new QPF_query; res= mysql_explain_union(thd, &thd->lex->unit, result); if (!res) @@ -4831,8 +4843,8 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) result->reset_offset_limit(); thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); } - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; + //delete thd->lex->query_plan_footprint; + //thd->lex->query_plan_footprint= NULL; //psergey-todo: here, produce the EXPLAIN output. // mysql_explain_union() itself is only responsible for calling diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 71b4a0cf817..d81070b7406 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2486,6 +2486,7 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) object and because of this can be used in different threads. */ lex->thd= thd; + DBUG_ASSERT(!lex->query_plan_footprint); if (lex->empty_field_list_on_rset) { @@ -3925,6 +3926,9 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (! cursor) cleanup_stmt(); + //psergey: TODO the "EXECUTE problem" is here + delete_qpf_query(thd->lex->query_plan_footprint); + thd->lex->query_plan_footprint= NULL; thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index a694eb3d360..ce56a725567 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -276,7 +276,9 @@ int mysql_update(THD *thd, ulonglong id; List all_fields; killed_state killed_status= NOT_KILLED; - Update_plan *query_plan; + Update_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; bool apc_target_enabled= false; // means was enabled *by code this function* DBUG_ENTER("mysql_update"); @@ -315,12 +317,9 @@ int mysql_update(THD *thd, /* Calculate "table->covering_keys" based on the WHERE */ table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); - - query_plan= new Update_plan; - query_plan->index= MAX_KEY; - query_plan->using_filesort= FALSE; - query_plan->select_lex= &thd->lex->select_lex; - query_plan->table= table; + + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Force privilege re-checking for views after they have been opened. */ want_privilege= (table_list->view ? UPDATE_ACL : @@ -379,7 +378,7 @@ int mysql_update(THD *thd, if (cond_value == Item::COND_FALSE) { limit= 0; // Impossible WHERE - query_plan->set_impossible_where(); + query_plan.set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; } @@ -412,7 +411,7 @@ int mysql_update(THD *thd, if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { - query_plan->set_impossible_where(); + query_plan.set_impossible_where(); if (thd->lex->describe) goto exit_without_my_ok; @@ -454,16 +453,16 @@ int mysql_update(THD *thd, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered"): Ok to use with key field UPDATE need_sort= FALSE; - query_plan->index= MAX_KEY; + query_plan.index= MAX_KEY; used_key_is_modified= FALSE; } else { - query_plan->index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + query_plan.index= get_index_for_order(order, table, select, limit, + &need_sort, &reverse); if (select && select->quick) { - DBUG_ASSERT(need_sort || query_plan->index == select->quick->index); + DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); used_key_is_modified= (!select->quick->unique_key_range() && select->quick->is_keys_used(table->write_set)); } @@ -471,11 +470,11 @@ int mysql_update(THD *thd, { if (need_sort) { // Assign table scan index to check below for modified key fields: - query_plan->index= table->file->key_used_on_scan; + query_plan.index= table->file->key_used_on_scan; } - if (query_plan->index != MAX_KEY) + if (query_plan.index != MAX_KEY) { // Check if we are modifying a key that we are used to search with: - used_key_is_modified= is_key_used(table, query_plan->index, table->write_set); + used_key_is_modified= is_key_used(table, query_plan.index, table->write_set); } } } @@ -485,11 +484,9 @@ int mysql_update(THD *thd, - Save the decisions in the query plan - if we're running EXPLAIN UPDATE, get out */ - query_plan->select= select; - query_plan->possible_keys= table->quick_keys; - query_plan->table_rows= table->file->stats.records; - thd->lex->query_plan_footprint= new QPF_query; - thd->lex->query_plan_footprint->upd_del_plan= query_plan; + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; /* Ok, we have generated a query plan for the UPDATE. @@ -498,8 +495,8 @@ int mysql_update(THD *thd, */ if (thd->lex->describe) goto exit_without_my_ok; - - query_plan->save_query_plan_footprint(); + + query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", @@ -518,8 +515,8 @@ int mysql_update(THD *thd, DBUG_ASSERT(table->read_set == &table->def_read_set); DBUG_ASSERT(table->write_set == &table->def_write_set); - if (query_plan->index < MAX_KEY && old_covering_keys.is_set(query_plan->index)) - table->add_read_columns_used_by_index(query_plan->index); + if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index)) + table->add_read_columns_used_by_index(query_plan.index); else table->use_all_columns(); @@ -585,13 +582,13 @@ int mysql_update(THD *thd, Full index scan must be started with init_read_record_idx */ - if (query_plan->index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) goto err; } else - init_read_record_idx(&info, thd, table, 1, query_plan->index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); thd_proc_info(thd, "Searching rows for update"); ha_rows tmp_limit= limit; @@ -1005,12 +1002,6 @@ err: if (apc_target_enabled) thd->apc_target.disable(); - if (thd->lex->query_plan_footprint) - { - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - } - delete select; free_underlaid_joins(thd, select_lex); table->disable_keyread(); @@ -1019,25 +1010,22 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); - query_plan->save_query_plan_footprint(); - thd->lex->query_plan_footprint->upd_del_plan= query_plan; - + query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); + select_send *result; + bool printed_anything; if (!(result= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->query_plan_footprint->print_explain(result, 0); + int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); if (err2) result->abort_result_set(); else result->send_eof(); - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - delete select; free_underlaid_joins(thd, select_lex); DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); @@ -1475,8 +1463,6 @@ bool mysql_multi_update(THD *thd, } select_lex->set_explain_type(FALSE); *result= NULL; /* no multi_update object */ - - thd->lex->query_plan_footprint= new QPF_query; } else { @@ -1506,15 +1492,6 @@ bool mysql_multi_update(THD *thd, DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); - - if (explain) - { - //result->reset_offset_limit(); - thd->lex->query_plan_footprint->print_explain(output, thd->lex->describe); - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - } - if (unlikely(res)) (*result)->abort_result_set(); else From 52cfa54c1d211a17a9df7c38a4568ddc4d09e6d9 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 20 Jun 2013 20:58:26 +0400 Subject: [PATCH 13/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring Single table UPDATE/DELETE - Correctly print type=SIMPLE vs type=PRIMARY - Handle UPDATE/DELETE of mergeable VIEWs: we get the VIEW's select as the first subquery. (MySQL 5.6 doesn't print it because it finds that the subquery is not attached to any select) --- mysql-test/r/explain_non_select.result | 2 +- sql/opt_qpf.cc | 10 ++++-- sql/opt_qpf.h | 2 ++ sql/sql_delete.cc | 42 ++++++++++++++++++++++---- sql/sql_lex.cc | 7 +++++ sql/sql_lex.h | 8 ++--- sql/sql_update.cc | 10 ++++-- 7 files changed, 64 insertions(+), 17 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 1c8e444c4b9..d664e7241e2 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -1,4 +1,4 @@ -drop table if exists t0; +drop table if exists t0, t1; create table t0 (a int) engine=myisam; insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); # diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 819c0e6cc22..425823df9e1 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -49,6 +49,7 @@ void QPF_query::add_node(QPF_node *node) if (node->get_type() == QPF_node::QPF_UNION) { QPF_union *u= (QPF_union*)node; + DBUG_ASSERT(!unions[u->get_select_id()]); unions[u->get_select_id()]= u; } else @@ -60,7 +61,10 @@ void QPF_query::add_node(QPF_node *node) DBUG_ASSERT(0); } else + { + DBUG_ASSERT(!selects[sel->select_id]); selects[sel->select_id] = sel; + } } } @@ -493,7 +497,7 @@ int QPF_delete::print_explain(QPF_query *query, select_result_sink *output, const char *msg= "Deleting all rows"; int res= print_explain_message_line(output, explain_flags, 1 /*select number*/, - "SIMPLE", msg); + select_type, msg); return res; } @@ -513,7 +517,7 @@ int QPF_update::print_explain(QPF_query *query, select_result_sink *output, const char *msg= "Impossible where"; int res= print_explain_message_line(output, explain_flags, 1 /*select number*/, - "SIMPLE", msg); + select_type, msg); return res; } @@ -541,7 +545,7 @@ int QPF_update::print_explain(QPF_query *query, select_result_sink *output, print_explain_row(output, explain_flags, 1, /* id */ - "SIMPLE", + select_type, table_name.c_ptr(), // partitions, jtype, diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 6e0bb40c576..53839ecbac8 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -350,6 +350,8 @@ public: virtual enum qpf_node_type get_type() { return QPF_UPDATE; } virtual int get_select_id() { return 1; /* always root */ } + const char *select_type; + bool impossible_where; StringBuffer<64> table_name; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 602831829d7..30ae60ddc43 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -58,10 +58,12 @@ void Delete_plan::save_query_plan_footprint(QPF_query *query) if (deleting_all_rows) { qpf->deleting_all_rows= true; + qpf->select_type= "SIMPLE"; } else { - Update_plan::save_query_plan_footprint_intern(qpf); + qpf->deleting_all_rows= false; + Update_plan::save_query_plan_footprint_intern(query, qpf); } query->upd_del_plan= qpf; @@ -71,13 +73,14 @@ void Delete_plan::save_query_plan_footprint(QPF_query *query) void Update_plan::save_query_plan_footprint(QPF_query *query) { QPF_update* qpf= new QPF_update; - save_query_plan_footprint_intern(qpf); + save_query_plan_footprint_intern(query, qpf); query->upd_del_plan= qpf; } -void Update_plan::save_query_plan_footprint_intern(QPF_update *qpf) +void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update *qpf) { + qpf->select_type= "SIMPLE"; qpf->table_name.append(table->pos_in_table_list->alias); if (impossible_where) { @@ -85,8 +88,10 @@ void Update_plan::save_query_plan_footprint_intern(QPF_update *qpf) return; } - // TODO: do we need the following: select_type - //select_lex->set_explain_type(TRUE); + qpf->impossible_where= false; + + select_lex->set_explain_type(TRUE); + qpf->select_type= select_lex->type; /* Set jtype */ if (select && select->quick) @@ -134,6 +139,28 @@ void Update_plan::save_query_plan_footprint_intern(QPF_update *qpf) { explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &qpf->mrr_type); } + + bool skip= updating_a_view; + /* Save subquery children */ + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (skip) + { + skip= false; + continue; + } + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + qpf->add_child(unit->first_select()->select_number); + + //TODO: temporary?: + unit->save_qpf(query); + } } @@ -194,6 +221,9 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query_plan.select_lex= &thd->lex->select_lex; query_plan.table= table; + //psergey-todo: Ugly, discuss with Sanja + query_plan.updating_a_view= test(table_list->view); + if (mysql_prepare_delete(thd, table_list, &conds)) DBUG_RETURN(TRUE); @@ -612,7 +642,7 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->query_plan_footprint->print_explain(result, 0); + int err2= thd->lex->query_plan_footprint->print_explain(result, 0 /* explain flags*/); if (err2) result->abort_result_set(); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 6ad7b288a4b..455ef55944d 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4337,6 +4337,13 @@ int st_select_lex_unit::save_qpf(QPF_query *output) { //int res= 0; SELECT_LEX *first= first_select(); + + if (!first->next_select()) + { + /* This is a 1-way UNION, i.e. not really a UNION */ + first->save_qpf(output); + return 0; + } QPF_union *qpfu= new QPF_union; /* diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 47d01f2e137..6c7a5ca4d13 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -730,10 +730,7 @@ public: friend int subselect_union_engine::exec(); List *get_unit_column_types(); -#if 0 - int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); -#endif + int save_qpf(QPF_query *output); }; @@ -2379,6 +2376,7 @@ class Update_plan protected: bool impossible_where; public: + bool updating_a_view; TABLE *table; SQL_SELECT *select; uint index; @@ -2396,7 +2394,7 @@ public: void set_impossible_where() { impossible_where= true; } void save_query_plan_footprint(QPF_query *query); - void save_query_plan_footprint_intern(QPF_update *qpf); + void save_query_plan_footprint_intern(QPF_query *query, QPF_update *qpf); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} diff --git a/sql/sql_update.cc b/sql/sql_update.cc index ce56a725567..96f49785a32 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -314,6 +314,10 @@ int mysql_update(THD *thd, my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); DBUG_RETURN(1); } + + //psergey-todo: Ugly, discuss with Sanja + query_plan.updating_a_view= test(table_list->view); + /* Calculate "table->covering_keys" based on the WHERE */ table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); @@ -1013,13 +1017,14 @@ exit_without_my_ok: query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); select_send *result; - bool printed_anything; + //bool printed_anything; if (!(result= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + //int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); + int err2= thd->lex->query_plan_footprint->print_explain(result, 0 /* explain flags*/); if (err2) result->abort_result_set(); @@ -1498,6 +1503,7 @@ bool mysql_multi_update(THD *thd, { if (explain) { + thd->lex->query_plan_footprint->print_explain(output, 0); output->send_eof(); delete output; } From ab4a13b2b91e260d8c75a3c41b7ff5c24747cee0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 20 Jun 2013 22:30:30 +0400 Subject: [PATCH 14/64] Switching [EXPLAIN] UPDATE/DELETE to rely on query plan footprints. This requires that subselect's footprints are saved before it is deleted. Attempt to save select's QPF exposes one to a variety of edge cases: - the select may be a UNION's "fake select" which has no valid id - optimization may fail in the middle (but subsequent JOIN::optimize() calls will succeed, despite the fact that there never was a query plan) --- sql/sql_lex.cc | 6 ++++-- sql/sql_select.cc | 38 ++++++++++++++++++++++++++++++++++++-- sql/sql_select.h | 6 ------ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 455ef55944d..3161dc85fb9 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4341,7 +4341,8 @@ int st_select_lex_unit::save_qpf(QPF_query *output) if (!first->next_select()) { /* This is a 1-way UNION, i.e. not really a UNION */ - first->save_qpf(output); + if (!output->get_select(first->select_number)) + first->save_qpf(output); return 0; } @@ -4371,7 +4372,8 @@ int st_select_lex_unit::save_qpf(QPF_query *output) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - sl->save_qpf(output); + if (!output->get_select(sl->select_number)) + sl->save_qpf(output); qpfu->add_select(sl->select_number); } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 91da7526c72..eed6ed5e09f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1005,17 +1005,26 @@ err: int JOIN::optimize() { + bool was_optimized= optimized; int res= optimize_inner(); /* If we're inside a non-correlated subquery, this function may be called for the second time after the subquery has been executed and deleted. The second call will not produce a valid query plan, it will short-circuit because optimized==TRUE. + + "was_optimized != optimized" is here to handle this case: + - first optimization starts, gets an error (from a const. cheap + subquery), returns 1 + - another JOIN::optimize() call made, and now join->optimize() will + return 0, even though we never had a query plan. */ - if (!res && have_query_plan != QEP_DELETED) + if (was_optimized != optimized && !res && have_query_plan != QEP_DELETED) have_query_plan= QEP_AVAILABLE; return res; } + + /** global select optimisation. @@ -2301,7 +2310,6 @@ void JOIN::exec() select_lex->select_number)) dbug_serve_apcs(thd, 1); ); - thd->apc_target.disable(); } @@ -11099,7 +11107,33 @@ void JOIN::cleanup(bool full) DBUG_PRINT("enter", ("full %u", (uint) full)); if (full) + { + // + if (select_lex->select_number != UINT_MAX && + select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && + have_query_plan != QEP_NOT_PRESENT_YET && + !thd->lex->query_plan_footprint->get_select(select_lex->select_number)) + { + const char *message= NULL; + + if (!table_count || !tables_list || zero_result_cause) + { + /* It's a degenerate join */ + message= zero_result_cause ? zero_result_cause : "No tables used"; + } + + save_qpf(thd->lex->query_plan_footprint, + need_tmp, // need_tmp_table + !skip_sort_order && !no_order && + (order || group_list), // bool need_order + select_distinct, // bool distinct + message); // message + } + + // + have_query_plan= QEP_DELETED; //psergey: this is a problem! + } if (table) { diff --git a/sql/sql_select.h b/sql/sql_select.h index 5ec09fcfcb8..6d4dfed479e 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1462,12 +1462,6 @@ public: { return (unit->item && unit->item->is_in_predicate()); } -/* - int print_explain(select_result_sink *result, uint8 explain_flags, - bool on_the_fly, - bool need_tmp_table, bool need_order, - bool distinct,const char *message); -*/ int save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, bool distinct, const char *message); private: From af5e128e50cac8881f7bfca44cc473600abdce86 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 21 Jun 2013 13:26:53 +0400 Subject: [PATCH 15/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Handle statements inside SPs: = regular statements = SET command, which does not have its own statement. - Handle execution of subquery from range optimizer: allocate subquery QPFs on the same MEM_ROOT as the whole query plan was allocated. --- sql/opt_qpf.cc | 15 +++++++++++++-- sql/opt_qpf.h | 2 ++ sql/sp_head.cc | 6 ++++-- sql/sql_lex.cc | 6 +++--- sql/sql_lex.h | 3 ++- sql/sql_parse.cc | 11 +++-------- sql/sql_prepare.cc | 3 +-- sql/sql_select.cc | 9 +++++---- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 425823df9e1..d5a228e8f41 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -559,8 +559,19 @@ int QPF_update::print_explain(QPF_query *query, select_result_sink *output, return print_explain_for_children(query, output, explain_flags); } -void delete_qpf_query(QPF_query * query) + +void delete_qpf_query(LEX *lex) { - delete query; + delete lex->query_plan_footprint; + lex->query_plan_footprint= NULL; +} + + +void create_qpf_query(LEX *lex, MEM_ROOT *mem_root) +{ + DBUG_ASSERT(!lex->query_plan_footprint); + lex->query_plan_footprint= new QPF_query; + DBUG_ASSERT(mem_root == current_thd->mem_root); + lex->query_plan_footprint->mem_root= mem_root; } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 53839ecbac8..501ac7c609e 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -214,6 +214,8 @@ public: /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); + + MEM_ROOT *mem_root; private: QPF_union *unions[MAX_TABLES]; QPF_select *selects[MAX_TABLES]; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 3cc3a3893bb..2ad03f3d0dc 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -2973,6 +2973,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, } reinit_stmt_before_use(thd, m_lex); + // not here, but inside every instr: create_qpf_query(m_lex); if (open_tables) res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); @@ -3007,8 +3008,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->mdl_context.release_statement_locks(); } - delete_qpf_query(m_lex->query_plan_footprint); - m_lex->query_plan_footprint= NULL; + delete_qpf_query(m_lex); if (m_lex->query_tables_own_last) { @@ -3212,6 +3212,7 @@ sp_instr_set::execute(THD *thd, uint *nextp) int sp_instr_set::exec_core(THD *thd, uint *nextp) { + create_qpf_query(thd->lex, thd->mem_root); int res= thd->spcont->set_variable(thd, m_offset, &m_value); if (res) @@ -3224,6 +3225,7 @@ sp_instr_set::exec_core(THD *thd, uint *nextp) my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); } } + delete_qpf_query(thd->lex); *nextp = m_ip+1; return res; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 3161dc85fb9..df38d9fcdb0 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4251,7 +4251,7 @@ void st_select_lex::save_qpf(QPF_query *output) msg= "Query plan already deleted"; } set_explain_type(TRUE/* on_the_fly */); - QPF_select *qp_sel= new QPF_select; + QPF_select *qp_sel= new (output->mem_root) QPF_select; qp_sel->select_id= select_number; qp_sel->select_type= type; qp_sel->message= msg; @@ -4346,7 +4346,7 @@ int st_select_lex_unit::save_qpf(QPF_query *output) return 0; } - QPF_union *qpfu= new QPF_union; + QPF_union *qpfu= new (output->mem_root) QPF_union; /* TODO: The following code should be eliminated. If we have a capability to save Query Plan Footprints, we should just save them, and never need to @@ -4361,7 +4361,7 @@ int st_select_lex_unit::save_qpf(QPF_query *output) const char *msg="Query plan already deleted"; first->set_explain_type(TRUE/* on_the_fly */); - QPF_select *qp_sel= new QPF_select; + QPF_select *qp_sel= new (output->mem_root)QPF_select; qp_sel->select_id= first->select_number; qp_sel->select_type= first->type; qp_sel->message= msg; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 6c7a5ca4d13..7e76c09a4f5 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -619,7 +619,8 @@ class select_union; class Procedure; class QPF_query; -void delete_qpf_query(QPF_query * query); +void delete_qpf_query(LEX *lex); +void create_qpf_query(LEX *lex, MEM_ROOT *mem_root); class st_select_lex_unit: public st_select_lex_node { protected: diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 474d3889b53..6f2f7eaca20 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -598,10 +598,7 @@ static void handle_bootstrap_impl(THD *thd) #if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif - // - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; - // + delete_qpf_query(thd->lex); if (bootstrap_error) break; @@ -1523,8 +1520,7 @@ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); - delete thd->lex->query_plan_footprint; - thd->lex->query_plan_footprint= NULL; + delete_qpf_query(thd->lex); /* The following should never be true with our current code base, @@ -2188,8 +2184,7 @@ mysql_execute_command(THD *thd) thd->mdl_context.release_transactional_locks(); } - DBUG_ASSERT(!thd->lex->query_plan_footprint); - thd->lex->query_plan_footprint= new QPF_query; + create_qpf_query(thd->lex, thd->mem_root); #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index d81070b7406..2f62a06f6a4 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -3927,8 +3927,7 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (! cursor) cleanup_stmt(); //psergey: TODO the "EXECUTE problem" is here - delete_qpf_query(thd->lex->query_plan_footprint); - thd->lex->query_plan_footprint= NULL; + delete_qpf_query(thd->lex); thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index eed6ed5e09f..bad29d70af9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11112,6 +11112,7 @@ void JOIN::cleanup(bool full) if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && + thd->lex->query_plan_footprint && // for "SET" command in SPs. !thd->lex->query_plan_footprint->get_select(select_lex->select_number)) { const char *message= NULL; @@ -22986,7 +22987,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (message) { QPF_select *qp_sel; - qp_node= qp_sel= new QPF_select; + qp_node= qp_sel= new (output->mem_root) QPF_select; join->select_lex->set_explain_type(on_the_fly); qp_sel->select_id= join->select_lex->select_number; @@ -22998,7 +22999,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, else if (join->select_lex == join->unit->fake_select_lex) { select_lex->set_explain_type(on_the_fly); - QPF_union *qp_union= new QPF_union; + QPF_union *qp_union= new (output->mem_root) QPF_union; qp_node= qp_union; SELECT_LEX *child; @@ -23018,7 +23019,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, join->select_lex->master_unit()->derived->is_materialized_derived()) { QPF_select *qp_sel; - qp_node= qp_sel= new QPF_select; + qp_node= qp_sel= new (output->mem_root) QPF_select; table_map used_tables=0; if (on_the_fly) @@ -23077,7 +23078,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, tab= pre_sort_join_tab; } - QPF_table_access *qpt= new QPF_table_access; + QPF_table_access *qpt= new (output->mem_root) QPF_table_access; qp_sel->add_table(qpt); /* id */ From cebdf3de2ef2604f3459762fc05a50dd54c1c9f2 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 21 Jun 2013 22:26:03 +0400 Subject: [PATCH 16/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Handle another specific case where there the JOIN never had a query plan, but had multiple join->cleanup(full=true) calls - The idea that there can only be MAX_TABLES subuqeries/unions was wrong. Switch QPF_query to using a Dynamic_array. = make Dynamic_array template support size growth. its underlying DYNAMIC_ARRAY supports it. (this part will need more polishing) --- sql/opt_qpf.cc | 42 ++++++++++++++++++++++++++++-------------- sql/opt_qpf.h | 8 ++++++-- sql/sql_array.h | 18 ++++++++++++++++++ sql/sql_select.cc | 3 +++ 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index d5a228e8f41..204a7e00350 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -13,8 +13,8 @@ QPF_query::QPF_query() { upd_del_plan= NULL; - memset(&unions, 0, sizeof(unions)); - memset(&selects, 0, sizeof(selects)); + //memset(&unions, 0, sizeof(unions)); + //memset(&selects, 0, sizeof(selects)); } @@ -22,25 +22,30 @@ QPF_query::~QPF_query() { delete upd_del_plan; uint i; - for (i=0 ; i < MAX_TABLES; i++) - delete unions[i]; - for (i=0 ; i < MAX_TABLES; i++) - delete selects[i]; + for (i= 0 ; i < unions.elements(); i++) + delete unions.at(i); + for (i= 0 ; i < selects.elements(); i++) + delete selects.at(i); } QPF_node *QPF_query::get_node(uint select_id) { - if (unions[select_id]) - return unions[select_id]; + QPF_union *u; + if ((u= get_union(select_id))) + return u; else - return selects[select_id]; + return get_select(select_id); } +QPF_union *QPF_query::get_union(uint select_id) +{ + return (unions.elements() > select_id) ? unions.at(select_id) : NULL; +} QPF_select *QPF_query::get_select(uint select_id) { - return selects[select_id]; + return (selects.elements() > select_id) ? selects.at(select_id) : NULL; } @@ -49,8 +54,13 @@ void QPF_query::add_node(QPF_node *node) if (node->get_type() == QPF_node::QPF_UNION) { QPF_union *u= (QPF_union*)node; - DBUG_ASSERT(!unions[u->get_select_id()]); - unions[u->get_select_id()]= u; + uint select_id= u->get_select_id(); + DBUG_ASSERT(!get_union(select_id)); + + if (unions.elements() <= select_id) + unions.resize(max(select_id+1, unions.elements()*2), NULL); + + unions.at(select_id)= u; } else { @@ -62,8 +72,12 @@ void QPF_query::add_node(QPF_node *node) } else { - DBUG_ASSERT(!selects[sel->select_id]); - selects[sel->select_id] = sel; + uint select_id= sel->select_id; + DBUG_ASSERT(!get_select(select_id)); + + if (selects.elements() <= select_id) + selects.resize(max(select_id+1, selects.elements()*2), NULL); + selects.at(select_id)= sel; } } } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 501ac7c609e..1944f79254d 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -209,6 +209,8 @@ public: /* This will return a select (even if there is a union with this id) */ QPF_select *get_select(uint select_id); + QPF_union *get_union(uint select_id); + /* QPF_delete inherits from QPF_update */ QPF_update *upd_del_plan; @@ -217,8 +219,10 @@ public: MEM_ROOT *mem_root; private: - QPF_union *unions[MAX_TABLES]; - QPF_select *selects[MAX_TABLES]; + Dynamic_array unions; + Dynamic_array selects; + //QPF_union *unions[MAX_TABLES]; + //QPF_select *selects[MAX_TABLES]; }; diff --git a/sql/sql_array.h b/sql/sql_array.h index 43ca4ef4219..18f1fbd9f2f 100644 --- a/sql/sql_array.h +++ b/sql/sql_array.h @@ -106,6 +106,7 @@ public: Elem& at(size_t idx) { + DBUG_ASSERT(idx < array.elements); return *(((Elem*)array.buffer) + idx); } @@ -139,6 +140,23 @@ public: array.elements= n; } + bool resize(size_t new_size, Elem default_val) + { + size_t old_size= elements(); + if (allocate_dynamic(&array, new_size)) + return true; + + if (new_size > old_size) + { + set_dynamic(&array, (uchar*)&default_val, new_size - 1); + /*for (size_t i= old_size; i != new_size; i++) + { + at(i)= default_val; + }*/ + } + return false; + } + ~Dynamic_array() { delete_dynamic(&array); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index bad29d70af9..1b98d0b12ee 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11112,6 +11112,9 @@ void JOIN::cleanup(bool full) if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && + have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then + //cleanup() is called multiple times + thd->lex->query_plan_footprint && // for "SET" command in SPs. !thd->lex->query_plan_footprint->get_select(select_lex->select_number)) { From 79392b9383921cb1d48ca0defb8715f7385af0e8 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 21 Jun 2013 22:45:54 +0400 Subject: [PATCH 17/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Support "using index for group-by (scanning) " queries --- sql/opt_qpf.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 204a7e00350..309e24471eb 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -497,6 +497,12 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) str->append(extra_tag_text[tag]); break; } + case ET_USING_INDEX_FOR_GROUP_BY: + { + str->append(extra_tag_text[tag]); + str->append(loose_scan_type); + break; + } default: str->append(extra_tag_text[tag]); } From 99a8bfe68cdd6411ed24b3fa465d5dd4b70be6dc Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 01:00:22 +0400 Subject: [PATCH 18/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Update view.result (old EXPLAIN didn't match the execution) - Put in a stub code to work around the SELECT ... UNION SELECT ... ORDER BY (subuqery) problem --- mysql-test/disabled.def | 2 +- mysql-test/r/view.result | 2 +- sql/sql_delete.cc | 4 +- sql/sql_lex.cc | 194 ++++-------- sql/sql_lex.h | 2 + sql/sql_select.cc | 652 ++++----------------------------------- sql/sql_union.cc | 6 + 7 files changed, 129 insertions(+), 733 deletions(-) diff --git a/mysql-test/disabled.def b/mysql-test/disabled.def index 4133eb2a12c..d9f8f15cfff 100644 --- a/mysql-test/disabled.def +++ b/mysql-test/disabled.def @@ -16,4 +16,4 @@ read_many_rows_innodb : Bug#11748886 2010-11-15 mattiasj report already exist archive-big : Bug#11817185 2011-03-10 Anitha Disabled since this leads to timeout on Solaris Sparc log_tables-big : Bug#11756699 2010-11-15 mattiasj report already exists mysql_embedded : Bug#12561297 2011-05-14 Anitha Dependent on PB2 changes - eventum#41836 -show_explain : Psergey: random timeout in range-checked-for-each record query. +#show_explain : Psergey: random timeout in range-checked-for-each record query. diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index 7fc2fa72805..bc286c36655 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3584,7 +3584,7 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`a` AS `a`,`t1`.`b` AS `b` from `t1` FORCE INDEX (PRIMARY) FORCE INDEX (`b`) order by `t1`.`a` latin1 latin1_swedish_ci EXPLAIN SELECT * FROM v1; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index NULL PRIMARY 4 NULL 15 +1 SIMPLE t1 ALL NULL NULL NULL NULL 15 CREATE VIEW v2 AS SELECT * FROM t1 USE KEY () ORDER BY a; SHOW CREATE VIEW v2; View Create View character_set_client collation_connection diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 30ae60ddc43..50401e2af46 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -159,7 +159,9 @@ void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update qpf->add_child(unit->first_select()->select_number); //TODO: temporary?: - unit->save_qpf(query); + // A: yes. optimizing children subqueries has caused them to save QPFs, + // automatically. + //unit->save_qpf(query); } } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index df38d9fcdb0..c453a599dce 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4172,29 +4172,32 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } +/* + This is used by SHOW EXPLAIN. It assuses query plan has been already + collected into QPF structures and we only need to print it out. +*/ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything) + bool *printed_anything) //TODO: remove printed_anything { - /* if (upd_del_plan) + int res; + if (query_plan_footprint) { - upd_del_plan->print_explain(output, explain_flags, printed_anything); - return 0; - }*/ - //int res= unit.print_explain(output, explain_flags, printed_anything); - - //psergey-todo: here, we should make Query Plan Footprint, and then produce - // an EXPLAIN output from it. - /* - The new, QueryPlanFootprint way: - */ - QPF_query qpf; - unit.save_qpf(&qpf); - //return res; - return 0; + res= query_plan_footprint->print_explain(output, explain_flags); + *printed_anything= true; + } + else + { + res= 0; + *printed_anything= false; + } + return res; } +/* + +*/ void st_select_lex::save_qpf(QPF_query *output) { int res; @@ -4261,92 +4264,12 @@ err: return ;//res; } -#if 0 -int st_select_lex::print_explain(select_result_sink *output, - uint8 explain_flags, - bool *printed_anything) + +int st_select_lex_unit::save_union_qpf(QPF_query *output) { - int res; - if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) - { - /* - There is a number of reasons join can be marked as degenerate, so all - three conditions below can happen simultaneously, or individually: - */ - *printed_anything= TRUE; - if (!join->table_count || !join->tables_list || join->zero_result_cause) - { - /* It's a degenerate join */ - const char *cause= join->zero_result_cause ? join-> zero_result_cause : - "No tables used"; - res= join->print_explain(output, explain_flags, TRUE, FALSE, FALSE, - FALSE, cause); - } - else - { - res= join->print_explain(output, explain_flags, TRUE, - join->need_tmp, // need_tmp_table - !join->skip_sort_order && !join->no_order && - (join->order || join->group_list), // bool need_order - join->select_distinct, // bool distinct - NULL); //const char *message - } - if (res) - goto err; - - for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) - { - /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. - */ - if (!(unit->item && unit->item->eliminated)) - { - if ((res= unit->print_explain(output, explain_flags, printed_anything))) - goto err; - } - } - } - else - { - const char *msg; - if (!join) - DBUG_ASSERT(0); /* Seems not to be possible */ - - /* Not printing anything useful, don't touch *printed_anything here */ - if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET) - msg= "Not yet optimized"; - else - { - DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); - msg= "Query plan already deleted"; - } - set_explain_type(TRUE/* on_the_fly */); - res= print_explain_message_line(output, 0/*options*/, select_number, type, - msg); - } -err: - return res; -} -#endif - - -int st_select_lex_unit::save_qpf(QPF_query *output) -{ - //int res= 0; SELECT_LEX *first= first_select(); - - if (!first->next_select()) - { - /* This is a 1-way UNION, i.e. not really a UNION */ - if (!output->get_select(first->select_number)) - first->save_qpf(output); - return 0; - } - QPF_union *qpfu= new (output->mem_root) QPF_union; + /* TODO: The following code should be eliminated. If we have a capability to save Query Plan Footprints, we should just save them, and never need to @@ -4380,53 +4303,48 @@ int st_select_lex_unit::save_qpf(QPF_query *output) // Save the UNION node output->add_node(qpfu); -#if 0 - /* Note: fake_select_lex->join may be NULL or non-NULL at this point */ - if (fake_select_lex) - { - res= print_fake_select_lex_join(output, TRUE /* on the fly */, - fake_select_lex, explain_flags); - } - return res; -#endif + qpfu->fake_select_type= "UNION RESULT"; + qpfu->using_filesort= test(global_parameters->order_list.first); return 0; } -#if 0 -int st_select_lex_unit::print_explain(select_result_sink *output, - uint8 explain_flags, bool *printed_anything) + +int st_select_lex_unit::save_union_qpf_part2(QPF_query *output) { - int res= 0; - SELECT_LEX *first= first_select(); - - if (first && !first->next_select() && !first->join) - { - /* - If there is only one child, 'first', and it has join==NULL, emit "not in - EXPLAIN state" error. - */ - const char *msg="Query plan already deleted"; - first->set_explain_type(TRUE/* on_the_fly */); - res= print_explain_message_line(output, 0/*options*/, first->select_number, - first->type, msg); - return res; - } - - for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) - { - if ((res= sl->print_explain(output, explain_flags, printed_anything))) - break; - } - - /* Note: fake_select_lex->join may be NULL or non-NULL at this point */ + QPF_union *qpfu= output->get_union(first_select()->select_number); if (fake_select_lex) { - res= print_fake_select_lex_join(output, TRUE /* on the fly */, - fake_select_lex, explain_flags); + for (SELECT_LEX_UNIT *unit= fake_select_lex->first_inner_unit(); + unit; unit= unit->next_unit()) + { + if (!(unit->item && unit->item->eliminated)) + { + qpfu->add_child(unit->first_select()->select_number); + } + } } - return res; + return 0; } -#endif + + +int st_select_lex_unit::save_qpf(QPF_query *output) +{ + //int res= 0; + SELECT_LEX *first= first_select(); + + if (!first->next_select()) + { + /* This is a 1-way UNION, i.e. not really a UNION */ + if (!output->get_select(first->select_number)) + first->save_qpf(output); + return 0; + } + + save_union_qpf(output); + + return 0; +} + /** A routine used by the parser to decide whether we are specifying a full diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 7e76c09a4f5..77abc46d30b 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -733,6 +733,8 @@ public: List *get_unit_column_types(); int save_qpf(QPF_query *output); + int save_union_qpf(QPF_query *output); + int save_union_qpf_part2(QPF_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 1b98d0b12ee..5e20d2faf24 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1002,6 +1002,7 @@ err: DBUG_RETURN(res); /* purecov: inspected */ } +void join_save_qpf(JOIN *join); int JOIN::optimize() { @@ -1020,7 +1021,10 @@ int JOIN::optimize() return 0, even though we never had a query plan. */ if (was_optimized != optimized && !res && have_query_plan != QEP_DELETED) + { have_query_plan= QEP_AVAILABLE; + join_save_qpf(this); + } return res; } @@ -1611,8 +1615,10 @@ TODO: make view to decide if it is possible to write to WHERE directly or make S JOIN_TAB *tab= &join_tab[const_tables]; bool all_order_fields_used; if (order) + { skip_sort_order= test_if_skip_sort_order(tab, order, select_limit, 1, &tab->table->keys_in_use_for_order_by); + } if ((group_list=create_distinct_group(thd, select_lex->ref_pointer_array, order, fields_list, all_fields, &all_order_fields_used))) @@ -2289,11 +2295,48 @@ JOIN::save_join_tab() } +void join_save_qpf(JOIN *join) +{ + THD *thd= join->thd; +//TODO: why not call st_select_lex::save_qpf here? + + if (join->select_lex->select_number != UINT_MAX && + join->select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && + join->have_query_plan != JOIN::QEP_NOT_PRESENT_YET && + join->have_query_plan != JOIN::QEP_DELETED && // this happens when there was no QEP ever, but then + //cleanup() is called multiple times + + thd->lex->query_plan_footprint && // for "SET" command in SPs. + !thd->lex->query_plan_footprint->get_select(join->select_lex->select_number)) + { + const char *message= NULL; + + if (!join->table_count || !join->tables_list || join->zero_result_cause) + { + /* It's a degenerate join */ + message= join->zero_result_cause ? join->zero_result_cause : "No tables used"; + } + + join->save_qpf(thd->lex->query_plan_footprint, + join->need_tmp, // need_tmp_table + !join->skip_sort_order && !join->no_order && + (join->order || join->group_list), // bool need_order + join->select_distinct, // bool distinct + message); // message + } +} + + void JOIN::exec() { /* Enable SHOW EXPLAIN only if we're in the top-level query. */ + + /* + psergey: we can produce SHOW explain at this point. This means, we're ready + to save the query plan. + */ thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_join_exec_start", if (dbug_user_var_equals_int(thd, @@ -11109,6 +11152,7 @@ void JOIN::cleanup(bool full) if (full) { // +#if 0 if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && @@ -11133,7 +11177,7 @@ void JOIN::cleanup(bool full) select_distinct, // bool distinct message); // message } - +#endif // have_query_plan= QEP_DELETED; //psergey: this is a problem! @@ -22352,589 +22396,6 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) } -/** - EXPLAIN handling. - - Produce lines explaining execution of *this* select (not including children - selects) - @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make - modifications to any select's data structures - - psergey-todo: should this produce a data structure with a query plan? Or, the - data structure with the query plan should be produced in any way? -*/ -#if 0 -int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, - bool on_the_fly, - bool need_tmp_table, bool need_order, - bool distinct, const char *message) -{ - List field_list; - List item_list; - JOIN *join= this; /* Legacy: this code used to be a non-member function */ - THD *thd=join->thd; - Item *item_null= new Item_null(); - CHARSET_INFO *cs= system_charset_info; - int quick_type; - int error= 0; - DBUG_ENTER("JOIN::print_explain"); - DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", - (ulong)join->select_lex, join->select_lex->type, - message ? message : "NULL")); - DBUG_ASSERT(on_the_fly? have_query_plan == QEP_AVAILABLE: TRUE); - /* Don't log this into the slow query log */ - - if (!on_the_fly) - { - thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); - join->unit->offset_limit_cnt= 0; - } - - /* - NOTE: the number/types of items pushed into item_list must be in sync with - EXPLAIN column types as they're "defined" in THD::send_explain_fields() - */ - if (message) - { - if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); - - if (print_explain_message_line(result, explain_flags, - join->select_lex->select_number, - join->select_lex->type, message)) - error= 1; - - } - else if (join->select_lex == join->unit->fake_select_lex) - { - if (print_fake_select_lex_join(result, on_the_fly, - join->select_lex, - explain_flags)) - error= 1; - } - else if (!join->select_lex->master_unit()->derived || - join->select_lex->master_unit()->derived->is_materialized_derived()) - { - table_map used_tables=0; - - if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); - - bool printing_materialize_nest= FALSE; - uint select_id= join->select_lex->select_number; - - JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); - - for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; - tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) - { - if (tab->bush_root_tab) - { - JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; - select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); - printing_materialize_nest= TRUE; - } - - TABLE *table=tab->table; - TABLE_LIST *table_list= tab->table->pos_in_table_list; - char buff[512]; - char buff1[512], buff2[512], buff3[512], buff4[512]; - char keylen_str_buf[64]; - my_bool key_read; - String extra(buff, sizeof(buff),cs); - char table_name_buffer[SAFE_NAME_LEN]; - String tmp1(buff1,sizeof(buff1),cs); - String tmp2(buff2,sizeof(buff2),cs); - String tmp3(buff3,sizeof(buff3),cs); - String tmp4(buff4,sizeof(buff4),cs); - char hash_key_prefix[]= "#hash#"; - KEY *key_info= 0; - uint key_len= 0; - bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT; - - extra.length(0); - tmp1.length(0); - tmp2.length(0); - tmp3.length(0); - tmp4.length(0); - quick_type= -1; - QUICK_SELECT_I *quick= NULL; - JOIN_TAB *saved_join_tab= NULL; - - /* Don't show eliminated tables */ - if (table->map & join->eliminated_tables) - { - used_tables|=table->map; - continue; - } - - if (join->table_access_tabs == join->join_tab && - tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) - { - saved_join_tab= tab; - tab= pre_sort_join_tab; - } - - item_list.empty(); - /* id */ - item_list.push_back(new Item_uint((uint32)select_id)); - /* select_type */ - const char* stype= printing_materialize_nest? "MATERIALIZED" : - join->select_lex->type; - item_list.push_back(new Item_string(stype, strlen(stype), cs)); - - enum join_type tab_type= tab->type; - if ((tab->type == JT_ALL || tab->type == JT_HASH) && - tab->select && tab->select->quick && tab->use_quick != 2) - { - quick= tab->select->quick; - quick_type= tab->select->quick->get_type(); - if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || - (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || - (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) - tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE; - else - tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; - } - - /* table */ - if (table->derived_select_number) - { - /* Derived table name generation */ - int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, - "", - table->derived_select_number); - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } - else if (tab->bush_children) - { - JOIN_TAB *ctab= tab->bush_children->start; - /* table */ - int len= my_snprintf(table_name_buffer, - sizeof(table_name_buffer)-1, - "", - ctab->emb_sj_nest->sj_subq_pred->get_identifier()); - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } - else - { - TABLE_LIST *real_table= table->pos_in_table_list; - item_list.push_back(new Item_string(real_table->alias, - strlen(real_table->alias), cs)); - } - /* "partitions" column */ - if (explain_flags & DESCRIBE_PARTITIONS) - { -#ifdef WITH_PARTITION_STORAGE_ENGINE - partition_info *part_info; - if (!table->derived_select_number && - (part_info= table->part_info)) - { - Item_string *item_str= new Item_string(cs); - make_used_partitions_str(part_info, &item_str->str_value); - item_list.push_back(item_str); - } - else - item_list.push_back(item_null); -#else - /* just produce empty column if partitioning is not compiled in */ - item_list.push_back(item_null); -#endif - } - /* "type" column */ - item_list.push_back(new Item_string(join_type_str[tab_type], - strlen(join_type_str[tab_type]), - cs)); - /* Build "possible_keys" value and add it to item_list */ - if (!tab->keys.is_clear_all()) - { - uint j; - for (j=0 ; j < table->s->keys ; j++) - { - if (tab->keys.is_set(j)) - { - if (tmp1.length()) - tmp1.append(','); - tmp1.append(table->key_info[j].name, - strlen(table->key_info[j].name), - system_charset_info); - } - } - } - if (tmp1.length()) - item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs)); - else - item_list.push_back(item_null); - - /* Build "key", "key_len", and "ref" values and add them to item_list */ - if (tab_type == JT_NEXT) - { - key_info= table->key_info+tab->index; - key_len= key_info->key_length; - } - else if (tab->ref.key_parts) - { - key_info= tab->get_keyinfo_by_key_no(tab->ref.key); - key_len= tab->ref.key_length; - } - if (key_info) - { - register uint length; - if (is_hj) - tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs); - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); - if (tab->ref.key_parts && tab_type != JT_FT) - { - store_key **ref=tab->ref.key_copy; - for (uint kp= 0; kp < tab->ref.key_parts; kp++) - { - if (tmp4.length()) - tmp4.append(','); - - if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map) - tmp4.append("const"); - else - { - tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); - ref++; - } - } - } - } - if (is_hj && tab_type != JT_HASH) - { - tmp2.append(':'); - tmp3.append(':'); - } - if (tab_type == JT_HASH_NEXT) - { - register uint length; - key_info= table->key_info+tab->index; - key_len= key_info->key_length; - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); - } - if (tab->type != JT_CONST && tab->select && quick) - tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); - if (key_info || (tab->select && quick)) - { - if (tmp2.length()) - item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); - else - item_list.push_back(item_null); - if (tmp3.length()) - item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs)); - else - item_list.push_back(item_null); - if (key_info && tab_type != JT_NEXT) - item_list.push_back(new Item_string(tmp4.ptr(),tmp4.length(),cs)); - else - item_list.push_back(item_null); - } - else - { - if (table_list && /* SJM bushes don't have table_list */ - table_list->schema_table && - table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) - { - const char *tmp_buff; - int f_idx; - if (table_list->has_db_lookup_value) - { - f_idx= table_list->schema_table->idx_field1; - tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); - } - if (table_list->has_table_lookup_value) - { - if (table_list->has_db_lookup_value) - tmp2.append(','); - f_idx= table_list->schema_table->idx_field2; - tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); - } - if (tmp2.length()) - item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); - else - item_list.push_back(item_null); - } - else - item_list.push_back(item_null); - item_list.push_back(item_null); - item_list.push_back(item_null); - } - - /* Add "rows" field to item_list. */ - if (table_list /* SJM bushes don't have table_list */ && - table_list->schema_table) - { - /* in_rows */ - if (explain_flags & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); - } - else - { - ha_rows examined_rows= tab->get_examined_rows(); - - item_list.push_back(new Item_int((longlong) (ulonglong) examined_rows, - MY_INT64_NUM_DECIMAL_DIGITS)); - - /* Add "filtered" field to item_list. */ - if (explain_flags & DESCRIBE_EXTENDED) - { - float f= 0.0; - if (examined_rows) - { - double pushdown_cond_selectivity= tab->cond_selectivity; - if (pushdown_cond_selectivity == 1.0) - f= (float) (100.0 * tab->records_read / examined_rows); - else - f= (float) (100.0 * pushdown_cond_selectivity); - } - set_if_smaller(f, 100.0); - item_list.push_back(new Item_float(f, 2)); - } - } - - /* Build "Extra" field and add it to item_list. */ - key_read=table->key_read; - if ((tab_type == JT_NEXT || tab_type == JT_CONST) && - table->covering_keys.is_set(tab->index)) - key_read=1; - if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && - !((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row) - key_read=1; - - if (tab->info) - { - const char *reason; - switch (tab->info) { - case ET_CONST_ROW_NOT_FOUND: - reason= "const row not found"; - break; - case ET_UNIQUE_ROW_NOT_FOUND: - reason= "unique row not found"; - break; - case ET_IMPOSSIBLE_ON_CONDITION: - reason= "Impossible ON condition"; - break; - default: - DBUG_ASSERT(0); - } - item_list.push_back(new Item_string(reason,strlen(reason),cs)); - } - else if (tab->packed_info & TAB_INFO_HAVE_VALUE) - { - if (tab->packed_info & TAB_INFO_USING_INDEX) - extra.append(STRING_WITH_LEN("; Using index")); - if (tab->packed_info & TAB_INFO_USING_WHERE) - extra.append(STRING_WITH_LEN("; Using where")); - if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL) - extra.append(STRING_WITH_LEN("; Full scan on NULL key")); - /* Skip initial "; "*/ - const char *str= extra.ptr(); - uint32 len= extra.length(); - if (len) - { - str += 2; - len -= 2; - } - item_list.push_back(new Item_string(str, len, cs)); - } - else - { - uint keyno= MAX_KEY; - if (tab->ref.key_parts) - keyno= tab->ref.key; - else if (tab->select && quick) - keyno = quick->index; - - if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && - table->file->pushed_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition")); - else if (tab->cache_idx_cond) - extra.append(STRING_WITH_LEN("; Using index condition(BKA)")); - - if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) - { - extra.append(STRING_WITH_LEN("; Using ")); - tab->select->quick->add_info_string(&extra); - } - if (tab->select) - { - if (tab->use_quick == 2) - { - /* 4 bits per 1 hex digit + terminating '\0' */ - char buf[MAX_KEY / 4 + 1]; - extra.append(STRING_WITH_LEN("; Range checked for each " - "record (index map: 0x")); - extra.append(tab->keys.print(buf)); - extra.append(')'); - } - else if (tab->select->cond) - { - const COND *pushed_cond= tab->table->file->pushed_cond; - - if (((thd->variables.optimizer_switch & - OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) || - (tab->table->file->ha_table_flags() & - HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && - pushed_cond) - { - extra.append(STRING_WITH_LEN("; Using where with pushed " - "condition")); - if (explain_flags & DESCRIBE_EXTENDED) - { - extra.append(STRING_WITH_LEN(": ")); - ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); - } - } - else - extra.append(STRING_WITH_LEN("; Using where")); - } - } - if (table_list /* SJM bushes don't have table_list */ && - table_list->schema_table && - table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) - { - if (!table_list->table_open_method) - extra.append(STRING_WITH_LEN("; Skip_open_table")); - else if (table_list->table_open_method == OPEN_FRM_ONLY) - extra.append(STRING_WITH_LEN("; Open_frm_only")); - else - extra.append(STRING_WITH_LEN("; Open_full_table")); - if (table_list->has_db_lookup_value && - table_list->has_table_lookup_value) - extra.append(STRING_WITH_LEN("; Scanned 0 databases")); - else if (table_list->has_db_lookup_value || - table_list->has_table_lookup_value) - extra.append(STRING_WITH_LEN("; Scanned 1 database")); - else - extra.append(STRING_WITH_LEN("; Scanned all databases")); - } - if (key_read) - { - if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - { - QUICK_GROUP_MIN_MAX_SELECT *qgs= - (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; - extra.append(STRING_WITH_LEN("; Using index for group-by")); - qgs->append_loose_scan_type(&extra); - } - else - extra.append(STRING_WITH_LEN("; Using index")); - } - if (table->reginfo.not_exists_optimize) - extra.append(STRING_WITH_LEN("; Not exists")); - - /* - if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE && - !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags & - HA_MRR_USE_DEFAULT_IMPL)) - { - extra.append(STRING_WITH_LEN("; Using MRR")); - } - */ - if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) - { - explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), - &extra); - } - - if (need_tmp_table) - { - need_tmp_table=0; - extra.append(STRING_WITH_LEN("; Using temporary")); - } - if (need_order) - { - need_order=0; - extra.append(STRING_WITH_LEN("; Using filesort")); - } - if (distinct & test_all_bits(used_tables, - join->select_list_used_tables)) - extra.append(STRING_WITH_LEN("; Distinct")); - if (tab->loosescan_match_tab) - { - extra.append(STRING_WITH_LEN("; LooseScan")); - } - - if (tab->first_weedout_table) - extra.append(STRING_WITH_LEN("; Start temporary")); - if (tab->check_weed_out_table) - extra.append(STRING_WITH_LEN("; End temporary")); - else if (tab->do_firstmatch) - { - if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) - extra.append(STRING_WITH_LEN("; FirstMatch")); - else - { - extra.append(STRING_WITH_LEN("; FirstMatch(")); - TABLE *prev_table=tab->do_firstmatch->table; - if (prev_table->derived_select_number) - { - char namebuf[NAME_LEN]; - /* Derived table name generation */ - int len= my_snprintf(namebuf, sizeof(namebuf)-1, - "", - prev_table->derived_select_number); - extra.append(namebuf, len); - } - else - extra.append(prev_table->pos_in_table_list->alias); - extra.append(STRING_WITH_LEN(")")); - } - } - - for (uint part= 0; part < tab->ref.key_parts; part++) - { - if (tab->ref.cond_guards[part]) - { - extra.append(STRING_WITH_LEN("; Full scan on NULL key")); - break; - } - } - - if (tab->cache) - { - extra.append(STRING_WITH_LEN("; Using join buffer")); - tab->cache->print_explain_comment(&extra); - } - - /* Skip initial "; "*/ - const char *str= extra.ptr(); - uint32 len= extra.length(); - if (len) - { - str += 2; - len -= 2; - } - item_list.push_back(new Item_string(str, len, cs)); - } - - if (saved_join_tab) - tab= saved_join_tab; - - // For next iteration - used_tables|=table->map; - if (result->send_data(item_list)) - error= 1; - } - } - DBUG_RETURN(error); -} -#endif ///////////////////////////////////////////////////////////////////////////////////////////////// void QPF_table_access::push_extra(enum Extra_tag extra_tag) { @@ -23001,6 +22462,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } else if (join->select_lex == join->unit->fake_select_lex) { +#if 0 select_lex->set_explain_type(on_the_fly); QPF_union *qp_union= new (output->mem_root) QPF_union; qp_node= qp_union; @@ -23017,6 +22479,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, test(select_lex->master_unit()->global_parameters->order_list.first); output->add_node(qp_union); +#endif } else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) @@ -23555,16 +23018,21 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, THD *thd=join->thd; select_result *result=join->result; DBUG_ENTER("select_describe"); -#if 0 - join->error= join->print_explain(result, thd->lex->describe, - FALSE, /* Not on-the-fly */ - need_tmp_table, need_order, distinct, - message); -#endif - //psergey-todo: save QPF here, too. + + // Update the QPF: + QPF_select *qp; + if ((qp= thd->lex->query_plan_footprint->get_select(join->select_lex->select_number))) + { + qp->using_temporary= need_tmp_table; + qp->using_filesort= need_order; + } + +/* + WE DONT NEED THIS here anymore: + join->save_qpf(thd->lex->query_plan_footprint, need_tmp_table, need_order, distinct, message); - +*/ for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; unit= unit->next_unit()) diff --git a/sql/sql_union.cc b/sql/sql_union.cc index eb4454ecab3..b77b0669b50 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -626,6 +626,9 @@ bool st_select_lex_unit::exec() saved_error= optimize(); + + save_union_qpf(thd->lex->query_plan_footprint); + if (uncacheable || !item || !item->assigned() || describe) { for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select()) @@ -772,6 +775,9 @@ bool st_select_lex_unit::exec() */ if (!fake_select_lex->ref_pointer_array) fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items; + + + save_union_qpf_part2(thd->lex->query_plan_footprint); saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, &result_table_list, From 8b7bbcf4dca77690091360e5ae7d011fb74554d6 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 16:41:12 +0400 Subject: [PATCH 19/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Make query plan be re-saved after the first join execution (saving it after JOIN::cleanup is too late because EXPLAIN output is currently produced before that) - Handle QPF allocation/deallocation for edge cases, like unsuccessful BINLOG command. - Work around the problem with UNION's direct subselects not being visible. - Update test results ("Using temporary; Using filesort" are now always printed last in the Extra column) - This cset gets rid of memory leaks/crashes. Some result mismatches still remain. --- mysql-test/r/subselect3.result | 2 +- mysql-test/r/subselect3_jcl6.result | 2 +- mysql-test/r/view.result | 2 +- sql/log_event.cc | 1 + sql/opt_qpf.cc | 16 +++++++++--- sql/opt_qpf.h | 2 ++ sql/sql_lex.cc | 3 ++- sql/sql_parse.cc | 8 +++++- sql/sql_select.cc | 38 +++++++++++++++++++++++++---- sql/sql_select.h | 3 +++ sql/sql_union.cc | 9 ++++--- 11 files changed, 68 insertions(+), 18 deletions(-) diff --git a/mysql-test/r/subselect3.result b/mysql-test/r/subselect3.result index 1a33fb0c6a3..24d9f0de35a 100644 --- a/mysql-test/r/subselect3.result +++ b/mysql-test/r/subselect3.result @@ -1031,7 +1031,7 @@ update t22 set c = '2005-12-08 15:58:27' where a = 255; explain select t21.* from t21,t22 where t21.a = t22.a and t22.a in (select t12.a from t11, t12 where t11.a in(255,256) and t11.a = t12.a and t11.c is null) and t22.c is null order by t21.a; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t11 ALL NULL NULL NULL NULL 8 Using where; Using temporary; Using filesort; Start temporary +1 PRIMARY t11 ALL NULL NULL NULL NULL 8 Using where; Start temporary; Using temporary; Using filesort 1 PRIMARY t12 ALL NULL NULL NULL NULL 8 Using where; Using join buffer (flat, BNL join) 1 PRIMARY t22 ALL NULL NULL NULL NULL 26 Using where; End temporary; Using join buffer (flat, BNL join) 1 PRIMARY t21 ALL NULL NULL NULL NULL 26 Using where; Using join buffer (flat, BNL join) diff --git a/mysql-test/r/subselect3_jcl6.result b/mysql-test/r/subselect3_jcl6.result index 5bae1453a5e..19d3d25148f 100644 --- a/mysql-test/r/subselect3_jcl6.result +++ b/mysql-test/r/subselect3_jcl6.result @@ -1041,7 +1041,7 @@ update t22 set c = '2005-12-08 15:58:27' where a = 255; explain select t21.* from t21,t22 where t21.a = t22.a and t22.a in (select t12.a from t11, t12 where t11.a in(255,256) and t11.a = t12.a and t11.c is null) and t22.c is null order by t21.a; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY t11 ALL NULL NULL NULL NULL 8 Using where; Using temporary; Using filesort; Start temporary +1 PRIMARY t11 ALL NULL NULL NULL NULL 8 Using where; Start temporary; Using temporary; Using filesort 1 PRIMARY t12 hash_ALL NULL #hash#$hj 4 test.t11.a 8 Using where; Using join buffer (flat, BNLH join) 1 PRIMARY t22 hash_ALL NULL #hash#$hj 4 test.t11.a 26 Using where; End temporary; Using join buffer (incremental, BNLH join) 1 PRIMARY t21 hash_ALL NULL #hash#$hj 4 test.t11.a 26 Using where; Using join buffer (incremental, BNLH join) diff --git a/mysql-test/r/view.result b/mysql-test/r/view.result index bc286c36655..7fc2fa72805 100644 --- a/mysql-test/r/view.result +++ b/mysql-test/r/view.result @@ -3584,7 +3584,7 @@ View Create View character_set_client collation_connection v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select `t1`.`a` AS `a`,`t1`.`b` AS `b` from `t1` FORCE INDEX (PRIMARY) FORCE INDEX (`b`) order by `t1`.`a` latin1 latin1_swedish_ci EXPLAIN SELECT * FROM v1; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 15 +1 SIMPLE t1 index NULL PRIMARY 4 NULL 15 CREATE VIEW v2 AS SELECT * FROM t1 USE KEY () ORDER BY a; SHOW CREATE VIEW v2; View Create View character_set_client collation_connection diff --git a/sql/log_event.cc b/sql/log_event.cc index f4e34dd9224..a89806fa708 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -9041,6 +9041,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) call might reset the value of current_stmt_binlog_format, so we need to do any changes to that value after this function. */ + delete_qpf_query(thd->lex); lex_start(thd); mysql_reset_thd_for_next_command(thd, 0); /* diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 309e24471eb..37a01a4f832 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -13,6 +13,7 @@ QPF_query::QPF_query() { upd_del_plan= NULL; + operations= 0; //memset(&unions, 0, sizeof(unions)); //memset(&selects, 0, sizeof(selects)); } @@ -51,15 +52,18 @@ QPF_select *QPF_query::get_select(uint select_id) void QPF_query::add_node(QPF_node *node) { + operations++; if (node->get_type() == QPF_node::QPF_UNION) { QPF_union *u= (QPF_union*)node; uint select_id= u->get_select_id(); - DBUG_ASSERT(!get_union(select_id)); - if (unions.elements() <= select_id) unions.resize(max(select_id+1, unions.elements()*2), NULL); + QPF_union *old_node; + if ((old_node= get_union(select_id))) + delete old_node; + unions.at(select_id)= u; } else @@ -73,10 +77,14 @@ void QPF_query::add_node(QPF_node *node) else { uint select_id= sel->select_id; - DBUG_ASSERT(!get_select(select_id)); - + QPF_select *old_node; + //DBUG_ASSERT(!get_select(select_id)); if (selects.elements() <= select_id) selects.resize(max(select_id+1, selects.elements()*2), NULL); + + if ((old_node= get_select(select_id))) + delete old_node; + selects.at(select_id)= sel; } } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 1944f79254d..35afcde10df 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -223,6 +223,8 @@ private: Dynamic_array selects; //QPF_union *unions[MAX_TABLES]; //QPF_select *selects[MAX_TABLES]; + + longlong operations; }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index c453a599dce..2e4eaf5b721 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2552,7 +2552,8 @@ void Query_tables_list::destroy_query_tables_list() */ LEX::LEX() - :result(0), option_type(OPT_DEFAULT), is_lex_started(0), + : query_plan_footprint(NULL), + result(0), option_type(OPT_DEFAULT), is_lex_started(0), limit_rows_examined_cnt(ULONGLONG_MAX) { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 6f2f7eaca20..9e477e62e19 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4750,8 +4750,14 @@ finish: ha_maria::implicit_commit(thd, FALSE); #endif } - lex->unit.cleanup(); + //psergey-todo: print EXPLAIN here? After the above JOIN::cleanup calls? + // how do we print EXPLAIN extended, then? + if (lex->describe) + { + DBUG_ASSERT(lex->query_plan_footprint); + ///.. + } /* Free tables */ thd_proc_info(thd, "closing tables"); close_thread_tables(thd); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5e20d2faf24..5a5928f9b73 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2347,6 +2347,36 @@ void JOIN::exec() exec_inner(); + if (!exec_qpf_saved) + { + if (select_lex->select_number != UINT_MAX && + select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && + have_query_plan != QEP_NOT_PRESENT_YET && + have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then + //cleanup() is called multiple times + + thd->lex->query_plan_footprint //&& // for "SET" command in SPs. + /*!thd->lex->query_plan_footprint->get_select(select_lex->select_number)*/) + { + const char *message= NULL; + + if (!table_count || !tables_list || zero_result_cause) + { + /* It's a degenerate join */ + message= zero_result_cause ? zero_result_cause : "No tables used"; + } + + save_qpf(thd->lex->query_plan_footprint, + need_tmp, // need_tmp_table + // !skip_sort_order && !no_order && + // (order || group_list), // bool need_order + order != 0 && !skip_sort_order, + select_distinct, // bool distinct + message); // message + } + exec_qpf_saved= true; + } + DBUG_EXECUTE_IF("show_explain_probe_join_exec_end", if (dbug_user_var_equals_int(thd, "show_explain_probe_select_id", @@ -11151,7 +11181,7 @@ void JOIN::cleanup(bool full) if (full) { - // + /* Save it again */ #if 0 if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && @@ -11159,8 +11189,8 @@ void JOIN::cleanup(bool full) have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then //cleanup() is called multiple times - thd->lex->query_plan_footprint && // for "SET" command in SPs. - !thd->lex->query_plan_footprint->get_select(select_lex->select_number)) + thd->lex->query_plan_footprint //&& // for "SET" command in SPs. + /*!thd->lex->query_plan_footprint->get_select(select_lex->select_number)*/) { const char *message= NULL; @@ -11178,8 +11208,6 @@ void JOIN::cleanup(bool full) message); // message } #endif - // - have_query_plan= QEP_DELETED; //psergey: this is a problem! } diff --git a/sql/sql_select.h b/sql/sql_select.h index 6d4dfed479e..b394ee40276 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1336,7 +1336,10 @@ public: pre_sort_join_tab= NULL; emb_sjm_nest= NULL; sjm_lookup_tables= 0; + + exec_qpf_saved= false; } + bool exec_qpf_saved; int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, COND *conds, uint og_num, ORDER *order, ORDER *group, diff --git a/sql/sql_union.cc b/sql/sql_union.cc index b77b0669b50..e4a9c7630e6 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -617,6 +617,7 @@ bool st_select_lex_unit::exec() ulonglong add_rows=0; ha_rows examined_rows= 0; DBUG_ENTER("st_select_lex_unit::exec"); + bool was_executed= executed; if (executed && !uncacheable && !describe) DBUG_RETURN(FALSE); @@ -626,8 +627,8 @@ bool st_select_lex_unit::exec() saved_error= optimize(); - - save_union_qpf(thd->lex->query_plan_footprint); + if (!was_executed && thd->lex->query_plan_footprint) + save_union_qpf(thd->lex->query_plan_footprint); if (uncacheable || !item || !item->assigned() || describe) { @@ -776,8 +777,8 @@ bool st_select_lex_unit::exec() if (!fake_select_lex->ref_pointer_array) fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items; - - save_union_qpf_part2(thd->lex->query_plan_footprint); + if (!was_executed && thd->lex->query_plan_footprint) + save_union_qpf_part2(thd->lex->query_plan_footprint); saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, &result_table_list, From d634638c5613683dd0690c1ed40db1bb75877e1c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 18:52:47 +0400 Subject: [PATCH 20/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - If a subquery is correlated wrt a const table, it will change from being a "DEPENDENT SUBQUERY" into "SUBQUERY", at the end of its parent's JOIN::optimize() call. Handle this, update the subquery's QPF. - Make show_explain.test to work = "Query plan already deleted" does not happen anymore. = Handle special case of queries that don't have top-level selects, like SET x = (SELECT ...) --- mysql-test/r/show_explain.result | 33 +++++++++++++++++++++++++------- mysql-test/t/show_explain.test | 9 ++++----- sql/opt_qpf.h | 4 +++- sql/sql_lex.cc | 14 +++++++++++++- 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 2a6f0ca583b..3092fdc41bd 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -165,7 +165,11 @@ set @show_explain_probe_select_id=1; set debug_dbug='+d,show_explain_probe_join_exec_end'; select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY a ALL NULL NULL NULL NULL 10 Using where +2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1 a (select max(a) from t0 b where b.a+a.a<10) 0 9 set debug_dbug=@old_debug; @@ -343,7 +347,11 @@ SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; # FIXED by "conservative assumptions about when QEP is available" fix: # NOTE: current code will not show "Using join buffer": show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using join buffer (flat, BNL join) +Warnings: +Note 1003 SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a a 1 2 @@ -428,7 +436,10 @@ set @show_explain_probe_select_id=1; set debug_dbug='+d,show_explain_probe_join_exec_end'; select * from t0 where 1>10; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +Warnings: +Note 1003 select * from t0 where 1>10 a set debug_dbug=@old_debug; # @@ -440,7 +451,10 @@ set @show_explain_probe_select_id=1; set debug_dbug='+d,show_explain_probe_join_exec_end'; select * from t0,t3 where t3.a=112233; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL no matching row in const table +Warnings: +Note 1003 select * from t0,t3 where t3.a=112233 a a set debug_dbug=@old_debug; drop table t3; @@ -545,7 +559,12 @@ set @show_explain_probe_select_id=1; set debug_dbug='+d,show_explain_probe_join_exec_end'; SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`); show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY const distinct_key distinct_key 8 const,const 1 +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 Using join buffer (flat, BNL join) +2 MATERIALIZED t2 index NULL a1 4 NULL 20 Using index +Warnings: +Note 1003 SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`) pk a1 set debug_dbug=@old_debug; DROP TABLE t2; @@ -651,7 +670,7 @@ SELECT a + 1 FROM v1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY ALL NULL NULL NULL NULL 2 -2 DERIVED NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DERIVED t1 ALL NULL NULL NULL NULL 2 Warnings: Note 1003 SELECT a + 1 FROM v1 a + 1 @@ -1061,7 +1080,7 @@ show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY alias1 ALL NULL NULL NULL NULL 14 1 PRIMARY t2 ALL NULL NULL NULL NULL 20 -3 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +3 SUBQUERY t3 ALL NULL NULL NULL NULL 20 Using where Warnings: Note 1003 SELECT max(a+b+c) FROM t1 AS alias1, ( SELECT * FROM t2 ) AS alias WHERE EXISTS ( SELECT * FROM t3 WHERE b = c ) OR a <= 10 diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 52c680f57de..8ab1813f5bb 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -202,7 +202,6 @@ set debug_dbug='+d,show_explain_probe_join_exec_end'; send select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -349,7 +348,7 @@ connection default; --source include/wait_condition.inc --echo # FIXED by "conservative assumptions about when QEP is available" fix: --echo # NOTE: current code will not show "Using join buffer": ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -428,7 +427,7 @@ set debug_dbug='+d,show_explain_probe_join_exec_end'; send select * from t0 where 1>10; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -444,7 +443,7 @@ set debug_dbug='+d,show_explain_probe_join_exec_end'; send select * from t0,t3 where t3.a=112233; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +# --error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -540,7 +539,7 @@ send SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`); connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +# --error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 35afcde10df..2af04ab28a7 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -216,7 +216,9 @@ public: /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); - + + /* If true, at least part of EXPLAIN can be printed */ + bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; } MEM_ROOT *mem_root; private: Dynamic_array unions; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 2e4eaf5b721..f5aa84b6f41 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3495,6 +3495,18 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) is_correlated_unit|= sl->is_correlated; inner_join->select_options= save_options; un->thd->lex->current_select= save_select; + /// psergey: + QPF_query *qpf; + if ((qpf= inner_join->thd->lex->query_plan_footprint)) + { + QPF_select *qp_sel; + if ((qp_sel= qpf->get_select(inner_join->select_lex->select_number))) + { + sl->set_explain_type(TRUE); + qp_sel->select_type= sl->type; + } + } + /// if (empty_union_result) { /* @@ -4182,7 +4194,7 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) //TODO: remove printed_anything { int res; - if (query_plan_footprint) + if (query_plan_footprint && query_plan_footprint->have_query_plan()) { res= query_plan_footprint->print_explain(output, explain_flags); *printed_anything= true; From c0f7efb1aeeabebe502a786cc461a9d76bf18487 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 16:28:57 +0400 Subject: [PATCH 21/64] Code cleanup --- sql/opt_qpf.cc | 1 - sql/sql_lex.cc | 105 +++++++--------------------------------------- sql/sql_lex.h | 7 +--- sql/sql_select.cc | 40 +++++++----------- sql/sql_update.cc | 1 - 5 files changed, 29 insertions(+), 125 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 37a01a4f832..34b1a213207 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -339,7 +339,6 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl push_str(&item_list, join_type_str[type]); /* `possible_keys` column */ - //push_str(item_list, "TODO"); if (possible_keys_str.length() > 0) push_string(&item_list, &possible_keys_str); else diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f5aa84b6f41..9337ed5c356 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4191,7 +4191,7 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) */ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything) //TODO: remove printed_anything + bool *printed_anything) { int res; if (query_plan_footprint && query_plan_footprint->have_query_plan()) @@ -4209,74 +4209,20 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, /* - + Save query plan of a UNION. The only variable member is whether the union has + "Using filesort". + + There is also save_union_qpf_part2() function, which is called before we read + UNION's output. + + The reason for it is examples like this: + + SELECT col1 FROM t1 UNION SELECT col2 FROM t2 ORDER BY (select ... from t3 ...) + + Here, the (select ... from t3 ...) subquery must be a child of UNION's + st_select_lex. However, it is not connected as child until a very late + stage in execution. */ -void st_select_lex::save_qpf(QPF_query *output) -{ - int res; - if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) - { - /* - There is a number of reasons join can be marked as degenerate, so all - three conditions below can happen simultaneously, or individually: - */ - if (!join->table_count || !join->tables_list || join->zero_result_cause) - { - /* It's a degenerate join */ - const char *cause= join->zero_result_cause ? join-> zero_result_cause : - "No tables used"; - res= join->save_qpf(output, FALSE, FALSE, FALSE, cause); - } - else - { - join->save_qpf(output, join->need_tmp, // need_tmp_table - !join->skip_sort_order && !join->no_order && - (join->order || join->group_list), // bool need_order - join->select_distinct, // bool distinct - NULL); //const char *message - } - if (res) - goto err; - - for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); - unit; - unit= unit->next_unit()) - { - /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. - */ - if (!(unit->item && unit->item->eliminated)) - { - unit->save_qpf(output); - } - } - } - else - { - const char *msg; - if (!join) - DBUG_ASSERT(0); /* Seems not to be possible */ - - /* Not printing anything useful, don't touch *printed_anything here */ - if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET) - msg= "Not yet optimized"; - else - { - DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); - msg= "Query plan already deleted"; - } - set_explain_type(TRUE/* on_the_fly */); - QPF_select *qp_sel= new (output->mem_root) QPF_select; - qp_sel->select_id= select_number; - qp_sel->select_type= type; - qp_sel->message= msg; - output->add_node(qp_sel); - } -err: - return ;//res; -} - int st_select_lex_unit::save_union_qpf(QPF_query *output) { @@ -4307,11 +4253,7 @@ int st_select_lex_unit::save_union_qpf(QPF_query *output) } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) - { - if (!output->get_select(sl->select_number)) - sl->save_qpf(output); qpfu->add_select(sl->select_number); - } // Save the UNION node output->add_node(qpfu); @@ -4340,25 +4282,6 @@ int st_select_lex_unit::save_union_qpf_part2(QPF_query *output) } -int st_select_lex_unit::save_qpf(QPF_query *output) -{ - //int res= 0; - SELECT_LEX *first= first_select(); - - if (!first->next_select()) - { - /* This is a 1-way UNION, i.e. not really a UNION */ - if (!output->get_select(first->select_number)) - first->save_qpf(output); - return 0; - } - - save_union_qpf(output); - - return 0; -} - - /** A routine used by the parser to decide whether we are specifying a full partitioning or if only partitions to add or to split. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 77abc46d30b..4fe0e8b4d8b 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -732,7 +732,6 @@ public: List *get_unit_column_types(); - int save_qpf(QPF_query *output); int save_union_qpf(QPF_query *output); int save_union_qpf_part2(QPF_query *output); }; @@ -1053,11 +1052,7 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); -#if 0 - int print_explain(select_result_sink *output, uint8 explain_flags, - bool *printed_anything); -#endif - void save_qpf(QPF_query *output); + /* For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags: - Non-aggregated fields are used in this select. diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5a5928f9b73..201d1a6cc80 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22464,7 +22464,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, const CHARSET_INFO *cs= system_charset_info; int quick_type; int error= 0; - DBUG_ENTER("JOIN::print_explain"); + DBUG_ENTER("JOIN::save_qpf"); DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", (ulong)join->select_lex, join->select_lex->type, message ? message : "NULL")); @@ -22920,14 +22920,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (table->reginfo.not_exists_optimize) qpt->push_extra(ET_NOT_EXISTS); - /* - if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE && - !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags & - HA_MRR_USE_DEFAULT_IMPL)) - { - extra.append(STRING_WITH_LEN("; Using MRR")); - } - */ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) { explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), @@ -22940,13 +22932,11 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { need_tmp_table=0; qp_sel->using_temporary= true; - ///extra.append(STRING_WITH_LEN("; Using temporary")); } if (need_order) { need_order=0; qp_sel->using_filesort= true; - ///extra.append(STRING_WITH_LEN("; Using filesort")); } if (distinct & test_all_bits(used_tables, join->select_list_used_tables)) @@ -22968,11 +22958,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { qpt->push_extra(ET_FIRST_MATCH); TABLE *prev_table=tab->do_firstmatch->table; - /* - TODO: qpt->firstmatch_table... - This must be a reference to another QPF element. Or, its index. - */ - // extra.append(STRING_WITH_LEN("; FirstMatch(")); if (prev_table->derived_select_number) { char namebuf[NAME_LEN]; @@ -22984,7 +22969,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } else qpt->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); - //extra.append(STRING_WITH_LEN(")")); } } @@ -23033,11 +23017,21 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, DBUG_RETURN(error); } -////////////////////////////////////////////////////////////////////////////////////////////// - /* - See st_select_lex::print_explain() for the SHOW EXPLAIN counterpart + This function servers as "shortcut point" for EXPLAIN queries. + + For UNIONs and JOINs, EXPLAIN statement executes just like its SELECT + statement would execute, except that JOIN::exec() will call select_describe() + instead of actually executing the query. + + The purpose of select_describe() is: + - update the query plan with info about last-minute choices made at the start + of JOIN::exec + - Invoke "pseudo-execution" for the children subqueries. + + Overall, select_describe() is a legacy of old EXPLAIN implementation and + should be removed. */ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, @@ -23055,12 +23049,6 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, qp->using_filesort= need_order; } -/* - WE DONT NEED THIS here anymore: - - join->save_qpf(thd->lex->query_plan_footprint, need_tmp_table, need_order, - distinct, message); -*/ for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; unit= unit->next_unit()) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 96f49785a32..66a0a1eca21 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1023,7 +1023,6 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - //int err2= thd->lex->print_explain(result, 0 /* explain flags*/, &printed_anything); int err2= thd->lex->query_plan_footprint->print_explain(result, 0 /* explain flags*/); if (err2) From 5422098b758dfec801b32c833b8840ec96e00081 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 17:02:44 +0400 Subject: [PATCH 22/64] More of code cleanup --- sql/opt_qpf.h | 54 +++++++++++++++++++++++++++-------------------- sql/sql_select.cc | 11 ++-------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 2af04ab28a7..b82f004ff7f 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -50,18 +50,19 @@ class QPF_table_access; Query Plan Footprint of a SELECT. A select can be: - - a degenerate case. In this case, message!=NULL, and it contains a - description of what kind of degenerate case it is (e.g. "Impossible - WHERE"). - - a join. Here join_tabs has an array of JOIN_TAB query plan footprints. + 1. A degenerate case. In this case, message!=NULL, and it contains a + description of what kind of degenerate case it is (e.g. "Impossible + WHERE"). + 2. a non-degenrate join. In this case, join_tabs describes the join. In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation. - In both cases, a select may have children selects (see QPF_node) + + In both cases, the select may have children nodes. class QPF_node provides + a way get node's children. */ class QPF_select : public QPF_node { - /*Construction interface */ public: enum qpf_node_type get_type() { return QPF_SELECT; } @@ -107,10 +108,6 @@ public: bool using_temporary; bool using_filesort; - void print_tabular(select_result_sink *output, uint8 explain_flags//, - //bool *printed_anything - ); - int print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags); }; @@ -223,9 +220,13 @@ public: private: Dynamic_array unions; Dynamic_array selects; - //QPF_union *unions[MAX_TABLES]; - //QPF_select *selects[MAX_TABLES]; - + + /* + Debugging aid: count how many times add_node() was called. Ideally, it + should be one, we currently allow O(1) query plan saves for each + select or union. The goal is not to have O(#rows_in_some_table), which + is unacceptable. + */ longlong operations; }; @@ -303,9 +304,6 @@ public: uint key_no; uint key_length; - Dynamic_array extra_tags; - - //temporary: bool key_set; /* not set means 'NULL' should be printed */ StringBuffer<64> key; @@ -315,15 +313,18 @@ public: bool ref_set; /* not set means 'NULL' should be printed */ StringBuffer<64> ref; - bool rows_set; + bool rows_set; /* not set means 'NULL' should be printed */ ha_rows rows; - bool filtered_set; + bool filtered_set; /* not set means 'NULL' should be printed */ double filtered; - /* Various stuff for 'Extra' column*/ - uint join_cache_level; - + /* + Contents of the 'Extra' column. Some are converted into strings, some have + parameters, values for which are stored below. + */ + Dynamic_array extra_tags; + // Valid if ET_USING tag is present StringBuffer<64> quick_info; @@ -351,7 +352,10 @@ private: /* - Query Plan Footprint for an UPDATE statement + Query Plan Footprint for single-table UPDATE. + + This is similar to QPF_table_access, except that it is more restrictive. + Also, it can have UPDATE operation options, but currently there aren't any. */ class QPF_update : public QPF_node @@ -382,12 +386,16 @@ public: /* - Query Plan Footprint for a DELETE statement + Query Plan Footprint for a single-table DELETE. */ class QPF_delete: public QPF_update { public: + /* + TRUE means we're going to call handler->delete_all_rows() and not read any + rows. + */ bool deleting_all_rows; virtual enum qpf_node_type get_type() { return QPF_DELETE; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 201d1a6cc80..ed1cb35c29a 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22448,9 +22448,9 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) /* Save Query Plan Footprint - push_extra - P + @note + Currently, this function may be called multiple times */ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, @@ -22471,11 +22471,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); /* Don't log this into the slow query log */ - - /* - NOTE: the number/types of items pushed into item_list must be in sync with - EXPLAIN column types as they're "defined" in THD::send_explain_fields() - */ if (message) { QPF_select *qp_sel; @@ -22998,8 +22993,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, output->add_node(qp_sel); } - //TODO: can a UNION have subquery children that are not union members? yes, - //perhaps... for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; unit= unit->next_unit()) From befacafd73d4892f2ad84991ad7c2d4626e45c47 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Jun 2013 17:56:49 +0400 Subject: [PATCH 23/64] [SHOW] EXPLAIN UPDATE/DELETE, code re-structuring - Let Query Plan Footprint store join buffer type in binary form, not string. - Same for LooseScan type. --- sql/opt_qpf.cc | 15 +++++++++++++-- sql/opt_qpf.h | 14 ++++++++++++-- sql/opt_range.h | 6 +----- sql/sql_join_cache.cc | 35 +++++++++++++---------------------- sql/sql_join_cache.h | 7 ++++--- sql/sql_select.cc | 5 ++--- 6 files changed, 45 insertions(+), 37 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 34b1a213207..6316fc0f0e9 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -489,7 +489,17 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) case ET_USING_JOIN_BUFFER: { str->append(extra_tag_text[tag]); - str->append(join_buffer_type); + + str->append(STRING_WITH_LEN(" (")); + const char *buffer_type= bka_type.incremental ? "incremental" : "flat"; + str->append(buffer_type); + str->append(STRING_WITH_LEN(", ")); + str->append(bka_type.join_alg); + str->append(STRING_WITH_LEN(" join")); + str->append(STRING_WITH_LEN(")")); + if (bka_type.mrr_type.length()) + str->append(bka_type.mrr_type); + break; } case ET_FIRST_MATCH: @@ -507,7 +517,8 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) case ET_USING_INDEX_FOR_GROUP_BY: { str->append(extra_tag_text[tag]); - str->append(loose_scan_type); + if (loose_scan_is_scanning) + str->append(" (scanning)"); break; } default: diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index b82f004ff7f..67f6b591e92 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -272,6 +272,15 @@ enum Extra_tag }; +typedef struct st_qpf_bka_type +{ + bool incremental; + const char *join_alg; + StringBuffer<64> mrr_type; + +} QPF_BKA_TYPE; + + /* Query Plan Footprint for a JOIN_TAB. */ @@ -329,7 +338,7 @@ public: StringBuffer<64> quick_info; // Valid if ET_USING_INDEX_FOR_GROUP_BY is present - StringBuffer<64> loose_scan_type; + bool loose_scan_is_scanning; // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD key_map range_checked_map; @@ -338,7 +347,8 @@ public: StringBuffer<64> mrr_type; // valid with ET_USING_JOIN_BUFFER - StringBuffer<64> join_buffer_type; + //StringBuffer<64> join_buffer_type; + QPF_BKA_TYPE bka_type; //TABLE *firstmatch_table; StringBuffer<64> firstmatch_table_name; diff --git a/sql/opt_range.h b/sql/opt_range.h index ddaa5c5e59a..e67274c19a6 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -944,11 +944,7 @@ public: void dbug_dump(int indent, bool verbose); #endif bool is_agg_distinct() { return have_agg_distinct; } - virtual void append_loose_scan_type(String *str) - { - if (is_index_scan) - str->append(STRING_WITH_LEN(" (scanning)")); - } + bool loose_scan_is_scanning() { return is_index_scan; } }; diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 9fca8730cb5..7710db5c7ba 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2568,34 +2568,26 @@ finish: none */ -void JOIN_CACHE::print_explain_comment(String *str) +void JOIN_CACHE::save_qpf(struct st_qpf_bka_type *qpf) { - str->append(STRING_WITH_LEN(" (")); - const char *buffer_type= prev_cache ? "incremental" : "flat"; - str->append(buffer_type); - str->append(STRING_WITH_LEN(", ")); - - const char *join_alg=""; + qpf->incremental= test(prev_cache); + switch (get_join_alg()) { case BNL_JOIN_ALG: - join_alg= "BNL"; + qpf->join_alg= "BNL"; break; case BNLH_JOIN_ALG: - join_alg= "BNLH"; + qpf->join_alg= "BNLH"; break; case BKA_JOIN_ALG: - join_alg= "BKA"; + qpf->join_alg= "BKA"; break; case BKAH_JOIN_ALG: - join_alg= "BKAH"; + qpf->join_alg= "BKAH"; break; default: DBUG_ASSERT(0); } - - str->append(join_alg); - str->append(STRING_WITH_LEN(" join")); - str->append(STRING_WITH_LEN(")")); } /** @@ -2621,18 +2613,17 @@ static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file) } } - -void JOIN_CACHE_BKA::print_explain_comment(String *str) +void JOIN_CACHE_BKA::save_qpf(struct st_qpf_bka_type *qpf) { - JOIN_CACHE::print_explain_comment(str); - add_mrr_explain_info(str, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_qpf(qpf); + add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); } -void JOIN_CACHE_BKAH::print_explain_comment(String *str) +void JOIN_CACHE_BKAH::save_qpf(struct st_qpf_bka_type *qpf) { - JOIN_CACHE::print_explain_comment(str); - add_mrr_explain_info(str, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_qpf(qpf); + add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); } diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h index 6953f6881ee..75589c3395f 100644 --- a/sql/sql_join_cache.h +++ b/sql/sql_join_cache.h @@ -63,6 +63,7 @@ typedef struct st_cache_field { class JOIN_TAB_SCAN; +struct st_qpf_bka_type; /* JOIN_CACHE is the base class to support the implementations of @@ -657,7 +658,7 @@ public: enum_nested_loop_state join_records(bool skip_last); /* Add a comment on the join algorithm employed by the join cache */ - virtual void print_explain_comment(String *str); + virtual void save_qpf(struct st_qpf_bka_type *qpf); THD *thd(); @@ -1335,7 +1336,7 @@ public: /* Check index condition of the joined table for a record from BKA cache */ bool skip_index_tuple(range_id_t range_info); - void print_explain_comment(String *str); + void save_qpf(struct st_qpf_bka_type *qpf); }; @@ -1426,5 +1427,5 @@ public: /* Check index condition of the joined table for a record from BKAH cache */ bool skip_index_tuple(range_id_t range_info); - void print_explain_comment(String *str); + void save_qpf(struct st_qpf_bka_type *qpf); }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index ed1cb35c29a..763c719f250 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22907,7 +22907,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, QUICK_GROUP_MIN_MAX_SELECT *qgs= (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; qpt->push_extra(ET_USING_INDEX_FOR_GROUP_BY); - qgs->append_loose_scan_type(&qpt->loose_scan_type); + qpt->loose_scan_is_scanning= qgs->loose_scan_is_scanning(); } else qpt->push_extra(ET_USING_INDEX); @@ -22979,9 +22979,8 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (tab->cache) { qpt->push_extra(ET_USING_JOIN_BUFFER); - tab->cache->print_explain_comment(&qpt->join_buffer_type); + tab->cache->save_qpf(&qpt->bka_type); } - } if (saved_join_tab) From eeb671325723706bbd423e00982919333a9f54cc Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 26 Aug 2013 14:43:52 +0400 Subject: [PATCH 24/64] [SHOW] EXPLAIN UPDATE/DELETE - Post-merge fixes (conflict with DELETE .. RETURNING) - Add a testcase with EXPLAIN ... DELETE ... RETURNING --- mysql-test/t/explain_non_select.test | 11 +++++++++++ sql/sql_delete.cc | 14 +++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 523e041f620..7385ef5d420 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -84,3 +84,14 @@ explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; drop table t0, t1; + +--echo # +--echo # Try DELETE ... RETURNING ... +--echo # +create table t0 (a int); +insert into t0 values (1),(2),(3),(4); +explain delete from t0 where a=1 returning a; +explain delete from t0 returning a; +drop table t0; + + diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 92ce69e5686..5655bb12886 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -658,18 +658,18 @@ cleanup: exit_without_my_ok: query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); - select_send *result; - if (!(result= new select_send())) + select_send *result2; + if (!(result2= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ - result->prepare(dummy, &thd->lex->unit); - thd->send_explain_fields(result); - int err2= thd->lex->query_plan_footprint->print_explain(result, 0 /* explain flags*/); + result2->prepare(dummy, &thd->lex->unit); + thd->send_explain_fields(result2); + int err2= thd->lex->query_plan_footprint->print_explain(result2, 0 /* explain flags*/); if (err2) - result->abort_result_set(); + result2->abort_result_set(); else - result->send_eof(); + result2->send_eof(); delete select; free_underlaid_joins(thd, select_lex); From 69c386d9a69aa73418160fae3626762213f649cc Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 4 Sep 2013 15:37:33 +0400 Subject: [PATCH 25/64] Code cleanup --- mysql-test/r/explain_non_select.result | 12 ++++++ sql/opt_qpf.cc | 46 +++++++++++++---------- sql/opt_qpf.h | 7 +++- sql/sql_select.cc | 52 ++++++++------------------ sql/sql_select.h | 1 - 5 files changed, 60 insertions(+), 58 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index d664e7241e2..021c5d92daa 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -102,3 +102,15 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where 1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index drop table t0, t1; +# +# Try DELETE ... RETURNING ... +# +create table t0 (a int); +insert into t0 values (1),(2),(3),(4); +explain delete from t0 where a=1 returning a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 4 Using where +explain delete from t0 returning a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 4 +drop table t0; diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 6316fc0f0e9..8155f28acb7 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -14,8 +14,6 @@ QPF_query::QPF_query() { upd_del_plan= NULL; operations= 0; - //memset(&unions, 0, sizeof(unions)); - //memset(&selects, 0, sizeof(selects)); } @@ -105,18 +103,13 @@ int QPF_query::print_explain(select_result_sink *output, } else { - // Start with id=1 + // Start printing from id=1 QPF_node *node= get_node(1); return node->print_explain(this, output, explain_flags); } } -void QPF_union::push_table_name(List *item_list) -{ -} - - static void push_str(List *item_list, const char *str) { item_list->push_back(new Item_string(str, @@ -134,7 +127,7 @@ static void push_string(List *item_list, String *str) int QPF_union::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { - // print all children, in order + /* print all UNION children, in order */ for (int i= 0; i < (int) union_members.elements(); i++) { QPF_select *sel= query->get_select(union_members.at(i)); @@ -152,7 +145,6 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, push_str(&item_list, fake_select_type); /* `table` column: something like "" */ - // { char table_name_buffer[SAFE_NAME_LEN]; uint childno= 0; @@ -180,8 +172,6 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, const CHARSET_INFO *cs= system_charset_info; item_list.push_back(new Item_string(table_name_buffer, len, cs)); } - // - push_table_name(&item_list); /* `partitions` column */ if (explain_flags & DESCRIBE_PARTITIONS) @@ -221,11 +211,19 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, //output->unit.offset_limit_cnt= 0; if (output->send_data(item_list)) return 1; - + + /* + Print all subquery children (UNION children have already been printed at + the start of this function) + */ return print_explain_for_children(query, output, explain_flags); } +/* + Print EXPLAINs for all children nodes (i.e. for subqueries) +*/ + int QPF_node::print_explain_for_children(QPF_query *query, select_result_sink *output, uint8 explain_flags) @@ -301,6 +299,12 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, } +void QPF_table_access::push_extra(enum Extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags, uint select_id, const char *select_type, bool using_temporary, bool using_filesort) @@ -422,13 +426,17 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl } +/* + Elements in this array match members of enum Extra_tag, defined in opt_qpf.h. +*/ + const char * extra_tag_text[]= { "ET_none", "Using index condition", "Using index condition(BKA)", - "Using ", //special - "Range checked for each record (index map: 0x", //special + "Using ", // special handling + "Range checked for each record (index map: 0x", // special handling "Using where with pushed condition", "Using where", "Not exists", @@ -443,17 +451,17 @@ const char * extra_tag_text[]= "Scanned 1 database", "Scanned all databases", - "Using index for group-by", // Special? + "Using index for group-by", // special handling - "USING MRR: DONT PRINT ME", // Special! + "USING MRR: DONT PRINT ME", // special handling "Distinct", "LooseScan", "Start temporary", "End temporary", - "FirstMatch", //TODO: also handle special variant! + "FirstMatch", // special handling - "Using join buffer", // Special!, + "Using join buffer", // special handling "const row not found", "unique row not found", diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 67f6b591e92..f8f0004c669 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -146,7 +146,6 @@ public: { union_members.append(select_no); } - void push_table_name(List *item_list); int print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags); @@ -231,6 +230,11 @@ private: }; +/* + Some of the tags have matching text. See extra_tag_text for text names, and + QPF_table_access::append_tag_name() for code to convert from tag form to text + form. +*/ enum Extra_tag { ET_none= 0, /* not-a-tag */ @@ -347,7 +351,6 @@ public: StringBuffer<64> mrr_type; // valid with ET_USING_JOIN_BUFFER - //StringBuffer<64> join_buffer_type; QPF_BKA_TYPE bka_type; //TABLE *firstmatch_table; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 9ec348f07c0..97f59d80ae2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11172,7 +11172,7 @@ void JOIN::cleanup(bool full) if (full) { /* Save it again */ -#if 0 +#if 0 psergey-todo: remove? if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && @@ -22487,11 +22487,7 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) } -///////////////////////////////////////////////////////////////////////////////////////////////// -void QPF_table_access::push_extra(enum Extra_tag extra_tag) -{ - extra_tags.append(extra_tag); -} +/////////////////////////////////////////////////////////////////////////////// void append_possible_keys(String *str, TABLE *table, key_map possible_keys) { @@ -22548,24 +22544,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } else if (join->select_lex == join->unit->fake_select_lex) { -#if 0 - select_lex->set_explain_type(on_the_fly); - QPF_union *qp_union= new (output->mem_root) QPF_union; - qp_node= qp_union; - - SELECT_LEX *child; - for (child= select_lex->master_unit()->first_select(); child; - child=child->next_select()) - { - qp_union->add_select(child->select_number); - } - - qp_union->fake_select_type= select_lex->type; - qp_union->using_filesort= - test(select_lex->master_unit()->global_parameters->order_list.first); - - output->add_node(qp_union); -#endif + /* Do nothing, QPF_union will create and print fake_select_lex */ } else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) @@ -22713,7 +22692,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, // tmp2 holds key_name // tmp3 holds key_length - // tmp4 holds ref? + // tmp4 holds ref if (tab_type == JT_NEXT) { key_info= table->key_info+tab->index; @@ -23074,16 +23053,16 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, /* - This function servers as "shortcut point" for EXPLAIN queries. + This function serves as "shortcut point" for EXPLAIN queries. + + The EXPLAIN statement executes just like its SELECT counterpart would + execute, except that JOIN::exec() will call select_describe() instead of + actually executing the query. - For UNIONs and JOINs, EXPLAIN statement executes just like its SELECT - statement would execute, except that JOIN::exec() will call select_describe() - instead of actually executing the query. - - The purpose of select_describe() is: - - update the query plan with info about last-minute choices made at the start - of JOIN::exec - - Invoke "pseudo-execution" for the children subqueries. + Inside select_describe(): + - Query plan is updated with latest QEP choices made at the start of + JOIN::exec(). + - the proces of "almost execution" is invoked for the children subqueries. Overall, select_describe() is a legacy of old EXPLAIN implementation and should be removed. @@ -23096,9 +23075,10 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, select_result *result=join->result; DBUG_ENTER("select_describe"); - // Update the QPF: + /* Update the QPF with latest values of using_temporary, using_filesort */ QPF_select *qp; - if ((qp= thd->lex->query_plan_footprint->get_select(join->select_lex->select_number))) + uint select_nr= join->select_lex->select_number; + if ((qp= thd->lex->query_plan_footprint->get_select(select_nr))) { qp->using_temporary= need_tmp_table; qp->using_filesort= need_order; diff --git a/sql/sql_select.h b/sql/sql_select.h index 16e5a91c0cd..75d2aaf6631 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -259,7 +259,6 @@ typedef struct st_join_table { /* Special content for EXPLAIN 'Extra' column or NULL if none */ enum Extra_tag info; - //const char *info; /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' From abf4a910f29b230efbb42eda1199533fb5b93173 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 17 Sep 2013 15:01:34 +0400 Subject: [PATCH 26/64] [SHOW] EXPLAIN UPDATE/DELETE - Make QPF structures store data members, not strings. This is not fully possible, because table names (and hence key names, etc) can be deleted, and we have to store strings. --- sql/opt_qpf.cc | 201 ++++++++++++++++++++++++++++++++++++++++++++-- sql/opt_qpf.h | 95 +++++++++++++++++----- sql/opt_range.cc | 106 +++++++++++++++--------- sql/opt_range.h | 22 +++-- sql/sql_select.cc | 114 ++++++++++---------------- 5 files changed, 385 insertions(+), 153 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 8155f28acb7..d3fd88ce9bd 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -1,6 +1,18 @@ /* - TODO MP AB copyright -*/ + Copyright (c) 2013 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifdef USE_PRAGMA_IMPLEMENTATION #pragma implementation // gcc: Class implementation @@ -309,6 +321,11 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl uint select_id, const char *select_type, bool using_temporary, bool using_filesort) { + const CHARSET_INFO *cs= system_charset_info; + const char *hash_key_prefix= "#hash#"; + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + List item_list; Item *item_null= new Item_null(); //const CHARSET_INFO *cs= system_charset_info; @@ -349,14 +366,62 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl item_list.push_back(item_null); /* `key` */ - if (key_set) - push_string(&item_list, &key); + StringBuffer<64> key_str; + if (key.key_name) + { + if (is_hj) + key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs); + + key_str.append(key.key_name); + + if (is_hj && type != JT_HASH) + key_str.append(':'); + } + + if (quick_info) + { + StringBuffer<64> buf2; + quick_info->print_key(&buf2); + key_str.append(buf2); + } + if (type == JT_HASH_NEXT) + key_str.append(hash_next_key.key_name); + + if (key_str.length() > 0) + push_string(&item_list, &key_str); else item_list.push_back(item_null); /* `key_len` */ - if (key_len_set) - push_string(&item_list, &key_len); + StringBuffer<64> key_len_str; + + if (key.key_len != (uint)-1) + { + char buf[64]; + size_t length; + length= longlong10_to_str(key.key_len, buf, 10) - buf; + key_len_str.append(buf, length); + if (is_hj && type != JT_HASH) + key_len_str.append(':'); + } + + if (quick_info) + { + StringBuffer<64> buf2; + quick_info->print_key_len(&buf2); + key_len_str.append(buf2); + } + + if (type == JT_HASH_NEXT) + { + char buf[64]; + size_t length; + length= longlong10_to_str(hash_next_key.key_len, buf, 10) - buf; + key_len_str.append(buf, length); + } + + if (key_len_str.length() > 0) + push_string(&item_list, &key_len_str); else item_list.push_back(item_null); @@ -416,7 +481,6 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl extra_buf.append(STRING_WITH_LEN("Using filesort")); } - const CHARSET_INFO *cs= system_charset_info; item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); if (output->send_data(item_list)) @@ -476,7 +540,7 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) { // quick select str->append(STRING_WITH_LEN("Using ")); - str->append(quick_info); + quick_info->print_extra(str); break; } case ET_RANGE_CHECKED_FOR_EACH_RECORD: @@ -535,6 +599,127 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) } +/* + This is called for top-level QPF_quick_select only. The point of this + function is: + - index_merge should print $index_merge_type (child, ...) + - 'range' should not print anything. +*/ + +void QPF_quick_select::print_extra(String *str) +{ + if (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) + { + /* print nothing */ + } + else + print_extra_recursive(str); +} + + +void QPF_quick_select::print_extra_recursive(String *str) +{ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC) + { + str->append(range.key_name); + } + else + { + str->append(get_name_by_type()); + str->append('('); + List_iterator_fast it (children); + QPF_quick_select* child; + bool first= true; + while ((child = it++)) + { + if (first) + first= false; + else + str->append(','); + + child->print_extra_recursive(str); + } + str->append(')'); + } +} + + +const char * QPF_quick_select::get_name_by_type() +{ + switch (quick_type) { + case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE: + return "sort_union"; + case QUICK_SELECT_I::QS_TYPE_ROR_UNION: + return "union"; + case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT: + return "intersect"; + case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT: + return "sort_intersect"; + default: + DBUG_ASSERT(0); + return "Oops"; + } +} + + +/* + This prints a comma-separated list of used indexes, ignoring nesting +*/ + +void QPF_quick_select::print_key(String *str) +{ + if (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) + { + if (str->length() > 0) + str->append(','); + str->append(range.key_name); + } + else + { + List_iterator_fast it (children); + QPF_quick_select* child; + while ((child = it++)) + { + child->print_key(str); + } + } +} + + +/* + This prints a comma-separated list of used key_lengths, ignoring nesting +*/ + +void QPF_quick_select::print_key_len(String *str) +{ + if (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) + { + char buf[64]; + size_t length; + length= longlong10_to_str(range.key_len, buf, 10) - buf; + if (str->length() > 0) + str->append(','); + str->append(buf, length); + } + else + { + List_iterator_fast it (children); + QPF_quick_select* child; + while ((child = it++)) + { + child->print_key_len(str); + } + } +} + + int QPF_delete::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index f8f0004c669..2ab32967c8b 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -1,3 +1,20 @@ +/* + Copyright (c) 2013 Monty Program Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + + /************************************************************************************** Query Plan Footprint (QPF) structures @@ -285,6 +302,41 @@ typedef struct st_qpf_bka_type } QPF_BKA_TYPE; +/* + Data about how an index is used by some access method +*/ +class QPF_index_use : public Sql_alloc +{ +public: + const char *key_name; + uint key_len; + /* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */ +}; + + +/* + QPF for quick range selects, as well as index_merge select +*/ +class QPF_quick_select : public Sql_alloc +{ +public: + int quick_type; + + /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ + QPF_index_use range; + + /* Used in all other cases */ + List children; + + void print_extra(String *str); + void print_key(String *str); + void print_key_len(String *str); +private: + void print_extra_recursive(String *str); + const char *get_name_by_type(); +}; + + /* Query Plan Footprint for a JOIN_TAB. */ @@ -302,29 +354,33 @@ public: int sjm_nest_select_id; /* id and 'select_type' are cared-of by the parent QPF_select */ - TABLE *table; - StringBuffer<64> table_name; + StringBuffer<32> table_name; enum join_type type; - StringBuffer<64> used_partitions; + StringBuffer<32> used_partitions; bool used_partitions_set; - - key_map possible_keys; - StringBuffer<64> possible_keys_str; - /* Not used? */ - uint key_no; - uint key_length; - - bool key_set; /* not set means 'NULL' should be printed */ - StringBuffer<64> key; - - bool key_len_set; /* not set means 'NULL' should be printed */ - StringBuffer<64> key_len; + /* Empty strings means "NULL" will be printed */ + StringBuffer<32> possible_keys_str; + + /* + Index use: key name and length. + Note: that when one is accessing I_S tables, those may show use of + non-existant indexes. + key.key_name == NULL means 'NULL' will be shown in tabular output. + key.key_len == (uint)-1 means 'NULL' will be shown in tabular output. + */ + QPF_index_use key; + + /* + when type==JT_HASH_NEXT, this stores the real index. + */ + QPF_index_use hash_next_key; + bool ref_set; /* not set means 'NULL' should be printed */ - StringBuffer<64> ref; + StringBuffer<32> ref; bool rows_set; /* not set means 'NULL' should be printed */ ha_rows rows; @@ -339,7 +395,7 @@ public: Dynamic_array extra_tags; // Valid if ET_USING tag is present - StringBuffer<64> quick_info; + QPF_quick_select *quick_info; // Valid if ET_USING_INDEX_FOR_GROUP_BY is present bool loose_scan_is_scanning; @@ -348,13 +404,12 @@ public: key_map range_checked_map; // valid with ET_USING_MRR - StringBuffer<64> mrr_type; + StringBuffer<32> mrr_type; // valid with ET_USING_JOIN_BUFFER QPF_BKA_TYPE bka_type; - //TABLE *firstmatch_table; - StringBuffer<64> firstmatch_table_name; + StringBuffer<32> firstmatch_table_name; int print_explain(select_result_sink *output, uint8 explain_flags, uint select_id, const char *select_type, diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 27913f0aa8e..c216a035794 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11940,78 +11940,106 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::add_info_string(String *str) +void QUICK_RANGE_SELECT::save_info(QPF_quick_select *qpf) { - bool first= TRUE; - - add_key_name(str, &first); + qpf->quick_type= QS_TYPE_RANGE; + qpf->range.key_name= head->key_info[index].name; + qpf->range.key_len= max_used_key_length; } -void QUICK_INDEX_MERGE_SELECT::add_info_string(String *str) -{ - QUICK_RANGE_SELECT *quick; - bool first= TRUE; - List_iterator_fast it(quick_selects); - str->append(STRING_WITH_LEN("sort_union(")); +void QUICK_GROUP_MIN_MAX_SELECT::save_info(QPF_quick_select *qpf) +{ + qpf->quick_type= QS_TYPE_GROUP_MIN_MAX; + qpf->range.key_name= head->key_info[index].name; + qpf->range.key_len= max_used_key_length; +} + + +void QUICK_INDEX_SORT_SELECT::save_info(QPF_quick_select *qpf) +{ + qpf->quick_type= get_type(); + + QUICK_RANGE_SELECT *quick; + QPF_quick_select *child_qpf; + List_iterator_fast it(quick_selects); while ((quick= it++)) { - quick->add_key_name(str, &first); + child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + quick->save_info(child_qpf); } + if (pk_quick_select) - pk_quick_select->add_key_name(str, &first); - str->append(')'); + { + child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + pk_quick_select->save_info(child_qpf); + } } -void QUICK_INDEX_INTERSECT_SELECT::add_info_string(String *str) +/* + Same as QUICK_INDEX_SORT_SELECT::save_info(), but primary key is printed + first +*/ +void QUICK_INDEX_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) { - QUICK_RANGE_SELECT *quick; - bool first= TRUE; - List_iterator_fast it(quick_selects); + qpf->quick_type= get_type(); + QPF_quick_select *child_qpf; - str->append(STRING_WITH_LEN("sort_intersect(")); if (pk_quick_select) - pk_quick_select->add_key_name(str, &first); + { + child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + pk_quick_select->save_info(child_qpf); + } + + QUICK_RANGE_SELECT *quick; + List_iterator_fast it(quick_selects); while ((quick= it++)) { - quick->add_key_name(str, &first); + child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + quick->save_info(child_qpf); } - str->append(')'); + } -void QUICK_ROR_INTERSECT_SELECT::add_info_string(String *str) + +void QUICK_ROR_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) { - bool first= TRUE; + qpf->quick_type= get_type(); + QUICK_SELECT_WITH_RECORD *qr; List_iterator_fast it(quick_selects); - - str->append(STRING_WITH_LEN("intersect(")); while ((qr= it++)) { - qr->quick->add_key_name(str, &first); + QPF_quick_select *child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + qr->quick->save_info(child_qpf); } + if (cpk_quick) - cpk_quick->add_key_name(str, &first); - str->append(')'); + { + QPF_quick_select *child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + cpk_quick->save_info(child_qpf); + } } -void QUICK_ROR_UNION_SELECT::add_info_string(String *str) +void QUICK_ROR_UNION_SELECT::save_info(QPF_quick_select *qpf) { - QUICK_SELECT_I *quick; - bool first= TRUE; - List_iterator_fast it(quick_selects); + qpf->quick_type= get_type(); - str->append(STRING_WITH_LEN("union(")); + QUICK_SELECT_I *quick; + List_iterator_fast it(quick_selects); while ((quick= it++)) { - if (first) - first= FALSE; - else - str->append(','); - quick->add_info_string(str); + QPF_quick_select *child_qpf= new QPF_quick_select; + qpf->children.push_back(child_qpf); + quick->save_info(child_qpf); } - str->append(')'); } diff --git a/sql/opt_range.h b/sql/opt_range.h index d701c7f9201..eeb4e4b77ad 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -52,7 +52,7 @@ typedef struct st_key_part { Field::imagetype image_type; } KEY_PART; - +class QPF_quick_select; /* A "MIN_TUPLE < tbl.key_tuple < MAX_TUPLE" interval. @@ -345,13 +345,8 @@ public: void add_key_name(String *str, bool *first); - /* - Append text representation of quick select structure (what and how is - merged) to str. The result is added to "Extra" field in EXPLAIN output. - This function is implemented only by quick selects that merge other quick - selects output and/or can produce output suitable for merging. - */ - virtual void add_info_string(String *str) {} + /* Save information about quick select's query plan */ + virtual void save_info(QPF_quick_select *qpf)= 0; /* Return 1 if any index used by this quick select @@ -478,7 +473,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + void save_info(QPF_quick_select *qpf); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -615,6 +610,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif + void save_info(QPF_quick_select *qpf); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -663,7 +659,6 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_MERGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); }; class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT @@ -679,7 +674,7 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + void save_info(QPF_quick_select *qpf); }; @@ -717,7 +712,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + void save_info(QPF_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -796,7 +791,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void add_info_string(String *str); + void save_info(QPF_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -945,6 +940,7 @@ public: #endif bool is_agg_distinct() { return have_agg_distinct; } bool loose_scan_is_scanning() { return is_index_scan; } + void save_info(QPF_quick_select *qpf); }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 97f59d80ae2..236ddbe8ed3 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -11172,7 +11172,8 @@ void JOIN::cleanup(bool full) if (full) { /* Save it again */ -#if 0 psergey-todo: remove? +#if 0 + psergey-todo: remove? if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && @@ -22576,20 +22577,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; - char buff2[512], buff3[512], buff4[512]; - char keylen_str_buf[64]; + char buff4[512]; my_bool key_read; char table_name_buffer[SAFE_NAME_LEN]; - String tmp2(buff2,sizeof(buff2),cs); - String tmp3(buff3,sizeof(buff3),cs); String tmp4(buff4,sizeof(buff4),cs); - char hash_key_prefix[]= "#hash#"; KEY *key_info= 0; uint key_len= 0; - bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT; - - tmp2.length(0); - tmp3.length(0); tmp4.length(0); quick_type= -1; QUICK_SELECT_I *quick= NULL; @@ -22611,6 +22604,9 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, QPF_table_access *qpt= new (output->mem_root) QPF_table_access; qp_sel->add_table(qpt); + qpt->key.key_name= NULL; + qpt->key.key_len= (uint)-1; + qpt->quick_info= NULL; /* id */ if (tab->bush_root_tab) @@ -22685,13 +22681,10 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, qpt->type= tab_type; /* Build "possible_keys" value */ - qpt->possible_keys= tab->keys; append_possible_keys(&qpt->possible_keys_str, table, tab->keys); /* Build "key", "key_len", and "ref" */ - // tmp2 holds key_name - // tmp3 holds key_length // tmp4 holds ref if (tab_type == JT_NEXT) { @@ -22703,16 +22696,22 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, key_info= tab->get_keyinfo_by_key_no(tab->ref.key); key_len= tab->ref.key_length; } - - if (key_info) + + /* + In STRAIGHT_JOIN queries, there can be join tabs with JT_CONST type + that still have quick selects. + */ + if (tab->select && tab->select->quick && tab_type != JT_CONST) { - register uint length; - if (is_hj) - tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs); - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); + qpt->quick_info= new QPF_quick_select; + tab->select->quick->save_info(qpt->quick_info); + } + + if (key_info) /* 'index' or 'ref' access */ + { + qpt->key.key_name= key_info->name; + qpt->key.key_len= key_len; + if (tab->ref.key_parts && tab_type != JT_FT) { store_key **ref=tab->ref.key_copy; @@ -22731,45 +22730,15 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } } } - - if (is_hj && tab_type != JT_HASH) + + if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { - tmp2.append(':'); - tmp3.append(':'); + qpt->hash_next_key.key_name= table->key_info[tab->index].name; + qpt->hash_next_key.key_len= table->key_info[tab->index].key_length; } - if (tab_type == JT_HASH_NEXT) + if (key_info) { - register uint length; - key_info= table->key_info+tab->index; - key_len= key_info->key_length; - tmp2.append(key_info->name, strlen(key_info->name), cs); - length= (longlong10_to_str(key_len, keylen_str_buf, 10) - - keylen_str_buf); - tmp3.append(keylen_str_buf, length, cs); - } - - if (tab->type != JT_CONST && tab->select && quick) - tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); - - if (key_info || (tab->select && quick)) - { - if (tmp2.length()) - { - qpt->key.copy(tmp2); - qpt->key_set= true; - } - else - qpt->key_set= false; - - if (tmp3.length()) - { - qpt->key_len.copy(tmp3); - qpt->key_len_set= true; - } - else - qpt->key_len_set= false; - if (key_info && tab_type != JT_NEXT) { qpt->ref.copy(tmp4); @@ -22786,37 +22755,37 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { const char *tmp_buff; int f_idx; + StringBuffer<64> key_name_buf; if (table_list->has_db_lookup_value) { + /* The "key" has the name of the column referring to the database */ f_idx= table_list->schema_table->idx_field1; tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); + key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); } if (table_list->has_table_lookup_value) { if (table_list->has_db_lookup_value) - tmp2.append(','); + key_name_buf.append(','); + f_idx= table_list->schema_table->idx_field2; tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; - tmp2.append(tmp_buff, strlen(tmp_buff), cs); + key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); } - if (tmp2.length()) - { - qpt->key.copy(tmp2); - qpt->key_set= true; - } - else - qpt->key_set= false; - } - else - qpt->key_set= false; - qpt->key_len_set= false; + size_t len; + if ((len= key_name_buf.length())) + { + char *ptr= (char*)thd->alloc(len+1); + memcpy(ptr, key_name_buf.c_ptr_safe(), len+1); + qpt->key.key_name= ptr; + qpt->key.key_len= -1; + } + } qpt->ref_set= false; } /* "rows" */ - if (table_list /* SJM bushes don't have table_list */ && table_list->schema_table) { @@ -22888,7 +22857,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) { qpt->push_extra(ET_USING); - tab->select->quick->add_info_string(&qpt->quick_info); } if (tab->select) { From ae6e95c498db4800e95fdd70fadaa608f6aa9f3f Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 17 Sep 2013 16:03:40 +0400 Subject: [PATCH 27/64] Code cleanup. --- sql/opt_qpf.cc | 10 +++------- sql/opt_qpf.h | 5 +++-- sql/sql_delete.cc | 21 ++++++++------------- sql/sql_lex.h | 6 +++--- sql/sql_select.cc | 21 ++------------------- sql/sql_update.cc | 5 ++--- 6 files changed, 21 insertions(+), 47 deletions(-) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index d3fd88ce9bd..f594d2b1b22 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -81,14 +81,13 @@ void QPF_query::add_node(QPF_node *node) QPF_select *sel= (QPF_select*)node; if (sel->select_id == (int)UINT_MAX) { - //TODO this is a "fake select" from a UNION. - DBUG_ASSERT(0); + DBUG_ASSERT(0); // this is a "fake select" from a UNION. } else { uint select_id= sel->select_id; QPF_select *old_node; - //DBUG_ASSERT(!get_select(select_id)); + if (selects.elements() <= select_id) selects.resize(max(select_id+1, selects.elements()*2), NULL); @@ -115,7 +114,7 @@ int QPF_query::print_explain(select_result_sink *output, } else { - // Start printing from id=1 + /* Start printing from node with id=1 */ QPF_node *node= get_node(1); return node->print_explain(this, output, explain_flags); } @@ -264,8 +263,6 @@ QPF_select::~QPF_select() int QPF_select::print_explain(QPF_query *query, select_result_sink *output, uint8 explain_flags) { - //output->unit.offset_limit_cnt= 0; - if (message) { List item_list; @@ -328,7 +325,6 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl List item_list; Item *item_null= new Item_null(); - //const CHARSET_INFO *cs= system_charset_info; if (sjm_nest_select_id) select_id= sjm_nest_select_id; diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index 2ab32967c8b..b3996fcb58d 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -361,7 +361,7 @@ public: StringBuffer<32> used_partitions; bool used_partitions_set; - /* Empty strings means "NULL" will be printed */ + /* Empty string means "NULL" will be printed */ StringBuffer<32> possible_keys_str; /* @@ -375,7 +375,8 @@ public: QPF_index_use key; /* - when type==JT_HASH_NEXT, this stores the real index. + when type==JT_HASH_NEXT, 'key' stores the hash join pseudo-key. + hash_next_key stores the table's key. */ QPF_index_use hash_next_key; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 5655bb12886..93de4fccf5e 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -51,7 +51,7 @@ invoked on a running DELETE statement. */ -void Delete_plan::save_query_plan_footprint(QPF_query *query) +void Delete_plan::save_qpf(QPF_query *query) { QPF_delete* qpf= new QPF_delete; @@ -63,22 +63,22 @@ void Delete_plan::save_query_plan_footprint(QPF_query *query) else { qpf->deleting_all_rows= false; - Update_plan::save_query_plan_footprint_intern(query, qpf); + Update_plan::save_qpf_intern(query, qpf); } query->upd_del_plan= qpf; } -void Update_plan::save_query_plan_footprint(QPF_query *query) +void Update_plan::save_qpf(QPF_query *query) { QPF_update* qpf= new QPF_update; - save_query_plan_footprint_intern(query, qpf); + save_qpf_intern(query, qpf); query->upd_del_plan= qpf; } -void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update *qpf) +void Update_plan::save_qpf_intern(QPF_query *query, QPF_update *qpf) { qpf->select_type= "SIMPLE"; qpf->table_name.append(table->pos_in_table_list->alias); @@ -116,7 +116,6 @@ void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update qpf->using_where= test(select && select->cond); qpf->using_filesort= using_filesort; - //using_filesort is already set make_possible_keys_line(table, possible_keys, &qpf->possible_keys_line); /* Calculate key_len */ @@ -141,6 +140,7 @@ void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update } bool skip= updating_a_view; + /* Save subquery children */ for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); unit; @@ -157,11 +157,6 @@ void Update_plan::save_query_plan_footprint_intern(QPF_query *query, QPF_update */ if (!(unit->item && unit->item->eliminated)) qpf->add_child(unit->first_select()->select_number); - - //TODO: temporary?: - // A: yes. optimizing children subqueries has caused them to save QPFs, - // automatically. - //unit->save_qpf(query); } } @@ -423,7 +418,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->lex->describe) goto exit_without_my_ok; - query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); + query_plan.save_qpf(thd->lex->query_plan_footprint); thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", @@ -656,7 +651,7 @@ cleanup: /* Special exits */ exit_without_my_ok: - query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); + query_plan.save_qpf(thd->lex->query_plan_footprint); select_send *result2; if (!(result2= new select_send())) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index a84a452ef68..d06f31bd53f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2397,8 +2397,8 @@ public: /* Set this plan to be a plan to do nothing because of impossible WHRE*/ void set_impossible_where() { impossible_where= true; } - void save_query_plan_footprint(QPF_query *query); - void save_query_plan_footprint_intern(QPF_query *query, QPF_update *qpf); + void save_qpf(QPF_query *query); + void save_qpf_intern(QPF_query *query, QPF_update *qpf); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} @@ -2422,7 +2422,7 @@ public: table_rows= rows_arg; } - void save_query_plan_footprint(QPF_query *query); + void save_qpf(QPF_query *query); }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 236ddbe8ed3..2c510a39f8e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22517,8 +22517,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, bool distinct, const char *message) { QPF_node *qp_node; - const bool on_the_fly= true; - JOIN *join= this; /* Legacy: this code used to be a non-member function */ THD *thd=join->thd; const CHARSET_INFO *cs= system_charset_info; @@ -22535,7 +22533,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, { QPF_select *qp_sel; qp_node= qp_sel= new (output->mem_root) QPF_select; - join->select_lex->set_explain_type(on_the_fly); + join->select_lex->set_explain_type(true); qp_sel->select_id= join->select_lex->select_number; qp_sel->select_type= join->select_lex->type; @@ -22554,10 +22552,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, qp_node= qp_sel= new (output->mem_root) QPF_select; table_map used_tables=0; - if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); - - //bool printing_materialize_nest= FALSE; + join->select_lex->set_explain_type(true); uint select_id= join->select_lex->select_number; qp_sel->select_id= join->select_lex->select_number; @@ -22568,13 +22563,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) { - if (tab->bush_root_tab) - { - JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; - select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); - //printing_materialize_nest= TRUE; - } - TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; char buff4[512]; @@ -22615,9 +22603,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, qpt->sjm_nest_select_id= 0; /* select_type */ - //const char* stype= printing_materialize_nest? "MATERIALIZED" : - // join->select_lex->type; - //item_list.push_back(new Item_string(stype, strlen(stype), cs)); qp_sel->select_type= join->select_lex->type; /* table */ @@ -22684,8 +22669,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, append_possible_keys(&qpt->possible_keys_str, table, tab->keys); /* Build "key", "key_len", and "ref" */ - - // tmp4 holds ref if (tab_type == JT_NEXT) { key_info= table->key_info+tab->index; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 74f1a279411..15b3b9ad7e7 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -500,7 +500,7 @@ int mysql_update(THD *thd, if (thd->lex->describe) goto exit_without_my_ok; - query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); + query_plan.save_qpf(thd->lex->query_plan_footprint); thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", @@ -1031,10 +1031,9 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); - query_plan.save_query_plan_footprint(thd->lex->query_plan_footprint); + query_plan.save_qpf(thd->lex->query_plan_footprint); select_send *result; - //bool printed_anything; if (!(result= new select_send())) return 1; /* purecov: inspected */ List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ From 2add402891e1b252991ae37f2065790aa6c2f728 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 19 Sep 2013 08:33:58 +0400 Subject: [PATCH 28/64] MDEV-407: Print EXPLAIN [ANALYZE] in the slow query log - Initial implementation. --- sql/log.cc | 9 ++++++ sql/log_slow.h | 1 + sql/opt_qpf.cc | 17 ++++++++++ sql/opt_qpf.h | 3 ++ sql/sql_class.h | 20 ++++++++++++ sql/sql_lex.h | 1 + sql/sql_parse.cc | 3 +- sql/sql_show.cc | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ sql/sys_vars.cc | 5 +-- 9 files changed, 136 insertions(+), 3 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 041a0b555d1..5d8b650b494 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2825,6 +2825,15 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, "Yes" : "No"), thd->query_plan_fsort_passes) == (size_t) -1) tmp_errno= errno; + if (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_EXPLAIN && + thd->lex->query_plan_footprint) + { + StringBuffer<128> buf; + DBUG_ASSERT(!thd->free_list); + print_qpf_query(thd->lex, thd, &buf); + my_b_printf(&log_file, "%s", buf.c_ptr_safe()); + thd->free_items(); + } if (thd->db && strcmp(thd->db, db)) { // Database changed if (my_b_printf(&log_file,"use %s;\n",thd->db) == (size_t) -1) diff --git a/sql/log_slow.h b/sql/log_slow.h index 92a2d1bf4f6..e8faf79a047 100644 --- a/sql/log_slow.h +++ b/sql/log_slow.h @@ -18,6 +18,7 @@ #define LOG_SLOW_VERBOSITY_INIT 0 #define LOG_SLOW_VERBOSITY_INNODB 1 << 0 #define LOG_SLOW_VERBOSITY_QUERY_PLAN 1 << 1 +#define LOG_SLOW_VERBOSITY_EXPLAIN 1 << 2 #define QPLAN_INIT QPLAN_QC_NO diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index f594d2b1b22..fc2597bcd73 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -120,6 +120,23 @@ int QPF_query::print_explain(select_result_sink *output, } } +void print_qpf_query(LEX *lex, THD *thd, String *str) +{ + lex->query_plan_footprint->print_explain_str(thd, str); +} + +bool QPF_query::print_explain_str(THD *thd, String *out_str) +{ + List fields; + thd->make_explain_field_list(fields); + + select_result_text_buffer output_buf(thd); + output_buf.send_result_set_metadata(fields, thd->lex->describe); + print_explain(&output_buf, 0); + output_buf.save_to(out_str); + return false; +} + static void push_str(List *item_list, const char *str) { diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index b3996fcb58d..e59af85a988 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -230,6 +230,9 @@ public: /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); + /* Return tabular EXPLAIN output as a text string */ + bool print_explain_str(THD *thd, String *out_str); + /* If true, at least part of EXPLAIN can be printed */ bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; } MEM_ROOT *mem_root; diff --git a/sql/sql_class.h b/sql/sql_class.h index 81b5f2847d3..ba14af850be 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3442,6 +3442,26 @@ public: }; +/* + This is a select_result_sink which stores the data in text form. +*/ + +class select_result_text_buffer : public select_result_sink +{ +public: + select_result_text_buffer(THD *thd_arg) : thd(thd_arg) {} + int send_data(List &items); + bool send_result_set_metadata(List &fields, uint flag); + + void save_to(String *res); +private: + int append_row(List &items, bool send_names); + + THD *thd; + List rows; + int n_columns; +}; + /* Base class for select_result descendands which intercept and diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d06f31bd53f..77f0054d122 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -622,6 +622,7 @@ class QPF_query; void delete_qpf_query(LEX *lex); void create_qpf_query(LEX *lex, MEM_ROOT *mem_root); +void print_qpf_query(LEX *lex, THD *thd, String *str); class st_select_lex_unit: public st_select_lex_node { protected: diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 011fc3e9347..b8ab4cef1be 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1529,7 +1529,6 @@ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); - delete_qpf_query(thd->lex); /* The following should never be true with our current code base, @@ -1567,6 +1566,8 @@ void log_slow_statement(THD *thd) thd->utime_after_query); thd_proc_info(thd, 0); } + + delete_qpf_query(thd->lex); DBUG_VOID_RETURN; } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d5a65a9640e..c463be985c4 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2377,6 +2377,86 @@ int select_result_explain_buffer::send_data(List &items) DBUG_RETURN(test(res)); } +bool select_result_text_buffer::send_result_set_metadata(List &fields, uint flag) +{ + n_columns= fields.elements; + return append_row(fields, true /*send item names */); + return send_data(fields); +} + + +int select_result_text_buffer::send_data(List &items) +{ + return append_row(items, false /*send item values */); +} + +int select_result_text_buffer::append_row(List &items, bool send_names) +{ + List_iterator it(items); + Item *item; + char **row; + int column= 0; + + if (!(row= (char**) thd->alloc(sizeof(char*) * n_columns))) + return true; + rows.push_back(row); + + while ((item= it++)) + { + DBUG_ASSERT(column < n_columns); + StringBuffer<32> buf; + const char *data_ptr; + size_t data_len; + if (send_names) + { + data_ptr= item->name; + data_len= strlen(item->name); + } + else + { + String *res; + res= item->val_str(&buf); + if (item->null_value) + { + data_ptr= "NULL"; + data_len=4; + } + else + { + data_ptr= res->c_ptr_safe(); + data_len= res->length(); + } + } + + char *ptr= (char*)thd->alloc(data_len + 1); + memcpy(ptr, data_ptr, data_len + 1); + row[column]= ptr; + + column++; + } + return false; +} + + +void select_result_text_buffer::save_to(String *res) +{ + List_iterator it(rows); + char **row; + res->append("#\n"); + while ((row= it++)) + { + res->append("# "); + for (int i=0; i < n_columns; i++) + { + if (i) + res->append('\t'); + res->append(row[i]); + } + res->append("\n"); + } + res->append("#\n"); +} + /* Store the SHOW EXPLAIN output in the temporary table. diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 3827e3f4b67..80b7f25324d 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -4115,11 +4115,12 @@ static Sys_var_ulong Sys_log_slow_rate_limit( SESSION_VAR(log_slow_rate_limit), CMD_LINE(REQUIRED_ARG), VALID_RANGE(1, UINT_MAX), DEFAULT(1), BLOCK_SIZE(1)); -static const char *log_slow_verbosity_names[]= { "innodb", "query_plan", 0 }; +static const char *log_slow_verbosity_names[]= { "innodb", "query_plan", + "explain", 0 }; static Sys_var_set Sys_log_slow_verbosity( "log_slow_verbosity", "log-slow-verbosity=[value[,value ...]] where value is one of " - "'innodb', 'query_plan'", + "'innodb', 'query_plan', 'explain' ", SESSION_VAR(log_slow_verbosity), CMD_LINE(REQUIRED_ARG), log_slow_verbosity_names, DEFAULT(LOG_SLOW_VERBOSITY_INIT)); From d998a1635fdcd2a375dc433e8d3aa1678e2ba908 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 20 Sep 2013 17:45:24 +0400 Subject: [PATCH 29/64] MDEV-5045: Server crashes in QPF_query::print_explain with log_slow_verbosity='explain' - Don't print a plan when the statement didn't produce it - Also, add first testcase. We can't check the EXPLAIN from the slow log itself, though. --- mysql-test/r/explain_slowquerylog.result | 11 +++++++++++ mysql-test/t/explain_slowquerylog-master.opt | 1 + mysql-test/t/explain_slowquerylog.test | 20 ++++++++++++++++++++ sql/log.cc | 4 ++-- sql/opt_qpf.cc | 9 ++++++--- sql/sql_lex.h | 2 +- 6 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 mysql-test/r/explain_slowquerylog.result create mode 100644 mysql-test/t/explain_slowquerylog-master.opt create mode 100644 mysql-test/t/explain_slowquerylog.test diff --git a/mysql-test/r/explain_slowquerylog.result b/mysql-test/r/explain_slowquerylog.result new file mode 100644 index 00000000000..86e1ca391d6 --- /dev/null +++ b/mysql-test/r/explain_slowquerylog.result @@ -0,0 +1,11 @@ +drop table if exists t0,t1; +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +explain select * from t0 where a < 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using where +# +# MDEV-5045: Server crashes in QPF_query::print_explain with log_slow_verbosity='query_plan,explain' +# +set autocommit=1; +drop table t0; diff --git a/mysql-test/t/explain_slowquerylog-master.opt b/mysql-test/t/explain_slowquerylog-master.opt new file mode 100644 index 00000000000..0a3ad969e79 --- /dev/null +++ b/mysql-test/t/explain_slowquerylog-master.opt @@ -0,0 +1 @@ +--slow-query-log --long-query-time=0.00000 --log-slow-verbosity=query_plan,explain diff --git a/mysql-test/t/explain_slowquerylog.test b/mysql-test/t/explain_slowquerylog.test new file mode 100644 index 00000000000..64005d98d05 --- /dev/null +++ b/mysql-test/t/explain_slowquerylog.test @@ -0,0 +1,20 @@ +# +# This is a test for EXPLAINs being written into slow query log. +# For now, we just run the queries and hope not to crash. +# +# +--disable_warnings +drop table if exists t0,t1; +--enable_warnings + +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +explain select * from t0 where a < 3; + +--echo # +--echo # MDEV-5045: Server crashes in QPF_query::print_explain with log_slow_verbosity='query_plan,explain' +--echo # +set autocommit=1; + +drop table t0; diff --git a/sql/log.cc b/sql/log.cc index 5d8b650b494..3f782a478bb 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2830,8 +2830,8 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, { StringBuffer<128> buf; DBUG_ASSERT(!thd->free_list); - print_qpf_query(thd->lex, thd, &buf); - my_b_printf(&log_file, "%s", buf.c_ptr_safe()); + if (!print_qpf_query(thd->lex, thd, &buf)) + my_b_printf(&log_file, "%s", buf.c_ptr_safe()); thd->free_items(); } if (thd->db && strcmp(thd->db, db)) diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index fc2597bcd73..0f047f05ba0 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -116,13 +116,15 @@ int QPF_query::print_explain(select_result_sink *output, { /* Start printing from node with id=1 */ QPF_node *node= get_node(1); + if (!node) + return 1; /* No query plan */ return node->print_explain(this, output, explain_flags); } } -void print_qpf_query(LEX *lex, THD *thd, String *str) +bool print_qpf_query(LEX *lex, THD *thd, String *str) { - lex->query_plan_footprint->print_explain_str(thd, str); + return lex->query_plan_footprint->print_explain_str(thd, str); } bool QPF_query::print_explain_str(THD *thd, String *out_str) @@ -132,7 +134,8 @@ bool QPF_query::print_explain_str(THD *thd, String *out_str) select_result_text_buffer output_buf(thd); output_buf.send_result_set_metadata(fields, thd->lex->describe); - print_explain(&output_buf, 0); + if (print_explain(&output_buf, 0)) + return true; output_buf.save_to(out_str); return false; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 77f0054d122..13140063bff 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -622,7 +622,7 @@ class QPF_query; void delete_qpf_query(LEX *lex); void create_qpf_query(LEX *lex, MEM_ROOT *mem_root); -void print_qpf_query(LEX *lex, THD *thd, String *str); +bool print_qpf_query(LEX *lex, THD *thd, String *str); class st_select_lex_unit: public st_select_lex_node { protected: From ccca339459ddf1f881b4cbbec388167cbddf3948 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 23 Sep 2013 14:17:56 +0400 Subject: [PATCH 30/64] MDEV-5047 virtual THD::~THD(): Assertion `status_var.memory_used == 0' fails - Don't forget to delete the query plan footprint when the query wasn't printed into slow query log for some reason - ALso removed some garbage code. --- mysql-test/r/explain_slowquerylog.result | 11 +++++++++++ mysql-test/t/explain_slowquerylog.test | 12 +++++++++++- sql/sql_parse.cc | 15 ++++++++------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/explain_slowquerylog.result b/mysql-test/r/explain_slowquerylog.result index 86e1ca391d6..8ba631545c3 100644 --- a/mysql-test/r/explain_slowquerylog.result +++ b/mysql-test/r/explain_slowquerylog.result @@ -9,3 +9,14 @@ id select_type table type possible_keys key key_len ref rows Extra # set autocommit=1; drop table t0; +# +# MDEV-5047 virtual THD::~THD(): Assertion `status_var.memory_used == 0' fails on disconnect +# +ALTER TABLE nonexisting ENABLE KEYS; +ERROR 42S02: Table 'test.nonexisting' doesn't exist +SHOW WARNINGS; +Level Code Message +Error 1146 Table 'test.nonexisting' doesn't exist +SELECT 1; +1 +1 diff --git a/mysql-test/t/explain_slowquerylog.test b/mysql-test/t/explain_slowquerylog.test index 64005d98d05..09565d90931 100644 --- a/mysql-test/t/explain_slowquerylog.test +++ b/mysql-test/t/explain_slowquerylog.test @@ -16,5 +16,15 @@ explain select * from t0 where a < 3; --echo # MDEV-5045: Server crashes in QPF_query::print_explain with log_slow_verbosity='query_plan,explain' --echo # set autocommit=1; - drop table t0; + +--echo # +--echo # MDEV-5047 virtual THD::~THD(): Assertion `status_var.memory_used == 0' fails on disconnect +--echo # +--connect (con1,localhost,root,,) +--error ER_NO_SUCH_TABLE +ALTER TABLE nonexisting ENABLE KEYS; +SHOW WARNINGS; +--disconnect con1 +--connection default +SELECT 1; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b8ab4cef1be..bc3d53dc32e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1525,6 +1525,10 @@ bool dispatch_command(enum enum_server_command command, THD *thd, } +/* + @note + This function must call delete_qpf_query(). +*/ void log_slow_statement(THD *thd) { DBUG_ENTER("log_slow_statement"); @@ -1543,7 +1547,10 @@ void log_slow_statement(THD *thd) if (!thd->enable_slow_log || (thd->variables.log_slow_filter && !(thd->variables.log_slow_filter & thd->query_plan_flags))) + { + delete_qpf_query(thd->lex); DBUG_VOID_RETURN; + } if (((thd->server_status & SERVER_QUERY_WAS_SLOW) || ((thd->server_status & @@ -4799,13 +4806,7 @@ finish: #endif } lex->unit.cleanup(); - //psergey-todo: print EXPLAIN here? After the above JOIN::cleanup calls? - // how do we print EXPLAIN extended, then? - if (lex->describe) - { - DBUG_ASSERT(lex->query_plan_footprint); - ///.. - } + /* Free tables */ thd_proc_info(thd, "closing tables"); close_thread_tables(thd); From c0028bd2acca38c76a5294e7b3edfa9b1aa674b0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Sep 2013 15:51:16 +0400 Subject: [PATCH 31/64] - Fix incorrectly-removed piece of code JOIN::save_qpf - update mysqld--help.result --- mysql-test/r/mysqld--help.result | 2 +- sql/sql_select.cc | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/mysqld--help.result b/mysql-test/r/mysqld--help.result index d9cdf3c3240..4a22b174fb5 100644 --- a/mysql-test/r/mysqld--help.result +++ b/mysql-test/r/mysqld--help.result @@ -344,7 +344,7 @@ The following options may be given as the first argument: log if it is open. --log-slow-verbosity=name log-slow-verbosity=[value[,value ...]] where value is one - of 'innodb', 'query_plan' + of 'innodb', 'query_plan', 'explain' --log-tc=name Path to transaction coordinator log (used for transactions that affect more than one storage engine, when binary log is disabled). diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2c510a39f8e..15c9a2e3a58 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22553,8 +22553,6 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, table_map used_tables=0; join->select_lex->set_explain_type(true); - uint select_id= join->select_lex->select_number; - qp_sel->select_id= join->select_lex->select_number; qp_sel->select_type= join->select_lex->type; @@ -22563,6 +22561,15 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) { + uint select_id; + if (tab->bush_root_tab) + { + JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; + select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); + } + else + select_id= join->select_lex->select_number; + TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; char buff4[512]; From 3aaeb7305be997987f3fed29db1ddaf9a485bf25 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Sep 2013 16:07:37 +0400 Subject: [PATCH 32/64] Code cleanup --- sql/sql_parse.cc | 54 +++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index bc3d53dc32e..07ef62c5597 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -1493,7 +1493,6 @@ bool dispatch_command(enum enum_server_command command, THD *thd, thd->update_all_stats(); log_slow_statement(thd); - /* psergey-todo: this is the place we could print EXPLAIN to slow query log */ thd_proc_info(thd, "cleaning up"); thd->reset_query(); @@ -3351,9 +3350,8 @@ end_with_restore_list: else { result->reset_offset_limit(); - thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); - //delete thd->lex->query_plan_footprint; - //thd->lex->query_plan_footprint= NULL; + thd->lex->query_plan_footprint->print_explain(result, + thd->lex->describe); } if (res) @@ -4881,9 +4879,14 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); - //thd->lex->query_plan_footprint= new QPF_query; + + /* + This will call optimize() for all parts of query. The query plan is + printed out below. + */ res= mysql_explain_union(thd, &thd->lex->unit, result); - + + /* Print EXPLAIN only if we don't have an error */ if (!res) { /* @@ -4891,32 +4894,23 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) top-level LIMIT */ result->reset_offset_limit(); - thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + thd->lex->query_plan_footprint->print_explain(result, + thd->lex->describe); + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + /* + The warnings system requires input in utf8, @see + mysqld_show_warnings(). + */ + thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } } - //delete thd->lex->query_plan_footprint; - //thd->lex->query_plan_footprint= NULL; - //psergey-todo: here, produce the EXPLAIN output. - // mysql_explain_union() itself is only responsible for calling - // optimize() for all parts of the query. - - /* - The code which prints the extended description is not robust - against malformed queries, so skip it if we have an error. - */ - if (!res && (lex->describe & DESCRIBE_EXTENDED)) - { - char buff[1024]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); - /* - The warnings system requires input in utf8, @see - mysqld_show_warnings(). - */ - thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_YES, str.c_ptr_safe()); - } if (res) result->abort_result_set(); else From 28734220e7bbd85fa74a821300afdd4cad75ce13 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Sep 2013 16:27:47 +0400 Subject: [PATCH 33/64] More code cleanup --- sql/sql_parse.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 07ef62c5597..28d96feadbb 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3322,8 +3322,7 @@ end_with_restore_list: delete result; result= NULL; } - select_lex->set_explain_type(FALSE); - //thd->lex->query_plan_footprint= new QPF_query; + //select_lex->set_explain_type(FALSE); } else result= new multi_delete(aux_tables, lex->table_count); From ac54df04d80781ea243cfbc8865bfe080b44f4e2 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Sep 2013 17:23:22 +0400 Subject: [PATCH 34/64] MDEV-5070 - EXPLAIN INSERT ... SELECT crashes on 10.0-base-explain-slowquerylog - Add EXPLAIN output print out for INSERT/REPLACE ... SELECT --- mysql-test/r/explain_non_select.result | 13 +++++++++++++ mysql-test/t/explain_non_select.test | 12 ++++++++++++ sql/sql_class.cc | 1 + sql/sql_parse.cc | 23 ++++++++++++++++++++--- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 021c5d92daa..267b5f27c35 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -114,3 +114,16 @@ explain delete from t0 returning a; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 4 drop table t0; +# +# MDEV-5070 - EXPLAIN INSERT ... SELECT crashes on 10.0-base-explain-slowquerylog +# +create table t0 (a int); +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +create table t1 (a int); +explain insert into t1 select * from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 +explain replace into t1 select * from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 +drop table t0, t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 7385ef5d420..316720b17f3 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -94,4 +94,16 @@ explain delete from t0 where a=1 returning a; explain delete from t0 returning a; drop table t0; +--echo # +--echo # MDEV-5070 - EXPLAIN INSERT ... SELECT crashes on 10.0-base-explain-slowquerylog +--echo # +create table t0 (a int); +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +create table t1 (a int); + +explain insert into t1 select * from t0; +explain replace into t1 select * from t0; + +drop table t0, t1; + diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 08e8fbe7361..b080951dd01 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2089,6 +2089,7 @@ int THD::send_explain_fields(select_result *result) { List field_list; make_explain_field_list(field_list); + result->prepare(field_list, NULL); return (result->send_result_set_metadata(field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 28d96feadbb..133485c3a68 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3173,6 +3173,7 @@ end_with_restore_list: case SQLCOM_INSERT_SELECT: { select_result *sel_result; + bool explain= test(lex->describe); DBUG_ASSERT(first_table == all_tables && first_table != 0); if ((res= insert_precheck(thd, all_tables))) break; @@ -3205,7 +3206,10 @@ end_with_restore_list: if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0))) { - MYSQL_INSERT_SELECT_START(thd->query()); + if (!explain) + { + MYSQL_INSERT_SELECT_START(thd->query()); + } /* Only the INSERT table should be merged. Other will be handled by select. @@ -3242,8 +3246,22 @@ end_with_restore_list: } delete sel_result; } + + if (!res && explain) + { + select_result *result= new select_send(); + LEX *lex= thd->lex; + if (thd->send_explain_fields(result) || + lex->query_plan_footprint->print_explain(result, lex->describe) || + result->send_eof()) + res= 1; + } + /* revert changes for SP */ - MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); + if (!explain) + { + MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); + } select_lex->table_list.first= first_table; } /* @@ -3322,7 +3340,6 @@ end_with_restore_list: delete result; result= NULL; } - //select_lex->set_explain_type(FALSE); } else result= new multi_delete(aux_tables, lex->table_count); From 7d60030c0200b982c13a4c6a9023cf7190b2510a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Sep 2013 19:18:02 +0400 Subject: [PATCH 35/64] MDEV-5060 Server crashes on EXPLAIN EXTENDED or EXPLAIN PARTITIONS with explain in slow_log - If we're running explain with flags, use the same set of flags to make EXPLAIN columns and contents. --- mysql-test/r/explain_slowquerylog.result | 6 ++++++ mysql-test/t/explain_slowquerylog.test | 7 +++++++ sql/opt_qpf.cc | 4 +++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/explain_slowquerylog.result b/mysql-test/r/explain_slowquerylog.result index 8ba631545c3..62c742c5932 100644 --- a/mysql-test/r/explain_slowquerylog.result +++ b/mysql-test/r/explain_slowquerylog.result @@ -20,3 +20,9 @@ Error 1146 Table 'test.nonexisting' doesn't exist SELECT 1; 1 1 +# +# MDEV-5060 Server crashes on EXPLAIN EXTENDED or EXPLAIN PARTITIONS with explain in slow_log +# +EXPLAIN PARTITIONS SELECT 1 ; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used diff --git a/mysql-test/t/explain_slowquerylog.test b/mysql-test/t/explain_slowquerylog.test index 09565d90931..23e31166e32 100644 --- a/mysql-test/t/explain_slowquerylog.test +++ b/mysql-test/t/explain_slowquerylog.test @@ -28,3 +28,10 @@ SHOW WARNINGS; --disconnect con1 --connection default SELECT 1; + +--echo # +--echo # MDEV-5060 Server crashes on EXPLAIN EXTENDED or EXPLAIN PARTITIONS with explain in slow_log +--echo # +EXPLAIN PARTITIONS SELECT 1 ; + + diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 0f047f05ba0..152c55c98cb 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -122,11 +122,13 @@ int QPF_query::print_explain(select_result_sink *output, } } + bool print_qpf_query(LEX *lex, THD *thd, String *str) { return lex->query_plan_footprint->print_explain_str(thd, str); } + bool QPF_query::print_explain_str(THD *thd, String *out_str) { List fields; @@ -134,7 +136,7 @@ bool QPF_query::print_explain_str(THD *thd, String *out_str) select_result_text_buffer output_buf(thd); output_buf.send_result_set_metadata(fields, thd->lex->describe); - if (print_explain(&output_buf, 0)) + if (print_explain(&output_buf, thd->lex->describe)) return true; output_buf.save_to(out_str); return false; From 0b69c44e945658e82b4c5d532694795136018f12 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 26 Sep 2013 14:42:30 +0400 Subject: [PATCH 36/64] MDEV-5067: Valgrind warnings (Invalid read) in QPF_table_access::print_explain - Query plan footprint (in new terms, "EXPLAIN structure") should always keep a copy of key_name. This is because the table might be a temporary table which may be already freed by the time we use query plan footprint. --- mysql-test/t/explain_non_select.test | 8 +++++++ sql/opt_qpf.cc | 18 ++++++++-------- sql/opt_qpf.h | 20 +++++++++++++++-- sql/opt_range.cc | 32 +++++++++++++--------------- sql/opt_range.h | 14 ++++++------ sql/sql_select.cc | 23 +++++++------------- 6 files changed, 65 insertions(+), 50 deletions(-) diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 316720b17f3..2ca47ff8025 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -106,4 +106,12 @@ explain replace into t1 select * from t0; drop table t0, t1; +--echo # +--echo # MDEV-5067: Valgrind warnings (Invalid read) in QPF_table_access::print_explain +--echo # +CREATE TABLE t1 (i INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0),(9); +SELECT * FROM t1 INNER JOIN ( SELECT DISTINCT * FROM t1 ) AS sq ON (sq.i = t1.i); + +DROP TABLE t1; diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 152c55c98cb..30aca13e446 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -385,12 +385,12 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl /* `key` */ StringBuffer<64> key_str; - if (key.key_name) + if (key.get_key_name()) { if (is_hj) key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs); - key_str.append(key.key_name); + key_str.append(key.get_key_name()); if (is_hj && type != JT_HASH) key_str.append(':'); @@ -403,7 +403,7 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl key_str.append(buf2); } if (type == JT_HASH_NEXT) - key_str.append(hash_next_key.key_name); + key_str.append(hash_next_key.get_key_name()); if (key_str.length() > 0) push_string(&item_list, &key_str); @@ -413,11 +413,11 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl /* `key_len` */ StringBuffer<64> key_len_str; - if (key.key_len != (uint)-1) + if (key.get_key_len() != (uint)-1) { char buf[64]; size_t length; - length= longlong10_to_str(key.key_len, buf, 10) - buf; + length= longlong10_to_str(key.get_key_len(), buf, 10) - buf; key_len_str.append(buf, length); if (is_hj && type != JT_HASH) key_len_str.append(':'); @@ -434,7 +434,7 @@ int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_fl { char buf[64]; size_t length; - length= longlong10_to_str(hash_next_key.key_len, buf, 10) - buf; + length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf; key_len_str.append(buf, length); } @@ -642,7 +642,7 @@ void QPF_quick_select::print_extra_recursive(String *str) if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC) { - str->append(range.key_name); + str->append(range.get_key_name()); } else { @@ -695,7 +695,7 @@ void QPF_quick_select::print_key(String *str) { if (str->length() > 0) str->append(','); - str->append(range.key_name); + str->append(range.get_key_name()); } else { @@ -721,7 +721,7 @@ void QPF_quick_select::print_key_len(String *str) { char buf[64]; size_t length; - length= longlong10_to_str(range.key_len, buf, 10) - buf; + length= longlong10_to_str(range.get_key_len(), buf, 10) - buf; if (str->length() > 0) str->append(','); str->append(buf, length); diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index e59af85a988..d5544e22650 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -310,10 +310,26 @@ typedef struct st_qpf_bka_type */ class QPF_index_use : public Sql_alloc { -public: - const char *key_name; + 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) + { + 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; + } + + inline const char *get_key_name() { return key_name; } + inline uint get_key_len() { return key_len; } }; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index c216a035794..97cbe73dcb4 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11940,23 +11940,21 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= QS_TYPE_RANGE; - qpf->range.key_name= head->key_info[index].name; - qpf->range.key_len= max_used_key_length; + qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); } -void QUICK_GROUP_MIN_MAX_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_GROUP_MIN_MAX_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= QS_TYPE_GROUP_MIN_MAX; - qpf->range.key_name= head->key_info[index].name; - qpf->range.key_len= max_used_key_length; + qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); } -void QUICK_INDEX_SORT_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= get_type(); @@ -11967,14 +11965,14 @@ void QUICK_INDEX_SORT_SELECT::save_info(QPF_quick_select *qpf) { child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - quick->save_info(child_qpf); + quick->save_info(alloc, child_qpf); } if (pk_quick_select) { child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - pk_quick_select->save_info(child_qpf); + pk_quick_select->save_info(alloc, child_qpf); } } @@ -11982,7 +11980,7 @@ void QUICK_INDEX_SORT_SELECT::save_info(QPF_quick_select *qpf) Same as QUICK_INDEX_SORT_SELECT::save_info(), but primary key is printed first */ -void QUICK_INDEX_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= get_type(); QPF_quick_select *child_qpf; @@ -11991,7 +11989,7 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) { child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - pk_quick_select->save_info(child_qpf); + pk_quick_select->save_info(alloc, child_qpf); } QUICK_RANGE_SELECT *quick; @@ -12000,13 +11998,13 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) { child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - quick->save_info(child_qpf); + quick->save_info(alloc, child_qpf); } } -void QUICK_ROR_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= get_type(); @@ -12016,19 +12014,19 @@ void QUICK_ROR_INTERSECT_SELECT::save_info(QPF_quick_select *qpf) { QPF_quick_select *child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - qr->quick->save_info(child_qpf); + qr->quick->save_info(alloc, child_qpf); } if (cpk_quick) { QPF_quick_select *child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - cpk_quick->save_info(child_qpf); + cpk_quick->save_info(alloc, child_qpf); } } -void QUICK_ROR_UNION_SELECT::save_info(QPF_quick_select *qpf) +void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) { qpf->quick_type= get_type(); @@ -12038,7 +12036,7 @@ void QUICK_ROR_UNION_SELECT::save_info(QPF_quick_select *qpf) { QPF_quick_select *child_qpf= new QPF_quick_select; qpf->children.push_back(child_qpf); - quick->save_info(child_qpf); + quick->save_info(alloc, child_qpf); } } diff --git a/sql/opt_range.h b/sql/opt_range.h index eeb4e4b77ad..f2e8b92e7d0 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -346,7 +346,7 @@ public: void add_key_name(String *str, bool *first); /* Save information about quick select's query plan */ - virtual void save_info(QPF_quick_select *qpf)= 0; + virtual void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf)= 0; /* Return 1 if any index used by this quick select @@ -473,7 +473,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -610,7 +610,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -674,7 +674,7 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); }; @@ -712,7 +712,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -791,7 +791,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -940,7 +940,7 @@ public: #endif bool is_agg_distinct() { return have_agg_distinct; } bool loose_scan_is_scanning() { return is_index_scan; } - void save_info(QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); }; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 15c9a2e3a58..f9f96321bf1 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22599,8 +22599,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, QPF_table_access *qpt= new (output->mem_root) QPF_table_access; qp_sel->add_table(qpt); - qpt->key.key_name= NULL; - qpt->key.key_len= (uint)-1; + qpt->key.set(thd->mem_root, NULL, (uint)-1); qpt->quick_info= NULL; /* id */ @@ -22694,13 +22693,12 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (tab->select && tab->select->quick && tab_type != JT_CONST) { qpt->quick_info= new QPF_quick_select; - tab->select->quick->save_info(qpt->quick_info); + tab->select->quick->save_info(thd->mem_root, qpt->quick_info); } if (key_info) /* 'index' or 'ref' access */ { - qpt->key.key_name= key_info->name; - qpt->key.key_len= key_len; + qpt->key.set(thd->mem_root, key_info->name, key_len); if (tab->ref.key_parts && tab_type != JT_FT) { @@ -22723,8 +22721,9 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { - qpt->hash_next_key.key_name= table->key_info[tab->index].name; - qpt->hash_next_key.key_len= table->key_info[tab->index].key_length; + qpt->hash_next_key.set(thd->mem_root, + table->key_info[tab->index].name, + table->key_info[tab->index].key_length); } if (key_info) @@ -22763,14 +22762,8 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, key_name_buf.append(tmp_buff, strlen(tmp_buff), cs); } - size_t len; - if ((len= key_name_buf.length())) - { - char *ptr= (char*)thd->alloc(len+1); - memcpy(ptr, key_name_buf.c_ptr_safe(), len+1); - qpt->key.key_name= ptr; - qpt->key.key_len= -1; - } + if (key_name_buf.length()) + qpt->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); } qpt->ref_set= false; } From e8eeb2e7f90dfa7d54a0146322cfba32731e4b34 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 26 Sep 2013 14:47:32 +0400 Subject: [PATCH 37/64] Update test results for the previous cset --- mysql-test/r/explain_non_select.result | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 267b5f27c35..7705ed29386 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -127,3 +127,14 @@ explain replace into t1 select * from t0; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 drop table t0, t1; +# +# MDEV-5067: Valgrind warnings (Invalid read) in QPF_table_access::print_explain +# +CREATE TABLE t1 (i INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0),(9); +SELECT * FROM t1 INNER JOIN ( SELECT DISTINCT * FROM t1 ) AS sq ON (sq.i = t1.i); +i i +7 7 +0 0 +9 9 +DROP TABLE t1; From 6519ca51dd8815dc1f2d88708e540bd4032cb45e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 1 Oct 2013 17:49:03 +0400 Subject: [PATCH 38/64] EXPLAIN UPDATE/DELETE - Make EXPLAIN UPDATE/DELETE work inside SPs - Return correct error code from mysql_delete() - EXPLAIN will create a multi_delete object (as it affects the optimization). select_result will be only used for producing EXPLAIN output. --- mysql-test/r/explain_non_select.result | 2 +- sql/sp_head.cc | 21 +++++++++++---- sql/sql_delete.cc | 5 ++-- sql/sql_parse.cc | 37 ++++++++++---------------- sql/sql_select.cc | 9 ++++--- sql/sql_update.cc | 8 +++--- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 7705ed29386..5597e6a876e 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -49,7 +49,7 @@ id select_type table type possible_keys key key_len ref rows Extra explain delete t1 from t0, t1 where t0.a = t1.a; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where -1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +1 SIMPLE t1 ref a a 5 test.t0.a 4 drop table t0, t1; # ################################################################### # ## EXPLAIN UPDATE tests diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 0e69956e951..a3027763b8a 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -313,14 +313,26 @@ sp_get_flags_for_command(LEX *lex) flags= sp_head::HAS_COMMIT_OR_ROLLBACK; break; case SQLCOM_DELETE: + case SQLCOM_DELETE_MULTI: { - if (lex->select_lex.item_list.is_empty()) + /* + DELETE normally doesn't return resultset, but there are two exceptions: + - DELETE ... RETURNING + - EXPLAIN DELETE ... + */ + if (lex->select_lex.item_list.is_empty() && !lex->describe) + flags= 0; + else + flags= sp_head::MULTI_RESULTS; + break; + } + case SQLCOM_UPDATE: + case SQLCOM_UPDATE_MULTI: + { + if (!lex->describe) flags= 0; else - { - /* This is DELETE ... RETURNING ... */ flags= sp_head::MULTI_RESULTS; - } break; } default: @@ -2988,7 +3000,6 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, } reinit_stmt_before_use(thd, m_lex); - // not here, but inside every instr: create_qpf_query(m_lex); if (open_tables) res= instr->exec_open_and_lock_tables(thd, m_lex->query_tables); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 93de4fccf5e..25fe56d2e71 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -183,7 +183,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, bool const_cond_result; ha_rows deleted= 0; bool reverse= FALSE; - bool err= true; ORDER *order= (ORDER *) ((order_list && order_list->elements) ? order_list->first : NULL); SELECT_LEX *select_lex= &thd->lex->select_lex; @@ -659,7 +658,7 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result2->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result2); - int err2= thd->lex->query_plan_footprint->print_explain(result2, 0 /* explain flags*/); + int err2= thd->lex->query_plan_footprint->print_explain(result2, thd->lex->describe); if (err2) result2->abort_result_set(); @@ -669,7 +668,7 @@ exit_without_my_ok: delete select; free_underlaid_joins(thd, select_lex); //table->set_keyread(false); - DBUG_RETURN((err || thd->is_error() || thd->killed) ? 1 : 0); + DBUG_RETURN((err2 || thd->is_error() || thd->killed) ? 1 : 0); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 133485c3a68..71d68c6730d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3332,18 +3332,7 @@ end_with_restore_list: if (!thd->is_fatal_error) { - if (explain) - { - result= new select_send(); - if (thd->send_explain_fields(result)) - { - delete result; - result= NULL; - } - } - else - result= new multi_delete(aux_tables, lex->table_count); - + result= new multi_delete(aux_tables, lex->table_count); if (result) { res= mysql_select(thd, &select_lex->ref_pointer_array, @@ -3362,20 +3351,22 @@ end_with_restore_list: if (!explain) { MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ } else { - result->reset_offset_limit(); - thd->lex->query_plan_footprint->print_explain(result, - thd->lex->describe); - } - - if (res) - result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ - else - { - if (explain) - result->send_eof(); + if (!res) + { + select_result *result= new select_send(); + LEX *lex= thd->lex; + if (thd->send_explain_fields(result) || + lex->query_plan_footprint->print_explain(result, lex->describe) || + result->send_eof()) + res= 1; + } + else + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ } delete result; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f9f96321bf1..afa99a5cd24 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22990,10 +22990,13 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, unit= unit->next_unit()) { /* - Display subqueries only if they are not parts of eliminated WHERE/ON - clauses. + Display subqueries only if + (1) they are not parts of ON clauses that were eliminated by table + elimination. + (2) they are not merged derived tables */ - if (!(unit->item && unit->item->eliminated)) + if (!(unit->item && unit->item->eliminated) && // (1) + (!unit->derived || unit->derived->is_materialized_derived())) // (2) { qp_node->add_child(unit->first_select()->select_number); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 15b3b9ad7e7..2881f6a7a1d 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1039,8 +1039,8 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->query_plan_footprint->print_explain(result, 0 /* explain flags*/); - + int err2= thd->lex->query_plan_footprint->print_explain(result, + thd->lex->describe); if (err2) result->abort_result_set(); else @@ -1048,7 +1048,7 @@ exit_without_my_ok: delete select; free_underlaid_joins(thd, select_lex); - DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); + DBUG_RETURN((err2 || thd->is_error()) ? 1 : 0); } /* @@ -1518,7 +1518,7 @@ bool mysql_multi_update(THD *thd, { if (explain) { - thd->lex->query_plan_footprint->print_explain(output, 0); + thd->lex->query_plan_footprint->print_explain(output, thd->lex->describe); output->send_eof(); delete output; } From 5e4044e92c748c69965bd9b22f231815c9fa8e51 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 4 Oct 2013 18:50:47 +0400 Subject: [PATCH 39/64] MDEV-5093, MDEV-5094: - Make EXPLAIN {PARTITIONS,EXTENDED} {UPDATE,DELETE} work. --- mysql-test/r/explain_non_select.result | 23 +++++++++++++++++++++++ mysql-test/t/explain_non_select.test | 21 +++++++++++++++++++++ sql/opt_qpf.cc | 2 +- sql/opt_qpf.h | 3 +++ sql/sql_delete.cc | 21 +++++++++++++++++++-- sql/sql_select.cc | 17 +++++++++++++---- sql/sql_select.h | 2 +- 7 files changed, 81 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 5597e6a876e..36e8197febe 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -138,3 +138,26 @@ i i 0 0 9 9 DROP TABLE t1; +# +# MDEV-5093, MDEV-5094: EXPLAIN PARTITIONS and EXPLAIN EXTENDED do not +# work for EXPLAIN UPDATE. +# +create table t1 (i int); +explain partitions update t1 set i = 3; +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t1 NULL ALL NULL NULL NULL NULL 0 +create table t2 (a int, b int) partition by hash(a) partitions 5; +insert into t2 values (0,0),(1,1),(2,2),(3,3),(4,4); +explain partitions update t2 set b=3 where a in (3,4); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t2 p3,p4 ALL NULL NULL NULL NULL 2 Using where +explain partitions delete from t2 where a in (3,4); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE t2 p3,p4 ALL NULL NULL NULL NULL 2 Using where +explain extended update t2 set b=3 where a in (3,4); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +explain extended delete from t2 where a in (3,4); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where +drop table t1,t2; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 2ca47ff8025..4da4b894d69 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -1,6 +1,8 @@ # # MariaDB tests for EXPLAIN UPDATE/DELETE. # +--source include/have_partition.inc + --disable_warnings drop table if exists t0, t1; --enable_warnings @@ -115,3 +117,22 @@ INSERT INTO t1 VALUES (7),(0),(9); SELECT * FROM t1 INNER JOIN ( SELECT DISTINCT * FROM t1 ) AS sq ON (sq.i = t1.i); DROP TABLE t1; + +--echo # +--echo # MDEV-5093, MDEV-5094: EXPLAIN PARTITIONS and EXPLAIN EXTENDED do not +--echo # work for EXPLAIN UPDATE. +--echo # +create table t1 (i int); +explain partitions update t1 set i = 3; + +create table t2 (a int, b int) partition by hash(a) partitions 5; +insert into t2 values (0,0),(1,1),(2,2),(3,3),(4,4); + +explain partitions update t2 set b=3 where a in (3,4); +explain partitions delete from t2 where a in (3,4); + +explain extended update t2 set b=3 where a in (3,4); +explain extended delete from t2 where a in (3,4); + +drop table t1,t2; + diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 30aca13e446..43c00573c2d 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -796,7 +796,7 @@ int QPF_update::print_explain(QPF_query *query, select_result_sink *output, 1, /* id */ select_type, table_name.c_ptr(), - // partitions, + used_partitions_set? used_partitions.c_ptr() : NULL, jtype, possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, key_str.length()? key_str.c_ptr() : NULL, diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index d5544e22650..d22135dcec0 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -454,6 +454,9 @@ public: const char *select_type; + StringBuffer<32> used_partitions; + bool used_partitions_set; + bool impossible_where; StringBuffer<64> table_name; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 25fe56d2e71..adc998be3a1 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -40,7 +40,7 @@ #include "records.h" // init_read_record, #include "sql_derived.h" // mysql_handle_list_of_derived // end_read_record - +#include "sql_partition.h" // make_used_partitions_str /* @brief @@ -92,7 +92,24 @@ void Update_plan::save_qpf_intern(QPF_query *query, QPF_update *qpf) select_lex->set_explain_type(TRUE); qpf->select_type= select_lex->type; - + /* Partitions */ + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; + if ((part_info= table->part_info)) + { + make_used_partitions_str(part_info, &qpf->used_partitions); + qpf->used_partitions_set= true; + } + else + qpf->used_partitions_set= false; +#else + /* just produce empty column if partitioning is not compiled in */ + qpf->used_partitions_set= false; +#endif + } + + /* Set jtype */ if (select && select->quick) { diff --git a/sql/sql_select.cc b/sql/sql_select.cc index afa99a5cd24..baaef14e7b9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22331,7 +22331,7 @@ int print_explain_row(select_result_sink *result, uint select_number, const char *select_type, const char *table_name, - //const char *partitions, (todo) + const char *partitions, enum join_type jtype, const char *possible_keys, const char *index, @@ -22351,7 +22351,15 @@ int print_explain_row(select_result_sink *result, item_list.push_back(new Item_string(table_name, strlen(table_name), cs)); if (options & DESCRIBE_PARTITIONS) - item_list.push_back(item_null); // psergey-todo: produce proper value + { + if (partitions) + { + item_list.push_back(new Item_string(partitions, + strlen(partitions), cs)); + } + else + item_list.push_back(item_null); + } const char *jtype_str= join_type_str[jtype]; item_list.push_back(new Item_string(jtype_str, @@ -22377,8 +22385,9 @@ int print_explain_row(select_result_sink *result, item_list.push_back(new Item_int(rows, MY_INT64_NUM_DECIMAL_DIGITS)); /* 'filtered' */ + const double filtered=100.0; if (options & DESCRIBE_EXTENDED) - item_list.push_back(item_null); + item_list.push_back(new Item_float(filtered, 2)); /* 'Extra' */ item_list.push_back(new Item_string(extra, strlen(extra), cs)); @@ -22489,7 +22498,7 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) /////////////////////////////////////////////////////////////////////////////// - +// TODO: join with make_possible_keys_line ? void append_possible_keys(String *str, TABLE *table, key_map possible_keys) { uint j; diff --git a/sql/sql_select.h b/sql/sql_select.h index 75d2aaf6631..b3177333d12 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1853,7 +1853,7 @@ int print_explain_row(select_result_sink *result, uint select_number, const char *select_type, const char *table_name, - //const char *partitions, (todo) + const char *partitions, enum join_type jtype, const char *possible_keys, const char *index, From f5fba6564b447369575bc129aac89d347368957e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 4 Oct 2013 19:09:39 +0400 Subject: [PATCH 40/64] MDEV-411: (different results for EXPLAIN and SHOW EXPLAIN) - Added a testcase --- mysql-test/r/show_explain.result | 39 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 37 +++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 3092fdc41bd..c124048aca5 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -1130,6 +1130,45 @@ set names default; # show explain for foo; ERROR HY000: You may only use constant expressions in this statement +# +# MDEV-411: SHOW EXPLAIN: For dependent subquery EXPLAIN produces type=index, key, 'Using where; Using index', +# while SHOW EXPLAIN says type=ALL, no key, 'Range checked for each record' +# +CREATE TABLE t1 (a INT NOT NULL, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0); +CREATE TABLE t2 (b INT NOT NULL) ENGINE=MyISAM; +INSERT INTO t2 VALUES (0),(8); +explain +SELECT SUM(b) FROM ( SELECT * FROM t1 ) AS alias1, t2 +WHERE b <= ANY ( +SELECT a FROM t1 +WHERE a = b + SLEEP(0.2) OR a >= ( SELECT SUM(b) FROM t2 )); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL a 4 NULL 2 Using index +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +3 DEPENDENT SUBQUERY t1 ALL a NULL NULL NULL 2 Range checked for each record (index map: 0x1) +4 SUBQUERY t2 ALL NULL NULL NULL NULL 2 +set @show_explain_probe_select_id=1; +set debug_dbug='+d,show_explain_probe_join_exec_start'; +SELECT SUM(b) FROM ( SELECT * FROM t1 ) AS alias1, t2 +WHERE b <= ANY ( +SELECT a FROM t1 +WHERE a = b + SLEEP(0.2) OR a >= ( SELECT SUM(b) FROM t2 )); +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL a 4 NULL 2 Using index +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +3 DEPENDENT SUBQUERY t1 ALL a NULL NULL NULL 2 Range checked for each record (index map: 0x1) +4 SUBQUERY t2 ALL NULL NULL NULL NULL 2 +Warnings: +Note 1003 SELECT SUM(b) FROM ( SELECT * FROM t1 ) AS alias1, t2 +WHERE b <= ANY ( +SELECT a FROM t1 +WHERE a = b + SLEEP(0.2) OR a >= ( SELECT SUM(b) FROM t2 )) +SUM(b) +0 +set debug_dbug=@old_debug; +DROP TABLE t1,t2; # End drop table t0; set debug_sync='RESET'; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 8ab1813f5bb..91e3be34408 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -1128,9 +1128,44 @@ set names default; --error ER_SET_CONSTANTS_ONLY show explain for foo; +--echo # +--echo # MDEV-411: SHOW EXPLAIN: For dependent subquery EXPLAIN produces type=index, key, 'Using where; Using index', +--echo # while SHOW EXPLAIN says type=ALL, no key, 'Range checked for each record' +--echo # +CREATE TABLE t1 (a INT NOT NULL, KEY(a)) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0); + +CREATE TABLE t2 (b INT NOT NULL) ENGINE=MyISAM; +INSERT INTO t2 VALUES (0),(8); + +explain +SELECT SUM(b) FROM ( SELECT * FROM t1 ) AS alias1, t2 +WHERE b <= ANY ( + SELECT a FROM t1 + WHERE a = b + SLEEP(0.2) OR a >= ( SELECT SUM(b) FROM t2 )); + + +set @show_explain_probe_select_id=1; +set debug_dbug='+d,show_explain_probe_join_exec_start'; + +send +SELECT SUM(b) FROM ( SELECT * FROM t1 ) AS alias1, t2 +WHERE b <= ANY ( + SELECT a FROM t1 + WHERE a = b + SLEEP(0.2) OR a >= ( SELECT SUM(b) FROM t2 )); + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=@old_debug; +DROP TABLE t1,t2; + --echo # End drop table t0; - connection default; disconnect con1; set debug_sync='RESET'; From fedf769f0b2001f8294c2b44dbcaca1e562f82a9 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 5 Oct 2013 09:58:22 +0400 Subject: [PATCH 41/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Address review feedback: rename nearly any name used by the new EXPLAIN code. --- sql/log.cc | 4 +- sql/log_event.cc | 2 +- sql/opt_qpf.cc | 102 +++++++++++++++++---------------- sql/opt_qpf.h | 130 +++++++++++++++++++++--------------------- sql/opt_range.cc | 35 +++++++----- sql/opt_range.h | 16 +++--- sql/sp_head.cc | 6 +- sql/sql_delete.cc | 16 +++--- sql/sql_join_cache.cc | 10 ++-- sql/sql_join_cache.h | 8 +-- sql/sql_lex.cc | 50 ++++++++-------- sql/sql_lex.h | 22 +++---- sql/sql_parse.cc | 17 +++--- sql/sql_prepare.cc | 6 +- sql/sql_select.cc | 101 +++++++++++--------------------- sql/sql_select.h | 16 ++++-- sql/sql_union.cc | 8 +-- sql/sql_update.cc | 8 +-- sql/sql_yacc.yy | 5 +- 19 files changed, 273 insertions(+), 289 deletions(-) diff --git a/sql/log.cc b/sql/log.cc index 3f782a478bb..7d39a27549a 100644 --- a/sql/log.cc +++ b/sql/log.cc @@ -2826,11 +2826,11 @@ bool MYSQL_QUERY_LOG::write(THD *thd, time_t current_time, thd->query_plan_fsort_passes) == (size_t) -1) tmp_errno= errno; if (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_EXPLAIN && - thd->lex->query_plan_footprint) + thd->lex->explain) { StringBuffer<128> buf; DBUG_ASSERT(!thd->free_list); - if (!print_qpf_query(thd->lex, thd, &buf)) + if (!print_explain_query(thd->lex, thd, &buf)) my_b_printf(&log_file, "%s", buf.c_ptr_safe()); thd->free_items(); } diff --git a/sql/log_event.cc b/sql/log_event.cc index 7ebcc6892e5..ca083711c40 100644 --- a/sql/log_event.cc +++ b/sql/log_event.cc @@ -9151,7 +9151,7 @@ int Rows_log_event::do_apply_event(Relay_log_info const *rli) call might reset the value of current_stmt_binlog_format, so we need to do any changes to that value after this function. */ - delete_qpf_query(thd->lex); + delete_explain_query(thd->lex); lex_start(thd); mysql_reset_thd_for_next_command(thd, 0); /* diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc index 43c00573c2d..248dc2b12be 100644 --- a/sql/opt_qpf.cc +++ b/sql/opt_qpf.cc @@ -22,14 +22,14 @@ #include "sql_select.h" -QPF_query::QPF_query() +Explain_query::Explain_query() { upd_del_plan= NULL; operations= 0; } -QPF_query::~QPF_query() +Explain_query::~Explain_query() { delete upd_del_plan; uint i; @@ -40,37 +40,37 @@ QPF_query::~QPF_query() } -QPF_node *QPF_query::get_node(uint select_id) +Explain_node *Explain_query::get_node(uint select_id) { - QPF_union *u; + Explain_union *u; if ((u= get_union(select_id))) return u; else return get_select(select_id); } -QPF_union *QPF_query::get_union(uint select_id) +Explain_union *Explain_query::get_union(uint select_id) { return (unions.elements() > select_id) ? unions.at(select_id) : NULL; } -QPF_select *QPF_query::get_select(uint select_id) +Explain_select *Explain_query::get_select(uint select_id) { return (selects.elements() > select_id) ? selects.at(select_id) : NULL; } -void QPF_query::add_node(QPF_node *node) +void Explain_query::add_node(Explain_node *node) { operations++; - if (node->get_type() == QPF_node::QPF_UNION) + if (node->get_type() == Explain_node::EXPLAIN_UNION) { - QPF_union *u= (QPF_union*)node; + Explain_union *u= (Explain_union*)node; uint select_id= u->get_select_id(); if (unions.elements() <= select_id) unions.resize(max(select_id+1, unions.elements()*2), NULL); - QPF_union *old_node; + Explain_union *old_node; if ((old_node= get_union(select_id))) delete old_node; @@ -78,15 +78,15 @@ void QPF_query::add_node(QPF_node *node) } else { - QPF_select *sel= (QPF_select*)node; - if (sel->select_id == (int)UINT_MAX) + Explain_select *sel= (Explain_select*)node; + if (sel->select_id == FAKE_SELECT_LEX_ID) { DBUG_ASSERT(0); // this is a "fake select" from a UNION. } else { uint select_id= sel->select_id; - QPF_select *old_node; + Explain_select *old_node; if (selects.elements() <= select_id) selects.resize(max(select_id+1, selects.elements()*2), NULL); @@ -104,7 +104,7 @@ void QPF_query::add_node(QPF_node *node) The main entry point to print EXPLAIN of the entire query */ -int QPF_query::print_explain(select_result_sink *output, +int Explain_query::print_explain(select_result_sink *output, uint8 explain_flags) { if (upd_del_plan) @@ -115,7 +115,7 @@ int QPF_query::print_explain(select_result_sink *output, else { /* Start printing from node with id=1 */ - QPF_node *node= get_node(1); + Explain_node *node= get_node(1); if (!node) return 1; /* No query plan */ return node->print_explain(this, output, explain_flags); @@ -123,13 +123,13 @@ int QPF_query::print_explain(select_result_sink *output, } -bool print_qpf_query(LEX *lex, THD *thd, String *str) +bool print_explain_query(LEX *lex, THD *thd, String *str) { - return lex->query_plan_footprint->print_explain_str(thd, str); + return lex->explain->print_explain_str(thd, str); } -bool QPF_query::print_explain_str(THD *thd, String *out_str) +bool Explain_query::print_explain_str(THD *thd, String *out_str) { List fields; thd->make_explain_field_list(fields); @@ -157,13 +157,14 @@ static void push_string(List *item_list, String *str) } -int QPF_union::print_explain(QPF_query *query, select_result_sink *output, - uint8 explain_flags) +int Explain_union::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) { /* print all UNION children, in order */ for (int i= 0; i < (int) union_members.elements(); i++) { - QPF_select *sel= query->get_select(union_members.at(i)); + Explain_select *sel= query->get_select(union_members.at(i)); sel->print_explain(query, output, explain_flags); } @@ -257,13 +258,13 @@ int QPF_union::print_explain(QPF_query *query, select_result_sink *output, Print EXPLAINs for all children nodes (i.e. for subqueries) */ -int QPF_node::print_explain_for_children(QPF_query *query, +int Explain_node::print_explain_for_children(Explain_query *query, select_result_sink *output, uint8 explain_flags) { for (int i= 0; i < (int) children.elements(); i++) { - QPF_node *node= query->get_node(children.at(i)); + Explain_node *node= query->get_node(children.at(i)); if (node->print_explain(query, output, explain_flags)) return 1; } @@ -271,7 +272,7 @@ int QPF_node::print_explain_for_children(QPF_query *query, } -QPF_select::~QPF_select() +Explain_select::~Explain_select() { if (join_tabs) { @@ -282,8 +283,9 @@ QPF_select::~QPF_select() } -int QPF_select::print_explain(QPF_query *query, select_result_sink *output, - uint8 explain_flags) +int Explain_select::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) { if (message) { @@ -330,13 +332,13 @@ int QPF_select::print_explain(QPF_query *query, select_result_sink *output, } -void QPF_table_access::push_extra(enum Extra_tag extra_tag) +void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) { extra_tags.append(extra_tag); } -int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags, +int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, uint select_id, const char *select_type, bool using_temporary, bool using_filesort) { @@ -551,7 +553,7 @@ const char * extra_tag_text[]= }; -void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) +void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag) { switch (tag) { case ET_USING: @@ -618,13 +620,13 @@ void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) /* - This is called for top-level QPF_quick_select only. The point of this + This is called for top-level Explain_quick_select only. The point of this function is: - index_merge should print $index_merge_type (child, ...) - 'range' should not print anything. */ -void QPF_quick_select::print_extra(String *str) +void Explain_quick_select::print_extra(String *str) { if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || @@ -637,7 +639,7 @@ void QPF_quick_select::print_extra(String *str) } -void QPF_quick_select::print_extra_recursive(String *str) +void Explain_quick_select::print_extra_recursive(String *str) { if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC) @@ -648,8 +650,8 @@ void QPF_quick_select::print_extra_recursive(String *str) { str->append(get_name_by_type()); str->append('('); - List_iterator_fast it (children); - QPF_quick_select* child; + List_iterator_fast it (children); + Explain_quick_select* child; bool first= true; while ((child = it++)) { @@ -665,7 +667,7 @@ void QPF_quick_select::print_extra_recursive(String *str) } -const char * QPF_quick_select::get_name_by_type() +const char * Explain_quick_select::get_name_by_type() { switch (quick_type) { case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE: @@ -687,7 +689,7 @@ const char * QPF_quick_select::get_name_by_type() This prints a comma-separated list of used indexes, ignoring nesting */ -void QPF_quick_select::print_key(String *str) +void Explain_quick_select::print_key(String *str) { if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || @@ -699,8 +701,8 @@ void QPF_quick_select::print_key(String *str) } else { - List_iterator_fast it (children); - QPF_quick_select* child; + List_iterator_fast it (children); + Explain_quick_select* child; while ((child = it++)) { child->print_key(str); @@ -713,7 +715,7 @@ void QPF_quick_select::print_key(String *str) This prints a comma-separated list of used key_lengths, ignoring nesting */ -void QPF_quick_select::print_key_len(String *str) +void Explain_quick_select::print_key_len(String *str) { if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || @@ -728,8 +730,8 @@ void QPF_quick_select::print_key_len(String *str) } else { - List_iterator_fast it (children); - QPF_quick_select* child; + List_iterator_fast it (children); + Explain_quick_select* child; while ((child = it++)) { child->print_key_len(str); @@ -738,7 +740,7 @@ void QPF_quick_select::print_key_len(String *str) } -int QPF_delete::print_explain(QPF_query *query, select_result_sink *output, +int QPF_delete::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags) { if (deleting_all_rows) @@ -757,7 +759,7 @@ int QPF_delete::print_explain(QPF_query *query, select_result_sink *output, } -int QPF_update::print_explain(QPF_query *query, select_result_sink *output, +int QPF_update::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags) { StringBuffer<64> extra_str; @@ -809,18 +811,18 @@ int QPF_update::print_explain(QPF_query *query, select_result_sink *output, } -void delete_qpf_query(LEX *lex) +void delete_explain_query(LEX *lex) { - delete lex->query_plan_footprint; - lex->query_plan_footprint= NULL; + delete lex->explain; + lex->explain= NULL; } -void create_qpf_query(LEX *lex, MEM_ROOT *mem_root) +void create_explain_query(LEX *lex, MEM_ROOT *mem_root) { - DBUG_ASSERT(!lex->query_plan_footprint); - lex->query_plan_footprint= new QPF_query; + DBUG_ASSERT(!lex->explain); + lex->explain= new Explain_query; DBUG_ASSERT(mem_root == current_thd->mem_root); - lex->query_plan_footprint->mem_root= mem_root; + lex->explain->mem_root= mem_root; } diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h index d22135dcec0..ef36cb4a44d 100644 --- a/sql/opt_qpf.h +++ b/sql/opt_qpf.h @@ -17,33 +17,35 @@ /************************************************************************************** - Query Plan Footprint (QPF) structures + Data structures for producing EXPLAIN outputs. These structures - - Can be produced in-expensively from query plan. - - Store sufficient information to produce either a tabular or a json EXPLAIN - output + - Can be produced inexpensively from query plan. + - Store sufficient information to produce a tabular and/or a json EXPLAIN - Have methods that produce a tabular output. - + *************************************************************************************/ -class QPF_query; + +const int FAKE_SELECT_LEX_ID= (int)UINT_MAX; + +class Explain_query; /* A node can be either a SELECT, or a UNION. */ -class QPF_node : public Sql_alloc +class Explain_node : public Sql_alloc { public: - enum qpf_node_type {QPF_UNION, QPF_SELECT, QPF_UPDATE, QPF_DELETE }; - virtual enum qpf_node_type get_type()= 0; + enum explain_node_type {EXPLAIN_UNION, EXPLAIN_SELECT, EXPLAIN_UPDATE, EXPLAIN_DELETE }; + virtual enum explain_node_type get_type()= 0; virtual int get_select_id()= 0; /* - A node may have children nodes. When a node's QPF (Query Plan Footprint) is - created, children nodes may not yet have QPFs. This is why we store ids. + A node may have children nodes. When a node's explain structure is + created, children nodes may not yet have QPFs. This is why we store ids. */ Dynamic_array children; void add_child(int select_no) @@ -51,20 +53,20 @@ public: children.append(select_no); } - virtual int print_explain(QPF_query *query, select_result_sink *output, + virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags)=0; - int print_explain_for_children(QPF_query *query, select_result_sink *output, + int print_explain_for_children(Explain_query *query, select_result_sink *output, uint8 explain_flags); - virtual ~QPF_node(){} + virtual ~Explain_node(){} }; -class QPF_table_access; +class Explain_table_access; /* - Query Plan Footprint of a SELECT. + EXPLAIN structure for a SELECT. A select can be: 1. A degenerate case. In this case, message!=NULL, and it contains a @@ -74,27 +76,27 @@ class QPF_table_access; In the non-degenerate case, a SELECT may have a GROUP BY/ORDER BY operation. - In both cases, the select may have children nodes. class QPF_node provides + In both cases, the select may have children nodes. class Explain_node provides a way get node's children. */ -class QPF_select : public QPF_node +class Explain_select : public Explain_node { public: - enum qpf_node_type get_type() { return QPF_SELECT; } + enum explain_node_type get_type() { return EXPLAIN_SELECT; } - QPF_select() : + Explain_select() : message(NULL), join_tabs(NULL), using_temporary(false), using_filesort(false) {} - ~QPF_select(); + ~Explain_select(); - bool add_table(QPF_table_access *tab) + bool add_table(Explain_table_access *tab) { if (!join_tabs) { - join_tabs= (QPF_table_access**) my_malloc(sizeof(QPF_table_access*) * + join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) * MAX_TABLES, MYF(0)); n_join_tabs= 0; } @@ -115,31 +117,31 @@ public: const char *message; /* - A flat array of Query Plan Footprints. The order is "just like EXPLAIN + A flat array of Explain structs for tables. The order is "just like EXPLAIN would print them". */ - QPF_table_access** join_tabs; + Explain_table_access** join_tabs; uint n_join_tabs; /* Global join attributes. In tabular form, they are printed on the first row */ bool using_temporary; bool using_filesort; - int print_explain(QPF_query *query, select_result_sink *output, + int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags); }; /* - Query Plan Footprint of a UNION. + Explain structure for a UNION. A UNION may or may not have "Using filesort". */ -class QPF_union : public QPF_node +class Explain_union : public Explain_node { public: - enum qpf_node_type get_type() { return QPF_UNION; } + enum explain_node_type get_type() { return EXPLAIN_UNION; } int get_select_id() { @@ -163,7 +165,7 @@ public: { union_members.append(select_no); } - int print_explain(QPF_query *query, select_result_sink *output, + int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags); const char *fake_select_type; @@ -174,7 +176,7 @@ class QPF_delete; /* - Query Plan Footprint (QPF) for a query (i.e. a statement). + Explain structure for a query (i.e. a statement). This should be able to survive when the query plan was deleted. Currently, we do not intend for it survive until after query's MEM_ROOT is freed. It @@ -208,21 +210,21 @@ class QPF_delete; */ -class QPF_query : public Sql_alloc +class Explain_query : public Sql_alloc { public: - QPF_query(); - ~QPF_query(); + Explain_query(); + ~Explain_query(); /* Add a new node */ - void add_node(QPF_node *node); + void add_node(Explain_node *node); /* This will return a select, or a union */ - QPF_node *get_node(uint select_id); + Explain_node *get_node(uint select_id); /* This will return a select (even if there is a union with this id) */ - QPF_select *get_select(uint select_id); + Explain_select *get_select(uint select_id); - QPF_union *get_union(uint select_id); + Explain_union *get_union(uint select_id); /* QPF_delete inherits from QPF_update */ QPF_update *upd_del_plan; @@ -237,8 +239,8 @@ public: bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; } MEM_ROOT *mem_root; private: - Dynamic_array unions; - Dynamic_array selects; + Dynamic_array unions; + Dynamic_array selects; /* Debugging aid: count how many times add_node() was called. Ideally, it @@ -252,10 +254,10 @@ private: /* Some of the tags have matching text. See extra_tag_text for text names, and - QPF_table_access::append_tag_name() for code to convert from tag form to text + Explain_table_access::append_tag_name() for code to convert from tag form to text form. */ -enum Extra_tag +enum explain_extra_tag { ET_none= 0, /* not-a-tag */ ET_USING_INDEX_CONDITION, @@ -296,19 +298,19 @@ enum Extra_tag }; -typedef struct st_qpf_bka_type +typedef struct st_explain_bka_type { bool incremental; const char *join_alg; StringBuffer<64> mrr_type; -} QPF_BKA_TYPE; +} EXPLAIN_BKA_TYPE; /* Data about how an index is used by some access method */ -class QPF_index_use : public Sql_alloc +class Explain_index_use : public Sql_alloc { char *key_name; uint key_len; @@ -336,16 +338,16 @@ public: /* QPF for quick range selects, as well as index_merge select */ -class QPF_quick_select : public Sql_alloc +class Explain_quick_select : public Sql_alloc { public: int quick_type; /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ - QPF_index_use range; + Explain_index_use range; /* Used in all other cases */ - List children; + List children; void print_extra(String *str); void print_key(String *str); @@ -359,20 +361,20 @@ private: /* Query Plan Footprint for a JOIN_TAB. */ -class QPF_table_access : public Sql_alloc +class Explain_table_access : public Sql_alloc { public: - void push_extra(enum Extra_tag extra_tag); + void push_extra(enum explain_extra_tag extra_tag); /* Internals */ public: /* - 0 means this tab is not inside SJM nest and should use QPF_select's id + 0 means this tab is not inside SJM nest and should use Explain_select's id other value means the tab is inside an SJM nest. */ int sjm_nest_select_id; - /* id and 'select_type' are cared-of by the parent QPF_select */ + /* id and 'select_type' are cared-of by the parent Explain_select */ StringBuffer<32> table_name; enum join_type type; @@ -391,13 +393,13 @@ public: key.key_name == NULL means 'NULL' will be shown in tabular output. key.key_len == (uint)-1 means 'NULL' will be shown in tabular output. */ - QPF_index_use key; + Explain_index_use key; /* when type==JT_HASH_NEXT, 'key' stores the hash join pseudo-key. hash_next_key stores the table's key. */ - QPF_index_use hash_next_key; + Explain_index_use hash_next_key; bool ref_set; /* not set means 'NULL' should be printed */ StringBuffer<32> ref; @@ -412,10 +414,10 @@ public: Contents of the 'Extra' column. Some are converted into strings, some have parameters, values for which are stored below. */ - Dynamic_array extra_tags; + Dynamic_array extra_tags; // Valid if ET_USING tag is present - QPF_quick_select *quick_info; + Explain_quick_select *quick_info; // Valid if ET_USING_INDEX_FOR_GROUP_BY is present bool loose_scan_is_scanning; @@ -427,7 +429,7 @@ public: StringBuffer<32> mrr_type; // valid with ET_USING_JOIN_BUFFER - QPF_BKA_TYPE bka_type; + EXPLAIN_BKA_TYPE bka_type; StringBuffer<32> firstmatch_table_name; @@ -435,21 +437,21 @@ public: uint select_id, const char *select_type, bool using_temporary, bool using_filesort); private: - void append_tag_name(String *str, enum Extra_tag tag); + void append_tag_name(String *str, enum explain_extra_tag tag); }; /* Query Plan Footprint for single-table UPDATE. - This is similar to QPF_table_access, except that it is more restrictive. + This is similar to Explain_table_access, except that it is more restrictive. Also, it can have UPDATE operation options, but currently there aren't any. */ -class QPF_update : public QPF_node +class QPF_update : public Explain_node { public: - virtual enum qpf_node_type get_type() { return QPF_UPDATE; } + virtual enum explain_node_type get_type() { return EXPLAIN_UPDATE; } virtual int get_select_id() { return 1; /* always root */ } const char *select_type; @@ -471,7 +473,7 @@ public: bool using_filesort; - virtual int print_explain(QPF_query *query, select_result_sink *output, + virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags); }; @@ -489,10 +491,10 @@ public: */ bool deleting_all_rows; - virtual enum qpf_node_type get_type() { return QPF_DELETE; } + virtual enum explain_node_type get_type() { return EXPLAIN_DELETE; } virtual int get_select_id() { return 1; /* always root */ } - virtual int print_explain(QPF_query *query, select_result_sink *output, + virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags); }; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 97cbe73dcb4..2e32009bc54 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11940,37 +11940,39 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, Explain_quick_select *qpf) { qpf->quick_type= QS_TYPE_RANGE; qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); } -void QUICK_GROUP_MIN_MAX_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_GROUP_MIN_MAX_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *qpf) { qpf->quick_type= QS_TYPE_GROUP_MIN_MAX; qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); } -void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *qpf) { qpf->quick_type= get_type(); QUICK_RANGE_SELECT *quick; - QPF_quick_select *child_qpf; + Explain_quick_select *child_qpf; List_iterator_fast it(quick_selects); while ((quick= it++)) { - child_qpf= new QPF_quick_select; + child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } if (pk_quick_select) { - child_qpf= new QPF_quick_select; + child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); pk_quick_select->save_info(alloc, child_qpf); } @@ -11980,14 +11982,15 @@ void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) Same as QUICK_INDEX_SORT_SELECT::save_info(), but primary key is printed first */ -void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *qpf) { qpf->quick_type= get_type(); - QPF_quick_select *child_qpf; + Explain_quick_select *child_qpf; if (pk_quick_select) { - child_qpf= new QPF_quick_select; + child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); pk_quick_select->save_info(alloc, child_qpf); } @@ -11996,7 +11999,7 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select * List_iterator_fast it(quick_selects); while ((quick= it++)) { - child_qpf= new QPF_quick_select; + child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } @@ -12004,7 +12007,8 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select * } -void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *qpf) { qpf->quick_type= get_type(); @@ -12012,21 +12016,22 @@ void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qp List_iterator_fast it(quick_selects); while ((qr= it++)) { - QPF_quick_select *child_qpf= new QPF_quick_select; + Explain_quick_select *child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); qr->quick->save_info(alloc, child_qpf); } if (cpk_quick) { - QPF_quick_select *child_qpf= new QPF_quick_select; + Explain_quick_select *child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); cpk_quick->save_info(alloc, child_qpf); } } -void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) +void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *qpf) { qpf->quick_type= get_type(); @@ -12034,7 +12039,7 @@ void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, QPF_quick_select *qpf) List_iterator_fast it(quick_selects); while ((quick= it++)) { - QPF_quick_select *child_qpf= new QPF_quick_select; + Explain_quick_select *child_qpf= new Explain_quick_select; qpf->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } diff --git a/sql/opt_range.h b/sql/opt_range.h index f2e8b92e7d0..9bc19927fd0 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -52,7 +52,7 @@ typedef struct st_key_part { Field::imagetype image_type; } KEY_PART; -class QPF_quick_select; +class Explain_quick_select; /* A "MIN_TUPLE < tbl.key_tuple < MAX_TUPLE" interval. @@ -346,7 +346,7 @@ public: void add_key_name(String *str, bool *first); /* Save information about quick select's query plan */ - virtual void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf)= 0; + virtual void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf)= 0; /* Return 1 if any index used by this quick select @@ -473,7 +473,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -610,7 +610,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -674,7 +674,7 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); }; @@ -712,7 +712,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -791,7 +791,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -940,7 +940,7 @@ public: #endif bool is_agg_distinct() { return have_agg_distinct; } bool loose_scan_is_scanning() { return is_index_scan; } - void save_info(MEM_ROOT *alloc, QPF_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); }; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index a3027763b8a..31fd0d7248e 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3034,7 +3034,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, thd->mdl_context.release_statement_locks(); } - delete_qpf_query(m_lex); + delete_explain_query(m_lex); if (m_lex->query_tables_own_last) { @@ -3242,7 +3242,7 @@ sp_instr_set::execute(THD *thd, uint *nextp) int sp_instr_set::exec_core(THD *thd, uint *nextp) { - create_qpf_query(thd->lex, thd->mem_root); + create_explain_query(thd->lex, thd->mem_root); int res= thd->spcont->set_variable(thd, m_offset, &m_value); if (res) @@ -3255,7 +3255,7 @@ sp_instr_set::exec_core(THD *thd, uint *nextp) my_error(ER_OUT_OF_RESOURCES, MYF(ME_FATALERROR)); } } - delete_qpf_query(thd->lex); + delete_explain_query(thd->lex); *nextp = m_ip+1; return res; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index adc998be3a1..58deae4162a 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -51,7 +51,7 @@ invoked on a running DELETE statement. */ -void Delete_plan::save_qpf(QPF_query *query) +void Delete_plan::save_explain_data(Explain_query *query) { QPF_delete* qpf= new QPF_delete; @@ -63,22 +63,22 @@ void Delete_plan::save_qpf(QPF_query *query) else { qpf->deleting_all_rows= false; - Update_plan::save_qpf_intern(query, qpf); + Update_plan::save_explain_data_intern(query, qpf); } query->upd_del_plan= qpf; } -void Update_plan::save_qpf(QPF_query *query) +void Update_plan::save_explain_data(Explain_query *query) { QPF_update* qpf= new QPF_update; - save_qpf_intern(query, qpf); + save_explain_data_intern(query, qpf); query->upd_del_plan= qpf; } -void Update_plan::save_qpf_intern(QPF_query *query, QPF_update *qpf) +void Update_plan::save_explain_data_intern(Explain_query *query, QPF_update *qpf) { qpf->select_type= "SIMPLE"; qpf->table_name.append(table->pos_in_table_list->alias); @@ -434,7 +434,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->lex->describe) goto exit_without_my_ok; - query_plan.save_qpf(thd->lex->query_plan_footprint); + query_plan.save_explain_data(thd->lex->explain); thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", @@ -667,7 +667,7 @@ cleanup: /* Special exits */ exit_without_my_ok: - query_plan.save_qpf(thd->lex->query_plan_footprint); + query_plan.save_explain_data(thd->lex->explain); select_send *result2; if (!(result2= new select_send())) @@ -675,7 +675,7 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result2->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result2); - int err2= thd->lex->query_plan_footprint->print_explain(result2, thd->lex->describe); + int err2= thd->lex->explain->print_explain(result2, thd->lex->describe); if (err2) result2->abort_result_set(); diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 7710db5c7ba..040e596d283 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2568,7 +2568,7 @@ finish: none */ -void JOIN_CACHE::save_qpf(struct st_qpf_bka_type *qpf) +void JOIN_CACHE::save_explain_data(struct st_explain_bka_type *qpf) { qpf->incremental= test(prev_cache); @@ -2613,16 +2613,16 @@ static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file) } } -void JOIN_CACHE_BKA::save_qpf(struct st_qpf_bka_type *qpf) +void JOIN_CACHE_BKA::save_explain_data(struct st_explain_bka_type *qpf) { - JOIN_CACHE::save_qpf(qpf); + JOIN_CACHE::save_explain_data(qpf); add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); } -void JOIN_CACHE_BKAH::save_qpf(struct st_qpf_bka_type *qpf) +void JOIN_CACHE_BKAH::save_explain_data(struct st_explain_bka_type *qpf) { - JOIN_CACHE::save_qpf(qpf); + JOIN_CACHE::save_explain_data(qpf); add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); } diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h index 75589c3395f..0b03f2b8bab 100644 --- a/sql/sql_join_cache.h +++ b/sql/sql_join_cache.h @@ -63,7 +63,7 @@ typedef struct st_cache_field { class JOIN_TAB_SCAN; -struct st_qpf_bka_type; +struct st_explain_bka_type; /* JOIN_CACHE is the base class to support the implementations of @@ -658,7 +658,7 @@ public: enum_nested_loop_state join_records(bool skip_last); /* Add a comment on the join algorithm employed by the join cache */ - virtual void save_qpf(struct st_qpf_bka_type *qpf); + virtual void save_explain_data(struct st_explain_bka_type *qpf); THD *thd(); @@ -1336,7 +1336,7 @@ public: /* Check index condition of the joined table for a record from BKA cache */ bool skip_index_tuple(range_id_t range_info); - void save_qpf(struct st_qpf_bka_type *qpf); + void save_explain_data(struct st_explain_bka_type *qpf); }; @@ -1427,5 +1427,5 @@ public: /* Check index condition of the joined table for a record from BKAH cache */ bool skip_index_tuple(range_id_t range_info); - void save_qpf(struct st_qpf_bka_type *qpf); + void save_explain_data(struct st_explain_bka_type *qpf); }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index ff71bf4eb09..48c65e68f87 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,7 +448,7 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; - lex->query_plan_footprint= NULL; + lex->explain= NULL; lex->context_stack.empty(); lex->unit.init_query(); @@ -2572,7 +2572,7 @@ void Query_tables_list::destroy_query_tables_list() */ LEX::LEX() - : query_plan_footprint(NULL), + : explain(NULL), result(0), option_type(OPT_DEFAULT), is_lex_started(0), limit_rows_examined_cnt(ULONGLONG_MAX) { @@ -3515,18 +3515,18 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) is_correlated_unit|= sl->is_correlated; inner_join->select_options= save_options; un->thd->lex->current_select= save_select; - /// psergey: - QPF_query *qpf; - if ((qpf= inner_join->thd->lex->query_plan_footprint)) + + Explain_query *qpf; + if ((qpf= inner_join->thd->lex->explain)) { - QPF_select *qp_sel; + Explain_select *qp_sel; if ((qp_sel= qpf->get_select(inner_join->select_lex->select_number))) { sl->set_explain_type(TRUE); qp_sel->select_type= sl->type; } } - /// + if (empty_union_result) { /* @@ -4215,9 +4215,9 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { int res; - if (query_plan_footprint && query_plan_footprint->have_query_plan()) + if (explain && explain->have_query_plan()) { - res= query_plan_footprint->print_explain(output, explain_flags); + res= explain->print_explain(output, explain_flags); *printed_anything= true; } else @@ -4230,10 +4230,10 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, /* - Save query plan of a UNION. The only variable member is whether the union has - "Using filesort". + Save explain structures of a UNION. The only variable member is whether the + union has "Using filesort". - There is also save_union_qpf_part2() function, which is called before we read + There is also save_union_explain_part2() function, which is called before we read UNION's output. The reason for it is examples like this: @@ -4245,10 +4245,10 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, stage in execution. */ -int st_select_lex_unit::save_union_qpf(QPF_query *output) +int st_select_lex_unit::save_union_explain(Explain_query *output) { SELECT_LEX *first= first_select(); - QPF_union *qpfu= new (output->mem_root) QPF_union; + Explain_union *eu= new (output->mem_root) Explain_union; /* TODO: The following code should be eliminated. If we have a capability to @@ -4264,30 +4264,34 @@ int st_select_lex_unit::save_union_qpf(QPF_query *output) const char *msg="Query plan already deleted"; first->set_explain_type(TRUE/* on_the_fly */); - QPF_select *qp_sel= new (output->mem_root)QPF_select; + Explain_select *qp_sel= new (output->mem_root)Explain_select; qp_sel->select_id= first->select_number; qp_sel->select_type= first->type; qp_sel->message= msg; output->add_node(qp_sel); - qpfu->add_select(qp_sel->select_id); + eu->add_select(qp_sel->select_id); return 0; } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) - qpfu->add_select(sl->select_number); + eu->add_select(sl->select_number); // Save the UNION node - output->add_node(qpfu); + output->add_node(eu); - qpfu->fake_select_type= "UNION RESULT"; - qpfu->using_filesort= test(global_parameters->order_list.first); + eu->fake_select_type= "UNION RESULT"; + eu->using_filesort= test(global_parameters->order_list.first); return 0; } -int st_select_lex_unit::save_union_qpf_part2(QPF_query *output) +/* + @see st_select_lex_unit::save_union_explain +*/ + +int st_select_lex_unit::save_union_explain_part2(Explain_query *output) { - QPF_union *qpfu= output->get_union(first_select()->select_number); + Explain_union *eu= output->get_union(first_select()->select_number); if (fake_select_lex) { for (SELECT_LEX_UNIT *unit= fake_select_lex->first_inner_unit(); @@ -4295,7 +4299,7 @@ int st_select_lex_unit::save_union_qpf_part2(QPF_query *output) { if (!(unit->item && unit->item->eliminated)) { - qpfu->add_child(unit->first_select()->select_number); + eu->add_child(unit->first_select()->select_number); } } } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 13140063bff..8bff5c640df 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -618,11 +618,11 @@ class select_result; class JOIN; class select_union; class Procedure; -class QPF_query; +class Explain_query; -void delete_qpf_query(LEX *lex); -void create_qpf_query(LEX *lex, MEM_ROOT *mem_root); -bool print_qpf_query(LEX *lex, THD *thd, String *str); +void delete_explain_query(LEX *lex); +void create_explain_query(LEX *lex, MEM_ROOT *mem_root); +bool print_explain_query(LEX *lex, THD *thd, String *str); class st_select_lex_unit: public st_select_lex_node { protected: @@ -734,8 +734,8 @@ public: List *get_unit_column_types(); - int save_union_qpf(QPF_query *output); - int save_union_qpf_part2(QPF_query *output); + int save_union_explain(Explain_query *output); + int save_union_explain_part2(Explain_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -2368,7 +2368,7 @@ protected: class Delete_plan; class SQL_SELECT; -class QPF_query; +class Explain_query; class QPF_update; /* @@ -2398,8 +2398,8 @@ public: /* Set this plan to be a plan to do nothing because of impossible WHRE*/ void set_impossible_where() { impossible_where= true; } - void save_qpf(QPF_query *query); - void save_qpf_intern(QPF_query *query, QPF_update *qpf); + void save_explain_data(Explain_query *query); + void save_explain_data_intern(Explain_query *query, QPF_update *qpf); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} @@ -2423,7 +2423,7 @@ public: table_rows= rows_arg; } - void save_qpf(QPF_query *query); + void save_explain_data(Explain_query *query); }; @@ -2439,7 +2439,7 @@ struct LEX: public Query_tables_list SELECT_LEX *all_selects_list; /* Query Plan Footprint of a currently running select */ - QPF_query *query_plan_footprint; + Explain_query *explain; char *length,*dec,*change; LEX_STRING name; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 71d68c6730d..76c26da5839 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -598,7 +598,7 @@ static void handle_bootstrap_impl(THD *thd) #if defined(ENABLED_PROFILING) thd->profiling.finish_current_query(); #endif - delete_qpf_query(thd->lex); + delete_explain_query(thd->lex); if (bootstrap_error) break; @@ -1526,7 +1526,7 @@ bool dispatch_command(enum enum_server_command command, THD *thd, /* @note - This function must call delete_qpf_query(). + This function must call delete_explain_query(). */ void log_slow_statement(THD *thd) { @@ -1547,7 +1547,7 @@ void log_slow_statement(THD *thd) (thd->variables.log_slow_filter && !(thd->variables.log_slow_filter & thd->query_plan_flags))) { - delete_qpf_query(thd->lex); + delete_explain_query(thd->lex); DBUG_VOID_RETURN; } @@ -1573,7 +1573,7 @@ void log_slow_statement(THD *thd) thd_proc_info(thd, 0); } - delete_qpf_query(thd->lex); + delete_explain_query(thd->lex); DBUG_VOID_RETURN; } @@ -2201,7 +2201,7 @@ mysql_execute_command(THD *thd) thd->mdl_context.release_transactional_locks(); } - create_qpf_query(thd->lex, thd->mem_root); + create_explain_query(thd->lex, thd->mem_root); #ifndef DBUG_OFF if (lex->sql_command != SQLCOM_SET_OPTION) @@ -3252,7 +3252,7 @@ end_with_restore_list: select_result *result= new select_send(); LEX *lex= thd->lex; if (thd->send_explain_fields(result) || - lex->query_plan_footprint->print_explain(result, lex->describe) || + lex->explain->print_explain(result, lex->describe) || result->send_eof()) res= 1; } @@ -3361,7 +3361,7 @@ end_with_restore_list: select_result *result= new select_send(); LEX *lex= thd->lex; if (thd->send_explain_fields(result) || - lex->query_plan_footprint->print_explain(result, lex->describe) || + lex->explain->print_explain(result, lex->describe) || result->send_eof()) res= 1; } @@ -4901,8 +4901,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) top-level LIMIT */ result->reset_offset_limit(); - thd->lex->query_plan_footprint->print_explain(result, - thd->lex->describe); + thd->lex->explain->print_explain(result, thd->lex->describe); if (lex->describe & DESCRIBE_EXTENDED) { char buff[1024]; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index e3d6c75453e..4a8f3314c8b 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2491,7 +2491,7 @@ void reinit_stmt_before_use(THD *thd, LEX *lex) object and because of this can be used in different threads. */ lex->thd= thd; - DBUG_ASSERT(!lex->query_plan_footprint); + DBUG_ASSERT(!lex->explain); if (lex->empty_field_list_on_rset) { @@ -3931,8 +3931,8 @@ bool Prepared_statement::execute(String *expanded_query, bool open_cursor) if (! cursor) cleanup_stmt(); - //psergey: TODO the "EXECUTE problem" is here - delete_qpf_query(thd->lex); + + delete_explain_query(thd->lex); thd->set_statement(&stmt_backup); thd->stmt_arena= old_stmt_arena; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index baaef14e7b9..e7557b92ea4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2305,9 +2305,8 @@ void join_save_qpf(JOIN *join) join->have_query_plan != JOIN::QEP_NOT_PRESENT_YET && join->have_query_plan != JOIN::QEP_DELETED && // this happens when there was no QEP ever, but then //cleanup() is called multiple times - - thd->lex->query_plan_footprint && // for "SET" command in SPs. - !thd->lex->query_plan_footprint->get_select(join->select_lex->select_number)) + thd->lex->explain && // for "SET" command in SPs. + !thd->lex->explain->get_select(join->select_lex->select_number)) { const char *message= NULL; @@ -2317,12 +2316,12 @@ void join_save_qpf(JOIN *join) message= join->zero_result_cause ? join->zero_result_cause : "No tables used"; } - join->save_qpf(thd->lex->query_plan_footprint, - join->need_tmp, // need_tmp_table - !join->skip_sort_order && !join->no_order && - (join->order || join->group_list), // bool need_order - join->select_distinct, // bool distinct - message); // message + join->save_explain_data(thd->lex->explain, + join->need_tmp, + !join->skip_sort_order && !join->no_order && + (join->order || join->group_list), + join->select_distinct, + message); } } @@ -2347,34 +2346,29 @@ void JOIN::exec() exec_inner(); - if (!exec_qpf_saved) + if (!exec_saved_explain) { if (select_lex->select_number != UINT_MAX && select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && have_query_plan != QEP_NOT_PRESENT_YET && have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then //cleanup() is called multiple times - - thd->lex->query_plan_footprint //&& // for "SET" command in SPs. - /*!thd->lex->query_plan_footprint->get_select(select_lex->select_number)*/) + thd->lex->explain)// for "SET" command in SPs. { const char *message= NULL; - if (!table_count || !tables_list || zero_result_cause) { /* It's a degenerate join */ message= zero_result_cause ? zero_result_cause : "No tables used"; } - save_qpf(thd->lex->query_plan_footprint, - need_tmp, // need_tmp_table - // !skip_sort_order && !no_order && - // (order || group_list), // bool need_order - order != 0 && !skip_sort_order, - select_distinct, // bool distinct - message); // message + save_explain_data(thd->lex->explain, + need_tmp, + order != 0 && !skip_sort_order, + select_distinct, + message); } - exec_qpf_saved= true; + exec_saved_explain= true; } DBUG_EXECUTE_IF("show_explain_probe_join_exec_end", @@ -11170,37 +11164,7 @@ void JOIN::cleanup(bool full) DBUG_PRINT("enter", ("full %u", (uint) full)); if (full) - { - /* Save it again */ -#if 0 - psergey-todo: remove? - if (select_lex->select_number != UINT_MAX && - select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && - have_query_plan != QEP_NOT_PRESENT_YET && - have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then - //cleanup() is called multiple times - - thd->lex->query_plan_footprint //&& // for "SET" command in SPs. - /*!thd->lex->query_plan_footprint->get_select(select_lex->select_number)*/) - { - const char *message= NULL; - - if (!table_count || !tables_list || zero_result_cause) - { - /* It's a degenerate join */ - message= zero_result_cause ? zero_result_cause : "No tables used"; - } - - save_qpf(thd->lex->query_plan_footprint, - need_tmp, // need_tmp_table - !skip_sort_order && !no_order && - (order || group_list), // bool need_order - select_distinct, // bool distinct - message); // message - } -#endif - have_query_plan= QEP_DELETED; //psergey: this is a problem! - } + have_query_plan= QEP_DELETED; if (table) { @@ -22522,16 +22486,17 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) Currently, this function may be called multiple times */ -int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, - bool distinct, const char *message) +int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message) { - QPF_node *qp_node; + Explain_node *qp_node; JOIN *join= this; /* Legacy: this code used to be a non-member function */ THD *thd=join->thd; const CHARSET_INFO *cs= system_charset_info; int quick_type; int error= 0; - DBUG_ENTER("JOIN::save_qpf"); + DBUG_ENTER("JOIN::save_explain_data"); DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", (ulong)join->select_lex, join->select_lex->type, message ? message : "NULL")); @@ -22540,8 +22505,8 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (message) { - QPF_select *qp_sel; - qp_node= qp_sel= new (output->mem_root) QPF_select; + Explain_select *qp_sel; + qp_node= qp_sel= new (output->mem_root) Explain_select; join->select_lex->set_explain_type(true); qp_sel->select_id= join->select_lex->select_number; @@ -22552,13 +22517,13 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, } else if (join->select_lex == join->unit->fake_select_lex) { - /* Do nothing, QPF_union will create and print fake_select_lex */ + /* Do nothing, Explain_union will create and print fake_select_lex */ } else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { - QPF_select *qp_sel; - qp_node= qp_sel= new (output->mem_root) QPF_select; + Explain_select *qp_sel; + qp_node= qp_sel= new (output->mem_root) Explain_select; table_map used_tables=0; join->select_lex->set_explain_type(true); @@ -22606,7 +22571,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, tab= pre_sort_join_tab; } - QPF_table_access *qpt= new (output->mem_root) QPF_table_access; + Explain_table_access *qpt= new (output->mem_root) Explain_table_access; qp_sel->add_table(qpt); qpt->key.set(thd->mem_root, NULL, (uint)-1); qpt->quick_info= NULL; @@ -22701,7 +22666,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, */ if (tab->select && tab->select->quick && tab_type != JT_CONST) { - qpt->quick_info= new QPF_quick_select; + qpt->quick_info= new Explain_quick_select; tab->select->quick->save_info(thd->mem_root, qpt->quick_info); } @@ -22981,7 +22946,7 @@ int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, if (tab->cache) { qpt->push_extra(ET_USING_JOIN_BUFFER); - tab->cache->save_qpf(&qpt->bka_type); + tab->cache->save_explain_data(&qpt->bka_type); } } @@ -23039,9 +23004,9 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, DBUG_ENTER("select_describe"); /* Update the QPF with latest values of using_temporary, using_filesort */ - QPF_select *qp; + Explain_select *qp; uint select_nr= join->select_lex->select_number; - if ((qp= thd->lex->query_plan_footprint->get_select(select_nr))) + if ((qp= thd->lex->explain->get_select(select_nr))) { qp->using_temporary= need_tmp_table; qp->using_filesort= need_order; @@ -23095,7 +23060,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) if (unit->is_union()) { - unit->fake_select_lex->select_number= UINT_MAX; // jost for initialization + unit->fake_select_lex->select_number= FAKE_SELECT_LEX_ID; // jost for initialization unit->fake_select_lex->type= "UNION RESULT"; unit->fake_select_lex->options|= SELECT_DESCRIBE; if (!(res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE))) diff --git a/sql/sql_select.h b/sql/sql_select.h index b3177333d12..fc2ffb3c175 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -258,7 +258,7 @@ typedef struct st_join_table { JOIN_TAB_RANGE *bush_children; /* Special content for EXPLAIN 'Extra' column or NULL if none */ - enum Extra_tag info; + enum explain_extra_tag info; /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' @@ -1336,7 +1336,7 @@ public: emb_sjm_nest= NULL; sjm_lookup_tables= 0; - exec_qpf_saved= false; + exec_saved_explain= false; /* The following is needed because JOIN::cleanup(true) may be called for joins for which JOIN::optimize was aborted with an error before a proper @@ -1344,7 +1344,13 @@ public: */ table_access_tabs= NULL; } - bool exec_qpf_saved; + + /* + TRUE <=> There was a JOIN::exec() call, which saved this JOIN's EXPLAIN. + The idea is that we also save at the end of JOIN::optimize(), but that + might not be the final plan. + */ + bool exec_saved_explain; int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, COND *conds, uint og_num, ORDER *order, ORDER *group, @@ -1470,8 +1476,8 @@ public: { return (unit->item && unit->item->is_in_predicate()); } - int save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, - bool distinct, const char *message); + int save_explain_data(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, const char *message); private: /** TRUE if the query contains an aggregate function but has no GROUP diff --git a/sql/sql_union.cc b/sql/sql_union.cc index a6e935f5cfd..72ba3853110 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -630,8 +630,8 @@ bool st_select_lex_unit::exec() saved_error= optimize(); - if (!was_executed && thd->lex->query_plan_footprint) - save_union_qpf(thd->lex->query_plan_footprint); + if (!was_executed && thd->lex->explain) + save_union_explain(thd->lex->explain); if (uncacheable || !item || !item->assigned() || describe) { @@ -780,8 +780,8 @@ bool st_select_lex_unit::exec() if (!fake_select_lex->ref_pointer_array) fake_select_lex->n_child_sum_items+= global_parameters->n_sum_items; - if (!was_executed && thd->lex->query_plan_footprint) - save_union_qpf_part2(thd->lex->query_plan_footprint); + if (!was_executed && thd->lex->explain) + save_union_explain_part2(thd->lex->explain); saved_error= mysql_select(thd, &fake_select_lex->ref_pointer_array, &result_table_list, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 2881f6a7a1d..cf6cc46e11c 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -500,7 +500,7 @@ int mysql_update(THD *thd, if (thd->lex->describe) goto exit_without_my_ok; - query_plan.save_qpf(thd->lex->query_plan_footprint); + query_plan.save_explain_data(thd->lex->explain); thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", @@ -1031,7 +1031,7 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); - query_plan.save_qpf(thd->lex->query_plan_footprint); + query_plan.save_explain_data(thd->lex->explain); select_send *result; if (!(result= new select_send())) @@ -1039,7 +1039,7 @@ exit_without_my_ok: List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ result->prepare(dummy, &thd->lex->unit); thd->send_explain_fields(result); - int err2= thd->lex->query_plan_footprint->print_explain(result, + int err2= thd->lex->explain->print_explain(result, thd->lex->describe); if (err2) result->abort_result_set(); @@ -1518,7 +1518,7 @@ bool mysql_multi_update(THD *thd, { if (explain) { - thd->lex->query_plan_footprint->print_explain(output, thd->lex->describe); + thd->lex->explain->print_explain(output, thd->lex->describe); output->send_eof(); delete output; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d5aee86fea3..d1ac0fec34a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1661,6 +1661,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); definer_opt no_definer definer parse_vcol_expr vcol_opt_specifier vcol_opt_attribute vcol_opt_attribute_list vcol_attribute + explainable_command END_OF_INPUT %type call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt @@ -12029,14 +12030,14 @@ describe: } | describe_command opt_extended_describe { Lex->describe|= DESCRIBE_NORMAL; } - explanable_command + explainable_command { LEX *lex=Lex; lex->select_lex.options|= SELECT_DESCRIBE; } ; -explanable_command: +explainable_command: select | insert | replace From 6a7f8af3f9ca95163cba3c7efdf04944378fb8d3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 5 Oct 2013 10:25:59 +0400 Subject: [PATCH 42/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Address review feedback: rename files --- libmysqld/CMakeLists.txt | 2 +- sql/CMakeLists.txt | 2 +- sql/{opt_qpf.cc => sql_explain.cc} | 0 sql/{opt_qpf.h => sql_explain.h} | 0 sql/sql_lex.cc | 10 ++++---- sql/sql_select.cc | 41 ++++++++++++------------------ sql/sql_select.h | 2 +- 7 files changed, 24 insertions(+), 33 deletions(-) rename sql/{opt_qpf.cc => sql_explain.cc} (100%) rename sql/{opt_qpf.h => sql_explain.h} (100%) diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 856063afd95..87004897525 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -99,7 +99,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_expression_cache.cc ../sql/my_apc.cc ../sql/my_apc.h ../sql/rpl_gtid.cc - ../sql/opt_qpf.cc ../sql/opt_qpf.h + ../sql/sql_explain.cc ../sql/sql_explain.h ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 33f3816c297..9db6ba16c30 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -80,7 +80,7 @@ SET (SQL_SOURCE sql_reload.cc # added in MariaDB: - opt_qpf.h opt_qpf.cc + sql_explain.h sql_explain.cc sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc create_options.cc multi_range_read.cc opt_index_cond_pushdown.cc opt_subselect.cc diff --git a/sql/opt_qpf.cc b/sql/sql_explain.cc similarity index 100% rename from sql/opt_qpf.cc rename to sql/sql_explain.cc diff --git a/sql/opt_qpf.h b/sql/sql_explain.h similarity index 100% rename from sql/opt_qpf.h rename to sql/sql_explain.h diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 48c65e68f87..fd8d07f90aa 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3516,14 +3516,14 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only) inner_join->select_options= save_options; un->thd->lex->current_select= save_select; - Explain_query *qpf; - if ((qpf= inner_join->thd->lex->explain)) + Explain_query *eq; + if ((eq= inner_join->thd->lex->explain)) { - Explain_select *qp_sel; - if ((qp_sel= qpf->get_select(inner_join->select_lex->select_number))) + Explain_select *expl_sel; + if ((expl_sel= eq->get_select(inner_join->select_lex->select_number))) { sl->set_explain_type(TRUE); - qp_sel->select_type= sl->type; + expl_sel->select_type= sl->type; } } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e7557b92ea4..c87470b5e37 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2328,14 +2328,6 @@ void join_save_qpf(JOIN *join) void JOIN::exec() { - /* - Enable SHOW EXPLAIN only if we're in the top-level query. - */ - - /* - psergey: we can produce SHOW explain at this point. This means, we're ready - to save the query plan. - */ thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_join_exec_start", if (dbug_user_var_equals_int(thd, @@ -2343,7 +2335,6 @@ void JOIN::exec() select_lex->select_number)) dbug_serve_apcs(thd, 1); ); - exec_inner(); if (!exec_saved_explain) @@ -22505,15 +22496,15 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (message) { - Explain_select *qp_sel; - qp_node= qp_sel= new (output->mem_root) Explain_select; + Explain_select *xpl_sel; + qp_node= xpl_sel= new (output->mem_root) Explain_select; join->select_lex->set_explain_type(true); - qp_sel->select_id= join->select_lex->select_number; - qp_sel->select_type= join->select_lex->type; - qp_sel->message= message; - /* Setting qp_sel->message means that all other members are invalid */ - output->add_node(qp_sel); + xpl_sel->select_id= join->select_lex->select_number; + xpl_sel->select_type= join->select_lex->type; + xpl_sel->message= message; + /* Setting xpl_sel->message means that all other members are invalid */ + output->add_node(xpl_sel); } else if (join->select_lex == join->unit->fake_select_lex) { @@ -22522,13 +22513,13 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { - Explain_select *qp_sel; - qp_node= qp_sel= new (output->mem_root) Explain_select; + Explain_select *xpl_sel; + qp_node= xpl_sel= new (output->mem_root) Explain_select; table_map used_tables=0; join->select_lex->set_explain_type(true); - qp_sel->select_id= join->select_lex->select_number; - qp_sel->select_type= join->select_lex->type; + xpl_sel->select_id= join->select_lex->select_number; + xpl_sel->select_type= join->select_lex->type; JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); @@ -22572,7 +22563,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, } Explain_table_access *qpt= new (output->mem_root) Explain_table_access; - qp_sel->add_table(qpt); + xpl_sel->add_table(qpt); qpt->key.set(thd->mem_root, NULL, (uint)-1); qpt->quick_info= NULL; @@ -22583,7 +22574,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, qpt->sjm_nest_select_id= 0; /* select_type */ - qp_sel->select_type= join->select_lex->type; + xpl_sel->select_type= join->select_lex->type; /* table */ if (table->derived_select_number) @@ -22893,12 +22884,12 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (need_tmp_table) { need_tmp_table=0; - qp_sel->using_temporary= true; + xpl_sel->using_temporary= true; } if (need_order) { need_order=0; - qp_sel->using_filesort= true; + xpl_sel->using_filesort= true; } if (distinct & test_all_bits(used_tables, join->select_list_used_tables)) @@ -22956,7 +22947,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, // For next iteration used_tables|=table->map; } - output->add_node(qp_sel); + output->add_node(xpl_sel); } for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); diff --git a/sql/sql_select.h b/sql/sql_select.h index fc2ffb3c175..d6e133262f1 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -198,7 +198,7 @@ int rr_sequential(READ_RECORD *info); int rr_sequential_and_unpack(READ_RECORD *info); -#include "opt_qpf.h" +#include "sql_explain.h" /************************************************************************************** * New EXPLAIN structures END From 72bc6d7364f7cf6cae49c74cf1e56832053f91eb Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 5 Oct 2013 13:19:45 +0400 Subject: [PATCH 43/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Address review feedback: more renames --- sql/opt_range.cc | 43 +++++++------ sql/opt_range.h | 14 ++-- sql/sql_delete.cc | 64 +++++++++--------- sql/sql_explain.cc | 15 +++-- sql/sql_explain.h | 12 ++-- sql/sql_join_cache.cc | 24 +++---- sql/sql_join_cache.h | 6 +- sql/sql_lex.cc | 12 ++-- sql/sql_lex.h | 4 +- sql/sql_select.cc | 146 +++++++++++++++++++++--------------------- 10 files changed, 173 insertions(+), 167 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 2e32009bc54..cee72be7bda 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11940,25 +11940,26 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, Explain_quick_select *qpf) +void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, + Explain_quick_select *explain) { - qpf->quick_type= QS_TYPE_RANGE; - qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); + explain->quick_type= QS_TYPE_RANGE; + explain->range.set(alloc, head->key_info[index].name, max_used_key_length); } void QUICK_GROUP_MIN_MAX_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *qpf) + Explain_quick_select *explain) { - qpf->quick_type= QS_TYPE_GROUP_MIN_MAX; - qpf->range.set(alloc, head->key_info[index].name, max_used_key_length); + explain->quick_type= QS_TYPE_GROUP_MIN_MAX; + explain->range.set(alloc, head->key_info[index].name, max_used_key_length); } void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *qpf) + Explain_quick_select *explain) { - qpf->quick_type= get_type(); + explain->quick_type= get_type(); QUICK_RANGE_SELECT *quick; Explain_quick_select *child_qpf; @@ -11966,14 +11967,14 @@ void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, while ((quick= it++)) { child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } if (pk_quick_select) { child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); pk_quick_select->save_info(alloc, child_qpf); } } @@ -11983,15 +11984,15 @@ void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, first */ void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *qpf) + Explain_quick_select *explain) { - qpf->quick_type= get_type(); + explain->quick_type= get_type(); Explain_quick_select *child_qpf; if (pk_quick_select) { child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); pk_quick_select->save_info(alloc, child_qpf); } @@ -12000,7 +12001,7 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, while ((quick= it++)) { child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } @@ -12008,39 +12009,39 @@ void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *qpf) + Explain_quick_select *explain) { - qpf->quick_type= get_type(); + explain->quick_type= get_type(); QUICK_SELECT_WITH_RECORD *qr; List_iterator_fast it(quick_selects); while ((qr= it++)) { Explain_quick_select *child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); qr->quick->save_info(alloc, child_qpf); } if (cpk_quick) { Explain_quick_select *child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); cpk_quick->save_info(alloc, child_qpf); } } void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *qpf) + Explain_quick_select *explain) { - qpf->quick_type= get_type(); + explain->quick_type= get_type(); QUICK_SELECT_I *quick; List_iterator_fast it(quick_selects); while ((quick= it++)) { Explain_quick_select *child_qpf= new Explain_quick_select; - qpf->children.push_back(child_qpf); + explain->children.push_back(child_qpf); quick->save_info(alloc, child_qpf); } } diff --git a/sql/opt_range.h b/sql/opt_range.h index 9bc19927fd0..23274466990 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -346,7 +346,7 @@ public: void add_key_name(String *str, bool *first); /* Save information about quick select's query plan */ - virtual void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf)= 0; + virtual void save_info(MEM_ROOT *alloc, Explain_quick_select *explain)= 0; /* Return 1 if any index used by this quick select @@ -473,7 +473,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -610,7 +610,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -674,7 +674,7 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); }; @@ -712,7 +712,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -791,7 +791,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -940,7 +940,7 @@ public: #endif bool is_agg_distinct() { return have_agg_distinct; } bool loose_scan_is_scanning() { return is_index_scan; } - void save_info(MEM_ROOT *alloc, Explain_quick_select *qpf); + void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); }; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 58deae4162a..518bdc6ab43 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -53,59 +53,60 @@ void Delete_plan::save_explain_data(Explain_query *query) { - QPF_delete* qpf= new QPF_delete; + Explain_delete* explain= new Explain_delete; if (deleting_all_rows) { - qpf->deleting_all_rows= true; - qpf->select_type= "SIMPLE"; + explain->deleting_all_rows= true; + explain->select_type= "SIMPLE"; } else { - qpf->deleting_all_rows= false; - Update_plan::save_explain_data_intern(query, qpf); + explain->deleting_all_rows= false; + Update_plan::save_explain_data_intern(query, explain); } - query->upd_del_plan= qpf; + query->upd_del_plan= explain; } void Update_plan::save_explain_data(Explain_query *query) { - QPF_update* qpf= new QPF_update; - save_explain_data_intern(query, qpf); - query->upd_del_plan= qpf; + Explain_update* explain= new Explain_update; + save_explain_data_intern(query, explain); + query->upd_del_plan= explain; } -void Update_plan::save_explain_data_intern(Explain_query *query, QPF_update *qpf) +void Update_plan::save_explain_data_intern(Explain_query *query, + Explain_update *explain) { - qpf->select_type= "SIMPLE"; - qpf->table_name.append(table->pos_in_table_list->alias); + explain->select_type= "SIMPLE"; + explain->table_name.append(table->pos_in_table_list->alias); if (impossible_where) { - qpf->impossible_where= true; + explain->impossible_where= true; return; } - qpf->impossible_where= false; + explain->impossible_where= false; select_lex->set_explain_type(TRUE); - qpf->select_type= select_lex->type; + explain->select_type= select_lex->type; /* Partitions */ { #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *part_info; if ((part_info= table->part_info)) { - make_used_partitions_str(part_info, &qpf->used_partitions); - qpf->used_partitions_set= true; + make_used_partitions_str(part_info, &explain->used_partitions); + explain->used_partitions_set= true; } else - qpf->used_partitions_set= false; + explain->used_partitions_set= false; #else /* just produce empty column if partitioning is not compiled in */ - qpf->used_partitions_set= false; + explain->used_partitions_set= false; #endif } @@ -118,42 +119,43 @@ void Update_plan::save_explain_data_intern(Explain_query *query, QPF_update *qpf (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) - qpf->jtype= JT_INDEX_MERGE; + explain->jtype= JT_INDEX_MERGE; else - qpf->jtype= JT_RANGE; + explain->jtype= JT_RANGE; } else { if (index == MAX_KEY) - qpf->jtype= JT_ALL; + explain->jtype= JT_ALL; else - qpf->jtype= JT_NEXT; + explain->jtype= JT_NEXT; } - qpf->using_where= test(select && select->cond); - qpf->using_filesort= using_filesort; + explain->using_where= test(select && select->cond); + explain->using_filesort= using_filesort; - make_possible_keys_line(table, possible_keys, &qpf->possible_keys_line); + make_possible_keys_line(table, possible_keys, &explain->possible_keys_line); /* Calculate key_len */ if (select && select->quick) { - select->quick->add_keys_and_lengths(&qpf->key_str, &qpf->key_len_str); + select->quick->add_keys_and_lengths(&explain->key_str, &explain->key_len_str); } else { if (index != MAX_KEY) { - qpf->key_str.append(table->key_info[index].name); + explain->key_str.append(table->key_info[index].name); } // key_len stays NULL } - qpf->rows= select ? select->records : table_rows; + explain->rows= select ? select->records : table_rows; if (select && select->quick && select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) { - explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &qpf->mrr_type); + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, + &explain->mrr_type); } bool skip= updating_a_view; @@ -173,7 +175,7 @@ void Update_plan::save_explain_data_intern(Explain_query *query, QPF_update *qpf clauses. */ if (!(unit->item && unit->item->eliminated)) - qpf->add_child(unit->first_select()->select_number); + explain->add_child(unit->first_select()->select_number); } } diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 248dc2b12be..0623ef0be8e 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -511,7 +511,8 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai /* - Elements in this array match members of enum Extra_tag, defined in opt_qpf.h. + Elements in this array match members of enum Extra_tag, defined in + sql_explain.h */ const char * extra_tag_text[]= @@ -740,8 +741,9 @@ void Explain_quick_select::print_key_len(String *str) } -int QPF_delete::print_explain(Explain_query *query, select_result_sink *output, - uint8 explain_flags) +int Explain_delete::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) { if (deleting_all_rows) { @@ -754,13 +756,14 @@ int QPF_delete::print_explain(Explain_query *query, select_result_sink *output, } else { - return QPF_update::print_explain(query, output, explain_flags); + return Explain_update::print_explain(query, output, explain_flags); } } -int QPF_update::print_explain(Explain_query *query, select_result_sink *output, - uint8 explain_flags) +int Explain_update::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags) { StringBuffer<64> extra_str; if (impossible_where) diff --git a/sql/sql_explain.h b/sql/sql_explain.h index ef36cb4a44d..66e8b1be109 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -172,7 +172,7 @@ public: bool using_filesort; }; -class QPF_delete; +class Explain_delete; /* @@ -226,8 +226,8 @@ public: Explain_union *get_union(uint select_id); - /* QPF_delete inherits from QPF_update */ - QPF_update *upd_del_plan; + /* Explain_delete inherits from Explain_update */ + Explain_update *upd_del_plan; /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); @@ -448,7 +448,7 @@ private: Also, it can have UPDATE operation options, but currently there aren't any. */ -class QPF_update : public Explain_node +class Explain_update : public Explain_node { public: virtual enum explain_node_type get_type() { return EXPLAIN_UPDATE; } @@ -479,10 +479,10 @@ public: /* - Query Plan Footprint for a single-table DELETE. + Explain data of a single-table DELETE. */ -class QPF_delete: public QPF_update +class Explain_delete: public Explain_update { public: /* diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 040e596d283..8b505b75272 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2568,22 +2568,22 @@ finish: none */ -void JOIN_CACHE::save_explain_data(struct st_explain_bka_type *qpf) +void JOIN_CACHE::save_explain_data(struct st_explain_bka_type *explain) { - qpf->incremental= test(prev_cache); + explain->incremental= test(prev_cache); switch (get_join_alg()) { case BNL_JOIN_ALG: - qpf->join_alg= "BNL"; + explain->join_alg= "BNL"; break; case BNLH_JOIN_ALG: - qpf->join_alg= "BNLH"; + explain->join_alg= "BNLH"; break; case BKA_JOIN_ALG: - qpf->join_alg= "BKA"; + explain->join_alg= "BKA"; break; case BKAH_JOIN_ALG: - qpf->join_alg= "BKAH"; + explain->join_alg= "BKAH"; break; default: DBUG_ASSERT(0); @@ -2613,17 +2613,17 @@ static void add_mrr_explain_info(String *str, uint mrr_mode, handler *file) } } -void JOIN_CACHE_BKA::save_explain_data(struct st_explain_bka_type *qpf) +void JOIN_CACHE_BKA::save_explain_data(struct st_explain_bka_type *explain) { - JOIN_CACHE::save_explain_data(qpf); - add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_explain_data(explain); + add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file); } -void JOIN_CACHE_BKAH::save_explain_data(struct st_explain_bka_type *qpf) +void JOIN_CACHE_BKAH::save_explain_data(struct st_explain_bka_type *explain) { - JOIN_CACHE::save_explain_data(qpf); - add_mrr_explain_info(&qpf->mrr_type, mrr_mode, join_tab->table->file); + JOIN_CACHE::save_explain_data(explain); + add_mrr_explain_info(&explain->mrr_type, mrr_mode, join_tab->table->file); } diff --git a/sql/sql_join_cache.h b/sql/sql_join_cache.h index 0b03f2b8bab..9dae7fb24e0 100644 --- a/sql/sql_join_cache.h +++ b/sql/sql_join_cache.h @@ -658,7 +658,7 @@ public: enum_nested_loop_state join_records(bool skip_last); /* Add a comment on the join algorithm employed by the join cache */ - virtual void save_explain_data(struct st_explain_bka_type *qpf); + virtual void save_explain_data(struct st_explain_bka_type *explain); THD *thd(); @@ -1336,7 +1336,7 @@ public: /* Check index condition of the joined table for a record from BKA cache */ bool skip_index_tuple(range_id_t range_info); - void save_explain_data(struct st_explain_bka_type *qpf); + void save_explain_data(struct st_explain_bka_type *explain); }; @@ -1427,5 +1427,5 @@ public: /* Check index condition of the joined table for a record from BKAH cache */ bool skip_index_tuple(range_id_t range_info); - void save_explain_data(struct st_explain_bka_type *qpf); + void save_explain_data(struct st_explain_bka_type *explain); }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index fd8d07f90aa..bd31200e749 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4264,12 +4264,12 @@ int st_select_lex_unit::save_union_explain(Explain_query *output) const char *msg="Query plan already deleted"; first->set_explain_type(TRUE/* on_the_fly */); - Explain_select *qp_sel= new (output->mem_root)Explain_select; - qp_sel->select_id= first->select_number; - qp_sel->select_type= first->type; - qp_sel->message= msg; - output->add_node(qp_sel); - eu->add_select(qp_sel->select_id); + Explain_select *explain= new (output->mem_root)Explain_select; + explain->select_id= first->select_number; + explain->select_type= first->type; + explain->message= msg; + output->add_node(explain); + eu->add_select(explain->select_id); return 0; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 8bff5c640df..62d44f622ae 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2369,7 +2369,7 @@ class Delete_plan; class SQL_SELECT; class Explain_query; -class QPF_update; +class Explain_update; /* Query plan of a single-table UPDATE. @@ -2399,7 +2399,7 @@ public: void set_impossible_where() { impossible_where= true; } void save_explain_data(Explain_query *query); - void save_explain_data_intern(Explain_query *query, QPF_update *qpf); + void save_explain_data_intern(Explain_query *query, Explain_update *eu); virtual ~Update_plan() {} Update_plan() : impossible_where(false), using_filesort(false) {} diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c87470b5e37..c787d659e36 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22481,7 +22481,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, bool need_order, bool distinct, const char *message) { - Explain_node *qp_node; + Explain_node *explain_node; JOIN *join= this; /* Legacy: this code used to be a non-member function */ THD *thd=join->thd; const CHARSET_INFO *cs= system_charset_info; @@ -22497,7 +22497,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (message) { Explain_select *xpl_sel; - qp_node= xpl_sel= new (output->mem_root) Explain_select; + explain_node= xpl_sel= new (output->mem_root) Explain_select; join->select_lex->set_explain_type(true); xpl_sel->select_id= join->select_lex->select_number; @@ -22514,7 +22514,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, join->select_lex->master_unit()->derived->is_materialized_derived()) { Explain_select *xpl_sel; - qp_node= xpl_sel= new (output->mem_root) Explain_select; + explain_node= xpl_sel= new (output->mem_root) Explain_select; table_map used_tables=0; join->select_lex->set_explain_type(true); @@ -22562,16 +22562,16 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, tab= pre_sort_join_tab; } - Explain_table_access *qpt= new (output->mem_root) Explain_table_access; - xpl_sel->add_table(qpt); - qpt->key.set(thd->mem_root, NULL, (uint)-1); - qpt->quick_info= NULL; + Explain_table_access *eta= new (output->mem_root) Explain_table_access; + xpl_sel->add_table(eta); + eta->key.set(thd->mem_root, NULL, (uint)-1); + eta->quick_info= NULL; /* id */ if (tab->bush_root_tab) - qpt->sjm_nest_select_id= select_id; + eta->sjm_nest_select_id= select_id; else - qpt->sjm_nest_select_id= 0; + eta->sjm_nest_select_id= 0; /* select_type */ xpl_sel->select_type= join->select_lex->type; @@ -22583,7 +22583,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, "", table->derived_select_number); - qpt->table_name.copy(table_name_buffer, len, cs); + eta->table_name.copy(table_name_buffer, len, cs); } else if (tab->bush_children) { @@ -22593,12 +22593,12 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, sizeof(table_name_buffer)-1, "", ctab->emb_sj_nest->sj_subq_pred->get_identifier()); - qpt->table_name.copy(table_name_buffer, len, cs); + eta->table_name.copy(table_name_buffer, len, cs); } else { TABLE_LIST *real_table= table->pos_in_table_list; - qpt->table_name.copy(real_table->alias, strlen(real_table->alias), cs); + eta->table_name.copy(real_table->alias, strlen(real_table->alias), cs); } /* "partitions" column */ @@ -22608,14 +22608,14 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (!table->derived_select_number && (part_info= table->part_info)) { - make_used_partitions_str(part_info, &qpt->used_partitions); - qpt->used_partitions_set= true; + make_used_partitions_str(part_info, &eta->used_partitions); + eta->used_partitions_set= true; } else - qpt->used_partitions_set= false; + eta->used_partitions_set= false; #else /* just produce empty column if partitioning is not compiled in */ - qpt->used_partitions_set= false; + eta->used_partitions_set= false; #endif } @@ -22634,10 +22634,10 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, else tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; } - qpt->type= tab_type; + eta->type= tab_type; /* Build "possible_keys" value */ - append_possible_keys(&qpt->possible_keys_str, table, tab->keys); + append_possible_keys(&eta->possible_keys_str, table, tab->keys); /* Build "key", "key_len", and "ref" */ if (tab_type == JT_NEXT) @@ -22657,13 +22657,13 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, */ if (tab->select && tab->select->quick && tab_type != JT_CONST) { - qpt->quick_info= new Explain_quick_select; - tab->select->quick->save_info(thd->mem_root, qpt->quick_info); + eta->quick_info= new Explain_quick_select; + tab->select->quick->save_info(thd->mem_root, eta->quick_info); } if (key_info) /* 'index' or 'ref' access */ { - qpt->key.set(thd->mem_root, key_info->name, key_len); + eta->key.set(thd->mem_root, key_info->name, key_len); if (tab->ref.key_parts && tab_type != JT_FT) { @@ -22686,7 +22686,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { - qpt->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].key_length); } @@ -22695,11 +22695,11 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, { if (key_info && tab_type != JT_NEXT) { - qpt->ref.copy(tmp4); - qpt->ref_set= true; + eta->ref.copy(tmp4); + eta->ref_set= true; } else - qpt->ref_set= false; + eta->ref_set= false; } else { @@ -22728,9 +22728,9 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, } if (key_name_buf.length()) - qpt->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); + eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); } - qpt->ref_set= false; + eta->ref_set= false; } /* "rows" */ @@ -22738,15 +22738,15 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, table_list->schema_table) { /* I_S tables have rows=extra=NULL */ - qpt->rows_set= false; - qpt->filtered_set= false; + eta->rows_set= false; + eta->filtered_set= false; } else { ha_rows examined_rows= tab->get_examined_rows(); - qpt->rows_set= true; - qpt->rows= examined_rows; + eta->rows_set= true; + eta->rows= examined_rows; /* "filtered" */ float f= 0.0; @@ -22759,8 +22759,8 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, f= (float) (100.0 * pushdown_cond_selectivity); } set_if_smaller(f, 100.0); - qpt->filtered_set= true; - qpt->filtered= f; + eta->filtered_set= true; + eta->filtered= f; } /* Build "Extra" field and save it */ @@ -22774,16 +22774,16 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (tab->info) { - qpt->push_extra(tab->info); + eta->push_extra(tab->info); } else if (tab->packed_info & TAB_INFO_HAVE_VALUE) { if (tab->packed_info & TAB_INFO_USING_INDEX) - qpt->push_extra(ET_USING_INDEX); + eta->push_extra(ET_USING_INDEX); if (tab->packed_info & TAB_INFO_USING_WHERE) - qpt->push_extra(ET_USING_WHERE); + eta->push_extra(ET_USING_WHERE); if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL) - qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); } else { @@ -22795,23 +22795,23 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && table->file->pushed_idx_cond) - qpt->push_extra(ET_USING_INDEX_CONDITION); + eta->push_extra(ET_USING_INDEX_CONDITION); else if (tab->cache_idx_cond) - qpt->push_extra(ET_USING_INDEX_CONDITION_BKA); + eta->push_extra(ET_USING_INDEX_CONDITION_BKA); if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) { - qpt->push_extra(ET_USING); + eta->push_extra(ET_USING); } if (tab->select) { if (tab->use_quick == 2) { - qpt->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); - qpt->range_checked_map= tab->keys; + eta->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); + eta->range_checked_map= tab->keys; } else if (tab->select->cond) { @@ -22823,7 +22823,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && pushed_cond) { - qpt->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); + eta->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); /* psergey-todo: what to do? This was useful with NDB only. @@ -22835,7 +22835,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, */ } else - qpt->push_extra(ET_USING_WHERE); + eta->push_extra(ET_USING_WHERE); } } if (table_list /* SJM bushes don't have table_list */ && @@ -22843,20 +22843,20 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) { if (!table_list->table_open_method) - qpt->push_extra(ET_SKIP_OPEN_TABLE); + eta->push_extra(ET_SKIP_OPEN_TABLE); else if (table_list->table_open_method == OPEN_FRM_ONLY) - qpt->push_extra(ET_OPEN_FRM_ONLY); + eta->push_extra(ET_OPEN_FRM_ONLY); else - qpt->push_extra(ET_OPEN_FULL_TABLE); + eta->push_extra(ET_OPEN_FULL_TABLE); /* psergey-note: the following has a bug.*/ if (table_list->has_db_lookup_value && table_list->has_table_lookup_value) - qpt->push_extra(ET_SCANNED_0_DATABASES); + eta->push_extra(ET_SCANNED_0_DATABASES); else if (table_list->has_db_lookup_value || table_list->has_table_lookup_value) - qpt->push_extra(ET_SCANNED_1_DATABASE); + eta->push_extra(ET_SCANNED_1_DATABASE); else - qpt->push_extra(ET_SCANNED_ALL_DATABASES); + eta->push_extra(ET_SCANNED_ALL_DATABASES); } if (key_read) { @@ -22864,21 +22864,21 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, { QUICK_GROUP_MIN_MAX_SELECT *qgs= (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; - qpt->push_extra(ET_USING_INDEX_FOR_GROUP_BY); - qpt->loose_scan_is_scanning= qgs->loose_scan_is_scanning(); + eta->push_extra(ET_USING_INDEX_FOR_GROUP_BY); + eta->loose_scan_is_scanning= qgs->loose_scan_is_scanning(); } else - qpt->push_extra(ET_USING_INDEX); + eta->push_extra(ET_USING_INDEX); } if (table->reginfo.not_exists_optimize) - qpt->push_extra(ET_NOT_EXISTS); + eta->push_extra(ET_NOT_EXISTS); if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) { explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), - &qpt->mrr_type); - if (qpt->mrr_type.length() > 0) - qpt->push_extra(ET_USING_MRR); + &eta->mrr_type); + if (eta->mrr_type.length() > 0) + eta->push_extra(ET_USING_MRR); } if (need_tmp_table) @@ -22893,23 +22893,23 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, } if (distinct & test_all_bits(used_tables, join->select_list_used_tables)) - qpt->push_extra(ET_DISTINCT); + eta->push_extra(ET_DISTINCT); if (tab->loosescan_match_tab) { - qpt->push_extra(ET_LOOSESCAN); + eta->push_extra(ET_LOOSESCAN); } if (tab->first_weedout_table) - qpt->push_extra(ET_START_TEMPORARY); + eta->push_extra(ET_START_TEMPORARY); if (tab->check_weed_out_table) - qpt->push_extra(ET_END_TEMPORARY); + eta->push_extra(ET_END_TEMPORARY); else if (tab->do_firstmatch) { if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) - qpt->push_extra(ET_FIRST_MATCH); + eta->push_extra(ET_FIRST_MATCH); else { - qpt->push_extra(ET_FIRST_MATCH); + eta->push_extra(ET_FIRST_MATCH); TABLE *prev_table=tab->do_firstmatch->table; if (prev_table->derived_select_number) { @@ -22918,10 +22918,10 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, int len= my_snprintf(namebuf, sizeof(namebuf)-1, "", prev_table->derived_select_number); - qpt->firstmatch_table_name.append(namebuf, len); + eta->firstmatch_table_name.append(namebuf, len); } else - qpt->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); + eta->firstmatch_table_name.append(prev_table->pos_in_table_list->alias); } } @@ -22929,15 +22929,15 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, { if (tab->ref.cond_guards[part]) { - qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + eta->push_extra(ET_FULL_SCAN_ON_NULL_KEY); break; } } if (tab->cache) { - qpt->push_extra(ET_USING_JOIN_BUFFER); - tab->cache->save_explain_data(&qpt->bka_type); + eta->push_extra(ET_USING_JOIN_BUFFER); + tab->cache->save_explain_data(&eta->bka_type); } } @@ -22963,7 +22963,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, if (!(unit->item && unit->item->eliminated) && // (1) (!unit->derived || unit->derived->is_materialized_derived())) // (2) { - qp_node->add_child(unit->first_select()->select_number); + explain_node->add_child(unit->first_select()->select_number); } } @@ -22995,12 +22995,12 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, DBUG_ENTER("select_describe"); /* Update the QPF with latest values of using_temporary, using_filesort */ - Explain_select *qp; + Explain_select *explain_sel; uint select_nr= join->select_lex->select_number; - if ((qp= thd->lex->explain->get_select(select_nr))) + if ((explain_sel= thd->lex->explain->get_select(select_nr))) { - qp->using_temporary= need_tmp_table; - qp->using_filesort= need_order; + explain_sel->using_temporary= need_tmp_table; + explain_sel->using_filesort= need_order; } for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); From abcf14e595d35ebacb1d23cd4a6ced20080f8d4b Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 5 Oct 2013 13:44:01 +0400 Subject: [PATCH 44/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Handle the case when EXPLAIN UPDATE/DELETE has pruned away all partitions. --- mysql-test/r/explain_non_select.result | 20 ++++++++++++++++++++ mysql-test/t/explain_non_select.test | 19 +++++++++++++++++++ sql/sql_delete.cc | 22 +++++++++++++++++----- sql/sql_explain.cc | 6 ++++-- sql/sql_explain.h | 1 + sql/sql_lex.h | 9 +++++++-- sql/sql_update.cc | 5 +++++ 7 files changed, 73 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 36e8197febe..3cc4d4f1e26 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -161,3 +161,23 @@ explain extended delete from t2 where a in (3,4); id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t2 ALL NULL NULL NULL NULL 2 100.00 Using where drop table t1,t2; +# +# Check the special case where partition pruning removed all partitions +# +create table t1 (a int, b int) +partition by range (a) ( +partition p0 values less than (10), +partition p1 values less than (20), +partition p2 values less than (30) +); +insert into t1 values (9,9), (19,19), (29,29); +explain partitions select * from t1 where a in (32,33); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +explain partitions delete from t1 where a in (32,33); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No matching rows after partition pruning +explain partitions update t1 set b=12345 where a in (32,33); +id select_type table partitions type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No matching rows after partition pruning +drop table t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 4da4b894d69..3c4c2d0739d 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -136,3 +136,22 @@ explain extended delete from t2 where a in (3,4); drop table t1,t2; +--echo # +--echo # Check the special case where partition pruning removed all partitions +--echo # + +create table t1 (a int, b int) +partition by range (a) ( + partition p0 values less than (10), + partition p1 values less than (20), + partition p2 values less than (30) +); +insert into t1 values (9,9), (19,19), (29,29); + +explain partitions select * from t1 where a in (32,33); + +explain partitions delete from t1 where a in (32,33); + +explain partitions update t1 set b=12345 where a in (32,33); + +drop table t1; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 518bdc6ab43..a84d6795603 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -83,14 +83,22 @@ void Update_plan::save_explain_data_intern(Explain_query *query, { explain->select_type= "SIMPLE"; explain->table_name.append(table->pos_in_table_list->alias); + + explain->impossible_where= false; + explain->no_partitions= false; + if (impossible_where) { explain->impossible_where= true; return; } - - explain->impossible_where= false; + if (no_partitions) + { + explain->no_partitions= true; + return; + } + select_lex->set_explain_type(TRUE); explain->select_type= select_lex->type; /* Partitions */ @@ -139,7 +147,8 @@ void Update_plan::save_explain_data_intern(Explain_query *query, /* Calculate key_len */ if (select && select->quick) { - select->quick->add_keys_and_lengths(&explain->key_str, &explain->key_len_str); + select->quick->add_keys_and_lengths(&explain->key_str, + &explain->key_len_str); } else { @@ -356,8 +365,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (prune_partitions(thd, table, conds)) { free_underlaid_joins(thd, select_lex); - // No matching record - //psergey-explain-todo: No-partitions used EXPLAIN here.. + + query_plan.set_no_partitions(); + if (thd->lex->describe) + goto exit_without_my_ok; + my_ok(thd, 0); DBUG_RETURN(0); } diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 0623ef0be8e..28774ef2e99 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -766,9 +766,11 @@ int Explain_update::print_explain(Explain_query *query, uint8 explain_flags) { StringBuffer<64> extra_str; - if (impossible_where) + if (impossible_where || no_partitions) { - const char *msg= "Impossible where"; + const char *msg= impossible_where ? + "Impossible where" : + "No matching rows after partition pruning"; int res= print_explain_message_line(output, explain_flags, 1 /*select number*/, select_type, msg); diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 66e8b1be109..87ae29678e9 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -460,6 +460,7 @@ public: bool used_partitions_set; bool impossible_where; + bool no_partitions; StringBuffer<64> table_name; enum join_type jtype; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 62d44f622ae..99a5b2d8ae3 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2380,8 +2380,10 @@ class Update_plan { protected: bool impossible_where; + bool no_partitions; public: bool updating_a_view; + TABLE *table; SQL_SELECT *select; uint index; @@ -2395,14 +2397,17 @@ public: key_map possible_keys; bool using_filesort; - /* Set this plan to be a plan to do nothing because of impossible WHRE*/ + /* Set this plan to be a plan to do nothing because of impossible WHERE */ void set_impossible_where() { impossible_where= true; } + void set_no_partitions() { no_partitions= true; } void save_explain_data(Explain_query *query); void save_explain_data_intern(Explain_query *query, Explain_update *eu); virtual ~Update_plan() {} - Update_plan() : impossible_where(false), using_filesort(false) {} + Update_plan() : + impossible_where(false), no_partitions(false), using_filesort(false) + {} }; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index cf6cc46e11c..81e0f6c0ebe 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -403,6 +403,11 @@ int mysql_update(THD *thd, if (prune_partitions(thd, table, conds)) { free_underlaid_joins(thd, select_lex); + + query_plan.set_no_partitions(); + if (thd->lex->describe) + goto exit_without_my_ok; + my_ok(thd); // No matching records DBUG_RETURN(0); } From 062b7bfa39ae418f1da85bef3d8968fc9d53569e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 5 Oct 2013 13:48:45 +0400 Subject: [PATCH 45/64] Better comments --- sql/sql_delete.cc | 2 -- sql/sql_lex.h | 7 +++++++ sql/sql_update.cc | 2 -- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index a84d6795603..a9d97a6debc 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -245,8 +245,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table->map=1; query_plan.select_lex= &thd->lex->select_lex; query_plan.table= table; - - //psergey-todo: Ugly, discuss with Sanja query_plan.updating_a_view= test(table_list->view); if (mysql_prepare_delete(thd, table_list, select_lex->with_wild, diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 99a5b2d8ae3..21f3b5a5859 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2382,6 +2382,13 @@ protected: bool impossible_where; bool no_partitions; public: + /* + When single-table UPDATE updates a VIEW, that VIEW's select is still + listed as the first child. When we print EXPLAIN, it looks like a + subquery. + In order to get rid of it, updating_a_view=TRUE means that first child + select should not be shown when printing EXPLAIN. + */ bool updating_a_view; TABLE *table; diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 81e0f6c0ebe..db32ad2a0c5 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -314,8 +314,6 @@ int mysql_update(THD *thd, my_error(ER_NON_UPDATABLE_TABLE, MYF(0), table_list->alias, "UPDATE"); DBUG_RETURN(1); } - - //psergey-todo: Ugly, discuss with Sanja query_plan.updating_a_view= test(table_list->view); /* Calculate "table->covering_keys" based on the WHERE */ From 8346a498eaa1b1c3d8d709f78ca6a913b6c9bc5e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 7 Oct 2013 13:20:22 +0400 Subject: [PATCH 46/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Run probes like MYSQL_INSERT_SELECT_START or MYSQL_MULTI_DELETE_START for EXPLAIN, too. We should run them, because 1. EXPLAIN SELECT does it, and 2. MySQL also does it. --- sql/sql_join_cache.cc | 8 ++++---- sql/sql_parse.cc | 39 +++++++++------------------------------ 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index 8b505b75272..a66ab42c855 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2553,16 +2553,16 @@ finish: /* - Add a comment on the join algorithm employed by the join cache + Save data on the join algorithm employed by the join cache SYNOPSIS - print_explain_comment() + save_explain_data() str string to add the comment on the employed join algorithm to DESCRIPTION - This function adds info on the type of the used join buffer (flat or + This function puts info about the type of the used join buffer (flat or incremental) and on the type of the the employed join algorithm (BNL, - BNLH, BKA or BKAH) to the the end of the sring str. + BNLH, BKA or BKAH) to the data structure RETURN VALUE none diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 76c26da5839..808c2cdb421 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3206,10 +3206,7 @@ end_with_restore_list: if (!(res= open_and_lock_tables(thd, all_tables, TRUE, 0))) { - if (!explain) - { - MYSQL_INSERT_SELECT_START(thd->query()); - } + MYSQL_INSERT_SELECT_START(thd->query()); /* Only the INSERT table should be merged. Other will be handled by select. @@ -3258,10 +3255,7 @@ end_with_restore_list: } /* revert changes for SP */ - if (!explain) - { - MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); - } + MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); select_lex->table_list.first= first_table; } /* @@ -3316,17 +3310,10 @@ end_with_restore_list: if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; - if (!explain) - { - MYSQL_MULTI_DELETE_START(thd->query()); - } - + MYSQL_MULTI_DELETE_START(thd->query()); if ((res= mysql_multi_delete_prepare(thd))) { - if (!explain) - { - MYSQL_MULTI_DELETE_DONE(1, 0); - } + MYSQL_MULTI_DELETE_DONE(1, 0); goto error; } @@ -3348,15 +3335,12 @@ end_with_restore_list: result, unit, select_lex); res|= thd->is_error(); - if (!explain) - { - MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); - if (res) - result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ - } + MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ else { - if (!res) + if (explain) { select_result *result= new select_send(); LEX *lex= thd->lex; @@ -3365,8 +3349,6 @@ end_with_restore_list: result->send_eof()) res= 1; } - else - result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ } delete result; } @@ -3374,10 +3356,7 @@ end_with_restore_list: else { res= TRUE; // Error - if (!explain) - { - MYSQL_MULTI_DELETE_DONE(1, 0); - } + MYSQL_MULTI_DELETE_DONE(1, 0); } break; } From 69393db3d15b5eac143ab6068037f938b3003ce1 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 7 Oct 2013 13:58:47 +0400 Subject: [PATCH 47/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Better EXPLAIN-saving methods for quick selects --- sql/opt_range.cc | 114 ++++++++++++++++++++++++++++------------------ sql/opt_range.h | 14 +++--- sql/sql_explain.h | 5 +- sql/sql_select.cc | 3 +- 4 files changed, 81 insertions(+), 55 deletions(-) diff --git a/sql/opt_range.cc b/sql/opt_range.cc index cee72be7bda..eec15ed93f9 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -11940,110 +11940,134 @@ void QUICK_SELECT_I::add_key_name(String *str, bool *first) } -void QUICK_RANGE_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) +Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= QS_TYPE_RANGE; - explain->range.set(alloc, head->key_info[index].name, max_used_key_length); + 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); + return res; } -void QUICK_GROUP_MIN_MAX_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) +Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= QS_TYPE_GROUP_MIN_MAX; - explain->range.set(alloc, head->key_info[index].name, max_used_key_length); + 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); + return res; } -void QUICK_INDEX_SORT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) +Explain_quick_select* QUICK_INDEX_SORT_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= get_type(); + Explain_quick_select *res; + if (!(res= new (alloc) Explain_quick_select(get_type()))) + return NULL; QUICK_RANGE_SELECT *quick; - Explain_quick_select *child_qpf; + Explain_quick_select *child_explain; List_iterator_fast it(quick_selects); while ((quick= it++)) { - child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - quick->save_info(alloc, child_qpf); + if ((child_explain= quick->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } if (pk_quick_select) { - child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - pk_quick_select->save_info(alloc, child_qpf); + if ((child_explain= pk_quick_select->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } + return res; } + /* - Same as QUICK_INDEX_SORT_SELECT::save_info(), but primary key is printed + Same as QUICK_INDEX_SORT_SELECT::get_explain(), but primary key is printed first */ -void QUICK_INDEX_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) + +Explain_quick_select* QUICK_INDEX_INTERSECT_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= get_type(); - Explain_quick_select *child_qpf; + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (alloc) Explain_quick_select(get_type()))) + return NULL; if (pk_quick_select) { - child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - pk_quick_select->save_info(alloc, child_qpf); + if ((child_explain= pk_quick_select->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } QUICK_RANGE_SELECT *quick; List_iterator_fast it(quick_selects); while ((quick= it++)) { - child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - quick->save_info(alloc, child_qpf); + if ((child_explain= quick->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } - + return res; } -void QUICK_ROR_INTERSECT_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) +Explain_quick_select* QUICK_ROR_INTERSECT_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= get_type(); + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (alloc) Explain_quick_select(get_type()))) + return NULL; QUICK_SELECT_WITH_RECORD *qr; List_iterator_fast it(quick_selects); while ((qr= it++)) { - Explain_quick_select *child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - qr->quick->save_info(alloc, child_qpf); + if ((child_explain= qr->quick->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } if (cpk_quick) { - Explain_quick_select *child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - cpk_quick->save_info(alloc, child_qpf); + if ((child_explain= cpk_quick->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } + return res; } -void QUICK_ROR_UNION_SELECT::save_info(MEM_ROOT *alloc, - Explain_quick_select *explain) +Explain_quick_select* QUICK_ROR_UNION_SELECT::get_explain(MEM_ROOT *alloc) { - explain->quick_type= get_type(); + Explain_quick_select *res; + Explain_quick_select *child_explain; + + if (!(res= new (alloc) Explain_quick_select(get_type()))) + return NULL; QUICK_SELECT_I *quick; List_iterator_fast it(quick_selects); while ((quick= it++)) { - Explain_quick_select *child_qpf= new Explain_quick_select; - explain->children.push_back(child_qpf); - quick->save_info(alloc, child_qpf); + if ((child_explain= quick->get_explain(alloc))) + res->children.push_back(child_explain); + else + return NULL; } + + return res; } diff --git a/sql/opt_range.h b/sql/opt_range.h index 23274466990..29bb9ed88ff 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -346,7 +346,7 @@ public: void add_key_name(String *str, bool *first); /* Save information about quick select's query plan */ - virtual void save_info(MEM_ROOT *alloc, Explain_quick_select *explain)= 0; + virtual Explain_quick_select* get_explain(MEM_ROOT *alloc)= 0; /* Return 1 if any index used by this quick select @@ -473,7 +473,7 @@ public: { file->position(record); } int get_type() { return QS_TYPE_RANGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -610,7 +610,7 @@ public: #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); @@ -674,7 +674,7 @@ public: int get_next(); int get_type() { return QS_TYPE_INDEX_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); }; @@ -712,7 +712,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -791,7 +791,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); bool is_keys_used(const MY_BITMAP *fields); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); @@ -940,7 +940,7 @@ public: #endif bool is_agg_distinct() { return have_agg_distinct; } bool loose_scan_is_scanning() { return is_index_scan; } - void save_info(MEM_ROOT *alloc, Explain_quick_select *explain); + Explain_quick_select *get_explain(MEM_ROOT *alloc); }; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 87ae29678e9..1eea61d98fb 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -341,7 +341,10 @@ public: class Explain_quick_select : public Sql_alloc { public: - int quick_type; + Explain_quick_select(int quick_type_arg) : quick_type(quick_type_arg) + {} + + const int quick_type; /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ Explain_index_use range; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c787d659e36..00a9eb53fb9 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22657,8 +22657,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, */ if (tab->select && tab->select->quick && tab_type != JT_CONST) { - eta->quick_info= new Explain_quick_select; - tab->select->quick->save_info(thd->mem_root, eta->quick_info); + eta->quick_info= tab->select->quick->get_explain(thd->mem_root); } if (key_info) /* 'index' or 'ref' access */ From 98a8642fe827fd9ac16bdfaf556599fa509d4180 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 7 Oct 2013 17:29:51 +0400 Subject: [PATCH 48/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Add support for EXPLAIN INSERT. --- mysql-test/r/explain_non_select.result | 17 ++++++++ mysql-test/t/explain_non_select.test | 15 +++++++ sql/sp_head.cc | 4 ++ sql/sql_delete.cc | 14 +----- sql/sql_explain.cc | 59 +++++++++++++++++++++++--- sql/sql_explain.h | 48 ++++++++++++++++++--- sql/sql_insert.cc | 47 +++++++++++++++++++- sql/sql_parse.cc | 18 +------- sql/sql_select.cc | 22 +++++++--- sql/sql_select.h | 2 +- sql/sql_update.cc | 17 ++------ 11 files changed, 201 insertions(+), 62 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 3cc4d4f1e26..1c5d545fd99 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -181,3 +181,20 @@ explain partitions update t1 set b=12345 where a in (32,33); id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No matching rows after partition pruning drop table t1; +# +# Tests for EXPLAIN INSERT ... VALUES +# +create table t1 (a int, key(a)); +explain insert into t1 values (1),(2),(3); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +insert into t1 values (1),(2),(3); +create table t2 (a int, b int); +explain insert into t2 values +(10, 1+(select max(a) from t1)), +(11, 1+(select max(a+1) from t1)); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t2 ALL NULL NULL NULL NULL NULL NULL +3 SUBQUERY t1 index NULL a 5 NULL 3 Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +drop table t1,t2; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 3c4c2d0739d..7220a00bd2a 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -155,3 +155,18 @@ explain partitions delete from t1 where a in (32,33); explain partitions update t1 set b=12345 where a in (32,33); drop table t1; + +--echo # +--echo # Tests for EXPLAIN INSERT ... VALUES +--echo # +create table t1 (a int, key(a)); +explain insert into t1 values (1),(2),(3); +insert into t1 values (1),(2),(3); + +create table t2 (a int, b int); +explain insert into t2 values + (10, 1+(select max(a) from t1)), + (11, 1+(select max(a+1) from t1)); + +drop table t1,t2; + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 31fd0d7248e..ffc77224c12 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -328,6 +328,10 @@ sp_get_flags_for_command(LEX *lex) } case SQLCOM_UPDATE: case SQLCOM_UPDATE_MULTI: + case SQLCOM_INSERT: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + case SQLCOM_INSERT_SELECT: { if (!lex->describe) flags= 0; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index a9d97a6debc..c023a1eebf4 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -680,20 +680,8 @@ cleanup: /* Special exits */ exit_without_my_ok: query_plan.save_explain_data(thd->lex->explain); + int err2= thd->lex->explain->send_explain(thd); - select_send *result2; - if (!(result2= new select_send())) - return 1; /* purecov: inspected */ - List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ - result2->prepare(dummy, &thd->lex->unit); - thd->send_explain_fields(result2); - int err2= thd->lex->explain->print_explain(result2, thd->lex->describe); - - if (err2) - result2->abort_result_set(); - else - result2->send_eof(); - delete select; free_underlaid_joins(thd, select_lex); //table->set_keyread(false); diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 28774ef2e99..2bd6a6bef25 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -22,9 +22,8 @@ #include "sql_select.h" -Explain_query::Explain_query() +Explain_query::Explain_query() : upd_del_plan(NULL), insert_plan(NULL) { - upd_del_plan= NULL; operations= 0; } @@ -32,6 +31,7 @@ Explain_query::Explain_query() Explain_query::~Explain_query() { delete upd_del_plan; + delete insert_plan; uint i; for (i= 0 ; i < unions.elements(); i++) delete unions.at(i); @@ -100,18 +100,46 @@ void Explain_query::add_node(Explain_node *node) } +/* + Send EXPLAIN output to the client. +*/ + +int Explain_query::send_explain(THD *thd) +{ + select_result *result; + LEX *lex= thd->lex; + + if (!(result= new select_send()) || + thd->send_explain_fields(result)) + return 1; + + int res; + if ((res= print_explain(result, lex->describe))) + result->abort_result_set(); + else + result->send_eof(); + + return res; +} + + /* The main entry point to print EXPLAIN of the entire query */ int Explain_query::print_explain(select_result_sink *output, - uint8 explain_flags) + uint8 explain_flags) { if (upd_del_plan) { upd_del_plan->print_explain(this, output, explain_flags); return 0; } + else if (insert_plan) + { + insert_plan->print_explain(this, output, explain_flags); + return 0; + } else { /* Start printing from node with id=1 */ @@ -681,7 +709,7 @@ const char * Explain_quick_select::get_name_by_type() return "sort_intersect"; default: DBUG_ASSERT(0); - return "Oops"; + return "unknown quick select type"; } } @@ -809,13 +837,34 @@ int Explain_update::print_explain(Explain_query *query, key_str.length()? key_str.c_ptr() : NULL, key_len_str.length() ? key_len_str.c_ptr() : NULL, NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ - rows, + &rows, extra_str.c_ptr()); return print_explain_for_children(query, output, explain_flags); } +int Explain_insert::print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags) +{ + const char *select_type="INSERT"; + print_explain_row(output, explain_flags, + 1, /* id */ + select_type, + table_name.c_ptr(), + NULL, // partitions + JT_ALL, + NULL, // possible_keys + NULL, // key + NULL, // key_len + NULL, // ref + NULL, // rows + NULL); + + return print_explain_for_children(query, output, explain_flags); +} + + void delete_explain_query(LEX *lex) { delete lex->explain; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 1eea61d98fb..07493cfa14d 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -37,10 +37,16 @@ class Explain_query; class Explain_node : public Sql_alloc { public: - enum explain_node_type {EXPLAIN_UNION, EXPLAIN_SELECT, EXPLAIN_UPDATE, EXPLAIN_DELETE }; + enum explain_node_type + { + EXPLAIN_UNION, + EXPLAIN_SELECT, + EXPLAIN_UPDATE, + EXPLAIN_DELETE, + EXPLAIN_INSERT + }; + virtual enum explain_node_type get_type()= 0; - - virtual int get_select_id()= 0; /* @@ -172,8 +178,10 @@ public: bool using_filesort; }; -class Explain_delete; +class Explain_update; +class Explain_delete; +class Explain_insert; /* Explain structure for a query (i.e. a statement). @@ -229,14 +237,20 @@ public: /* Explain_delete inherits from Explain_update */ Explain_update *upd_del_plan; + /* Query "plan" for INSERTs */ + Explain_insert *insert_plan; + /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); + /* Send tabular EXPLAIN to the client */ + int send_explain(THD *thd); + /* Return tabular EXPLAIN output as a text string */ bool print_explain_str(THD *thd, String *out_str); /* If true, at least part of EXPLAIN can be printed */ - bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; } + bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } MEM_ROOT *mem_root; private: Dynamic_array unions; @@ -445,7 +459,7 @@ private: /* - Query Plan Footprint for single-table UPDATE. + EXPLAIN structure for single-table UPDATE. This is similar to Explain_table_access, except that it is more restrictive. Also, it can have UPDATE operation options, but currently there aren't any. @@ -482,8 +496,28 @@ public: }; +/* + EXPLAIN data structure for an INSERT. + + At the moment this doesn't do much as we don't really have any query plans + for INSERT statements. +*/ + +class Explain_insert : public Explain_node +{ +public: + StringBuffer<64> table_name; + + enum explain_node_type get_type() { return EXPLAIN_INSERT; } + int get_select_id() { return 1; /* always root */ } + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + /* - Explain data of a single-table DELETE. + EXPLAIN data of a single-table DELETE. */ class Explain_delete: public Explain_update diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 982524e7e32..10cd8a6a7f6 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -644,6 +644,36 @@ create_insert_stmt_from_insert_delayed(THD *thd, String *buf) } +static void save_insert_query_plan(THD* thd, TABLE_LIST *table_list) +{ + Explain_insert* explain= new Explain_insert; + explain->table_name.append(table_list->table->alias); + + thd->lex->explain->insert_plan= explain; + + /* See Update_plan::updating_a_view for details */ + bool skip= test(table_list->view); + + /* Save subquery children */ + for (SELECT_LEX_UNIT *unit= thd->lex->select_lex.first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (skip) + { + skip= false; + continue; + } + /* + Table elimination doesn't work for INSERTS, but let's still have this + here for consistency + */ + if (!(unit->item && unit->item->eliminated)) + explain->add_child(unit->first_select()->select_number); + } +} + + /** INSERT statement implementation @@ -660,6 +690,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, enum_duplicates duplic, bool ignore) { + bool retval= true; int error, res; bool transactional_table, joins_freed= FALSE; bool changed; @@ -780,6 +811,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, /* Restore the current context. */ ctx_state.restore_state(context, table_list); + + if (thd->lex->unit.first_select()->optimize_unflattened_subqueries(false)) + { + goto abort; + } + save_insert_query_plan(thd, table_list); + if (thd->lex->describe) + { + retval= 0; + goto exit_without_my_ok; + } /* Fill in the given fields and dump it to the table file @@ -1128,16 +1170,19 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, DBUG_RETURN(FALSE); abort: +exit_without_my_ok: #ifndef EMBEDDED_LIBRARY if (lock_type == TL_WRITE_DELAYED) end_delayed_insert(thd); #endif if (table != NULL) table->file->ha_release_auto_increment(); + retval= thd->lex->explain->send_explain(thd); + if (!joins_freed) free_underlaid_joins(thd, &thd->lex->select_lex); thd->abort_on_warning= 0; - DBUG_RETURN(TRUE); + DBUG_RETURN(retval); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 808c2cdb421..3139cf4404f 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3245,14 +3245,7 @@ end_with_restore_list: } if (!res && explain) - { - select_result *result= new select_send(); - LEX *lex= thd->lex; - if (thd->send_explain_fields(result) || - lex->explain->print_explain(result, lex->describe) || - result->send_eof()) - res= 1; - } + res= thd->lex->explain->send_explain(thd); /* revert changes for SP */ MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); @@ -3341,14 +3334,7 @@ end_with_restore_list: else { if (explain) - { - select_result *result= new select_send(); - LEX *lex= thd->lex; - if (thd->send_explain_fields(result) || - lex->explain->print_explain(result, lex->describe) || - result->send_eof()) - res= 1; - } + res= thd->lex->explain->send_explain(thd); } delete result; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 00a9eb53fb9..72dc10a8917 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22238,7 +22238,10 @@ int print_explain_message_line(select_result_sink *result, if (options & DESCRIBE_EXTENDED) item_list.push_back(item_null); - item_list.push_back(new Item_string(message,strlen(message),cs)); + if (message) + item_list.push_back(new Item_string(message,strlen(message),cs)); + else + item_list.push_back(item_null); if (result->send_data(item_list)) return 1; @@ -22292,7 +22295,7 @@ int print_explain_row(select_result_sink *result, const char *index, const char *key_len, const char *ref, - ha_rows rows, + ha_rows *rows, const char *extra) { const CHARSET_INFO *cs= system_charset_info; @@ -22337,15 +22340,24 @@ int print_explain_row(select_result_sink *result, item_list.push_back(item); /* 'rows' */ - item_list.push_back(new Item_int(rows, - MY_INT64_NUM_DECIMAL_DIGITS)); + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + /* 'filtered' */ const double filtered=100.0; if (options & DESCRIBE_EXTENDED) item_list.push_back(new Item_float(filtered, 2)); /* 'Extra' */ - item_list.push_back(new Item_string(extra, strlen(extra), cs)); + if (extra) + item_list.push_back(new Item_string(extra, strlen(extra), cs)); + else + item_list.push_back(item_null); if (result->send_data(item_list)) return 1; diff --git a/sql/sql_select.h b/sql/sql_select.h index d6e133262f1..c615e855606 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1865,7 +1865,7 @@ int print_explain_row(select_result_sink *result, const char *index, const char *key_len, const char *ref, - ha_rows rows, + ha_rows *rows, const char *extra); void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index db32ad2a0c5..bc2c5c69adb 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -948,7 +948,7 @@ int mysql_update(THD *thd, if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; - thd->apc_target.disable(); //psergey-todo. + thd->apc_target.disable(); apc_target_enabled= false; end_read_record(&info); delete select; @@ -1035,19 +1035,8 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); query_plan.save_explain_data(thd->lex->explain); - - select_send *result; - if (!(result= new select_send())) - return 1; /* purecov: inspected */ - List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ - result->prepare(dummy, &thd->lex->unit); - thd->send_explain_fields(result); - int err2= thd->lex->explain->print_explain(result, - thd->lex->describe); - if (err2) - result->abort_result_set(); - else - result->send_eof(); + + int err2= thd->lex->explain->send_explain(thd); delete select; free_underlaid_joins(thd, select_lex); From fda46df62071f54ebc4d806c6d9caf031d801150 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 8 Oct 2013 14:26:14 +0400 Subject: [PATCH 49/64] MDEV-3798: EXPLAIN UPDATE/DELETE - if EXPLAIN DELETE prints "Deleting all rows", it should show the expected number of rows in the rows column. --- sql/sql_delete.cc | 1 + sql/sql_explain.cc | 8 +++++--- sql/sql_select.cc | 23 +++++++++++++++++++++-- sql/sql_select.h | 1 + 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c023a1eebf4..50a5ec79166 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -59,6 +59,7 @@ void Delete_plan::save_explain_data(Explain_query *query) { explain->deleting_all_rows= true; explain->select_type= "SIMPLE"; + explain->rows= table_rows; } else { diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 2bd6a6bef25..a8afc0ac6d3 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -778,7 +778,7 @@ int Explain_delete::print_explain(Explain_query *query, const char *msg= "Deleting all rows"; int res= print_explain_message_line(output, explain_flags, 1 /*select number*/, - select_type, msg); + select_type, &rows, msg); return res; } @@ -797,11 +797,13 @@ int Explain_update::print_explain(Explain_query *query, if (impossible_where || no_partitions) { const char *msg= impossible_where ? - "Impossible where" : + "Impossible WHERE" : "No matching rows after partition pruning"; int res= print_explain_message_line(output, explain_flags, 1 /*select number*/, - select_type, msg); + select_type, + NULL, /* rows */ + msg); return res; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 72dc10a8917..b99b41915f6 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22222,6 +22222,7 @@ int print_explain_message_line(select_result_sink *result, uint8 options, uint select_number, const char *select_type, + ha_rows *rows, const char *message) { const CHARSET_INFO *cs= system_charset_info; @@ -22231,13 +22232,31 @@ int print_explain_message_line(select_result_sink *result, item_list.push_back(new Item_int((int32) select_number)); item_list.push_back(new Item_string(select_type, strlen(select_type), cs)); - for (uint i=0 ; i < 7; i++) - item_list.push_back(item_null); + /* `table` */ + item_list.push_back(item_null); + + /* `partitions` */ if (options & DESCRIBE_PARTITIONS) item_list.push_back(item_null); + + /* type, possible_keys, key, key_len, ref */ + for (uint i=0 ; i < 5; i++) + item_list.push_back(item_null); + + /* `rows` */ + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* `filtered` */ if (options & DESCRIBE_EXTENDED) item_list.push_back(item_null); + /* `Extra` */ if (message) item_list.push_back(new Item_string(message,strlen(message),cs)); else diff --git a/sql/sql_select.h b/sql/sql_select.h index c615e855606..71760ea76f5 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1852,6 +1852,7 @@ int print_explain_message_line(select_result_sink *result, uint8 options, uint select_number, const char *select_type, + ha_rows *rows, const char *message); void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res); int print_explain_row(select_result_sink *result, From 69e6a2bb22434d94d96312ba8a0540195273dfdd Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 8 Oct 2013 16:13:49 +0400 Subject: [PATCH 50/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Update test results after last few csets - Generate correct value for `possible_keys` column for single table UPDATE/DELETE. --- mysql-test/r/explain_non_select.result | 6 +++--- sql/opt_range.cc | 14 ++++++++++++++ sql/opt_range.h | 2 ++ sql/sql_delete.cc | 2 +- sql/sql_update.cc | 2 +- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 1c5d545fd99..d118cd7b4e7 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -13,7 +13,7 @@ id select_type table type possible_keys key key_len ref rows Extra # DELETE without WHERE is a special case: explain delete from t0; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Deleting all rows +1 SIMPLE NULL NULL NULL NULL NULL NULL 8 Deleting all rows create table t1 (a int, b int, filler char(100), key(a), key(b)); insert into t1 select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' @@ -65,10 +65,10 @@ select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' from t0 A, t0 B, t0 C; explain update t1 set a=a+1 where 3>4; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE explain update t1 set a=a+1 where a=3 and a=4; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE # This should use an index, possible_keys=NULL because there is no WHERE explain update t1 set a=a+1 order by a limit 2; id select_type table type possible_keys key key_len ref rows Extra diff --git a/sql/opt_range.cc b/sql/opt_range.cc index eec15ed93f9..08d9df2a10a 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -861,6 +861,14 @@ class PARAM : public RANGE_OPT_PARAM { public: ha_rows quick_rows[MAX_KEY]; + + /* + This will collect 'possible keys' based on the range optimization. + + Queries with a JOIN object actually use ref optimizer (see add_key_field) + to collect possible_keys. This is used by single table UPDATE/DELETE. + */ + key_map possible_keys; longlong baseflag; uint max_key_part, range_count; @@ -2955,6 +2963,8 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, read_time= (double) records + scan_time + 1; // Force to use index else if (read_time <= 2.0 && !force_quick_range) DBUG_RETURN(0); /* No need for quick select */ + + possible_keys.clear_all(); DBUG_PRINT("info",("Time to scan table: %g", read_time)); @@ -2986,6 +2996,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, param.using_real_indexes= TRUE; param.remove_jump_scans= TRUE; param.force_default_mrr= ordered_output; + param.possible_keys.clear_all(); thd->no_errors=1; // Don't warn about NULL init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0, @@ -3197,6 +3208,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, quick= NULL; } } + possible_keys= param.possible_keys; free_mem: free_root(&alloc,MYF(0)); // Return memory & allocator @@ -3204,6 +3216,7 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, thd->no_errors=0; } + DBUG_EXECUTE("info", print_quick(quick, &needed_reg);); /* @@ -10467,6 +10480,7 @@ ha_rows check_quick_select(PARAM *param, uint idx, bool index_only, if (rows != HA_POS_ERROR) { param->quick_rows[keynr]= rows; + param->possible_keys.set_bit(keynr); if (update_tbl_stats) { param->table->quick_keys.set_bit(keynr); diff --git a/sql/opt_range.h b/sql/opt_range.h index 29bb9ed88ff..d61219b7dd0 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -982,6 +982,8 @@ class SQL_SELECT :public Sql_alloc { key_map quick_keys; // Possible quick keys key_map needed_reg; // Possible quick keys after prev tables. table_map const_tables,read_tables; + /* See PARAM::possible_keys */ + key_map possible_keys; bool free_cond; /* Currently not used and always FALSE */ SQL_SELECT(); diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 50a5ec79166..19401496a74 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -436,7 +436,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } query_plan.select= select; - query_plan.possible_keys= table->quick_keys; + query_plan.possible_keys= select? select->possible_keys: key_map(0); query_plan.table_rows= table->file->stats.records; /* diff --git a/sql/sql_update.cc b/sql/sql_update.cc index bc2c5c69adb..6842f58d92e 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -492,8 +492,8 @@ int mysql_update(THD *thd, - if we're running EXPLAIN UPDATE, get out */ query_plan.select= select; - query_plan.possible_keys= table->quick_keys; query_plan.table_rows= table->file->stats.records; + query_plan.possible_keys= select? select->possible_keys: key_map(0); /* Ok, we have generated a query plan for the UPDATE. From 161d68759433b0315a6b95209d3db86be411a686 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 9 Oct 2013 09:40:33 +0400 Subject: [PATCH 51/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Generate correct contents of `Extra` column for UPDATEs/DELETEs that use quick selects - UPDATEs with used_key_is_modified=true will show "Using buffer" --- sql/sql_delete.cc | 8 +++++--- sql/sql_explain.cc | 42 ++++++++++++++++++++++++++++++++++++++---- sql/sql_explain.h | 3 +++ sql/sql_lex.h | 16 ++++++++++++---- sql/sql_update.cc | 24 +++++++++++++++--------- 5 files changed, 73 insertions(+), 20 deletions(-) diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 19401496a74..15dfe3e6c7c 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -142,14 +142,16 @@ void Update_plan::save_explain_data_intern(Explain_query *query, explain->using_where= test(select && select->cond); explain->using_filesort= using_filesort; + explain->using_io_buffer= using_io_buffer; make_possible_keys_line(table, possible_keys, &explain->possible_keys_line); + explain->quick_info= NULL; + /* Calculate key_len */ if (select && select->quick) { - select->quick->add_keys_and_lengths(&explain->key_str, - &explain->key_len_str); + explain->quick_info= select->quick->get_explain(mem_root); } else { @@ -218,7 +220,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; bool with_select= !select_lex->item_list.is_empty(); - Delete_plan query_plan; + Delete_plan query_plan(thd->mem_root); query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_delete"); diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index a8afc0ac6d3..91a73cfb7d3 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -793,6 +793,8 @@ int Explain_update::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags) { + StringBuffer<64> key_buf; + StringBuffer<64> key_len_buf; StringBuffer<64> extra_str; if (impossible_where || no_partitions) { @@ -807,8 +809,32 @@ int Explain_update::print_explain(Explain_query *query, return res; } + + if (quick_info) + { + quick_info->print_key(&key_buf); + quick_info->print_key_len(&key_len_buf); + + StringBuffer<64> quick_buf; + quick_info->print_extra(&quick_buf); + if (quick_buf.length()) + { + extra_str.append(STRING_WITH_LEN("Using ")); + extra_str.append(quick_buf); + } + } + else + { + key_buf.copy(key_str); + key_len_buf.copy(key_len_str); + } + if (using_where) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); extra_str.append(STRING_WITH_LEN("Using where")); + } if (mrr_type.length() != 0) { @@ -816,7 +842,7 @@ int Explain_update::print_explain(Explain_query *query, extra_str.append(STRING_WITH_LEN("; ")); extra_str.append(mrr_type); } - + if (using_filesort) { if (extra_str.length() !=0) @@ -824,6 +850,13 @@ int Explain_update::print_explain(Explain_query *query, extra_str.append(STRING_WITH_LEN("Using filesort")); } + if (using_io_buffer) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using buffer")); + } + /* Single-table DELETE commands do not do "Using temporary". "Using index condition" is also not possible (which is an unjustified limitation) @@ -836,8 +869,8 @@ int Explain_update::print_explain(Explain_query *query, used_partitions_set? used_partitions.c_ptr() : NULL, jtype, possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, - key_str.length()? key_str.c_ptr() : NULL, - key_len_str.length() ? key_len_str.c_ptr() : NULL, + key_buf.length()? key_buf.c_ptr() : NULL, + key_len_buf.length() ? key_len_buf.c_ptr() : NULL, NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ &rows, extra_str.c_ptr()); @@ -846,7 +879,8 @@ int Explain_update::print_explain(Explain_query *query, } -int Explain_insert::print_explain(Explain_query *query, select_result_sink *output, +int Explain_insert::print_explain(Explain_query *query, + select_result_sink *output, uint8 explain_flags) { const char *select_type="INSERT"; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 07493cfa14d..2eb9e528a67 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -486,10 +486,13 @@ public: StringBuffer<128> key_len_str; StringBuffer<64> mrr_type; + Explain_quick_select *quick_info; + bool using_where; ha_rows rows; bool using_filesort; + bool using_io_buffer; virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 21f3b5a5859..9515079e75f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2390,6 +2390,9 @@ public: select should not be shown when printing EXPLAIN. */ bool updating_a_view; + + /* Allocate things there */ + MEM_ROOT *mem_root; TABLE *table; SQL_SELECT *select; @@ -2403,6 +2406,7 @@ public: key_map possible_keys; bool using_filesort; + bool using_io_buffer; /* Set this plan to be a plan to do nothing because of impossible WHERE */ void set_impossible_where() { impossible_where= true; } @@ -2412,8 +2416,10 @@ public: void save_explain_data_intern(Explain_query *query, Explain_update *eu); virtual ~Update_plan() {} - Update_plan() : - impossible_where(false), no_partitions(false), using_filesort(false) + Update_plan(MEM_ROOT *mem_root_arg) : + impossible_where(false), no_partitions(false), + mem_root(mem_root_arg), + using_filesort(false), using_io_buffer(false) {} }; @@ -2425,8 +2431,10 @@ class Delete_plan : public Update_plan public: /* Construction functions */ - Delete_plan() : - deleting_all_rows(false) {} + Delete_plan(MEM_ROOT *mem_root_arg) : + Update_plan(mem_root_arg), + deleting_all_rows(false) + {} /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ void set_delete_all_rows(ha_rows rows_arg) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 6842f58d92e..5b2333657e1 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -276,7 +276,7 @@ int mysql_update(THD *thd, ulonglong id; List all_fields; killed_state killed_status= NOT_KILLED; - Update_plan query_plan; + Update_plan query_plan(thd->mem_root); query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; bool apc_target_enabled= false; // means was enabled *by code this function* @@ -495,6 +495,17 @@ int mysql_update(THD *thd, query_plan.table_rows= table->file->stats.records; query_plan.possible_keys= select? select->possible_keys: key_map(0); + if (used_key_is_modified || order || + partition_key_modified(table, table->write_set)) + { + if (order && (need_sort || used_key_is_modified)) + query_plan.using_filesort= true; + else + query_plan.using_io_buffer= true; + } + + query_plan.save_explain_data(thd->lex->explain); + /* Ok, we have generated a query plan for the UPDATE. - if we're running EXPLAIN UPDATE, goto produce explain output @@ -502,16 +513,12 @@ int mysql_update(THD *thd, */ if (thd->lex->describe) goto exit_without_my_ok; - - query_plan.save_explain_data(thd->lex->explain); thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", dbug_serve_apcs(thd, 1);); - - - if (used_key_is_modified || order || - partition_key_modified(table, table->write_set)) + + if (query_plan.using_filesort || query_plan.using_io_buffer) { /* We can't update table directly; We must first search after all @@ -528,7 +535,7 @@ int mysql_update(THD *thd, table->use_all_columns(); /* note: We avoid sorting if we sort on the used index */ - if (order && (need_sort || used_key_is_modified)) + if (query_plan.using_filesort) { /* Doing an ORDER BY; Let filesort find and sort the rows we are going @@ -1034,7 +1041,6 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); - query_plan.save_explain_data(thd->lex->explain); int err2= thd->lex->explain->send_explain(thd); From 3c6ac6694d291dc454af6f9042c9217afd7fff9b Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 9 Oct 2013 13:07:46 +0400 Subject: [PATCH 52/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Produce correct #rows for ORDER BY ... LIMIT N queries that take advantage of ordered index read to read only N rows. --- mysql-test/r/explain_non_select.result | 8 ++++---- sql/sql_delete.cc | 12 +++++++++--- sql/sql_lex.h | 4 ++-- sql/sql_select.cc | 7 ++++++- sql/sql_select.h | 3 ++- sql/sql_update.cc | 12 +++++++++--- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index d118cd7b4e7..7d5228de0ee 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -21,7 +21,7 @@ from t0 A, t0 B, t0 C; # This should use an index, possible_keys=NULL because there is no WHERE explain delete from t1 order by a limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index NULL a NULL NULL 512 +1 SIMPLE t1 index NULL a NULL NULL 2 # This should use range, possible_keys={a,b} explain delete from t1 where a<20 and b < 10; id select_type table type possible_keys key key_len ref rows Extra @@ -72,7 +72,7 @@ id select_type table type possible_keys key key_len ref rows Extra # This should use an index, possible_keys=NULL because there is no WHERE explain update t1 set a=a+1 order by a limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 Using filesort # This should use range, possible_keys={a,b} explain update t1 set filler='fooo' where a<20 and b < 10; id select_type table type possible_keys key key_len ref rows Extra @@ -80,11 +80,11 @@ id select_type table type possible_keys key key_len ref rows Extra # This should use ALL + filesort explain update t1 set filler='fooo' order by a+1 limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 Using filesort # This should use range + using filesort explain update t1 set filler='fooo' where a<20 order by b limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range a a 5 NULL 1 Using where +1 SIMPLE t1 range a a 5 NULL 1 Using where; Using filesort # Try some subqueries: explain update t1 set filler='fooo' where a < (select max(a) from t0); id select_type table type possible_keys key key_len ref rows Extra diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 15dfe3e6c7c..00193800b93 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -59,7 +59,7 @@ void Delete_plan::save_explain_data(Explain_query *query) { explain->deleting_all_rows= true; explain->select_type= "SIMPLE"; - explain->rows= table_rows; + explain->rows= scanned_rows; } else { @@ -161,7 +161,7 @@ void Update_plan::save_explain_data_intern(Explain_query *query, } // key_len stays NULL } - explain->rows= select ? select->records : table_rows; + explain->rows= scanned_rows; if (select && select->quick && select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) @@ -421,6 +421,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_QUICK); + query_plan.scanned_rows= select? select->records: table->file->stats.records; if (order) { table->update_const_key_parts(conds); @@ -432,14 +433,19 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query_plan.index= MAX_KEY; } else + { + ha_rows scanned_limit= query_plan.scanned_rows; query_plan.index= get_index_for_order(order, table, select, limit, + &scanned_limit, &query_plan.using_filesort, &reverse); + if (!query_plan.using_filesort) + query_plan.scanned_rows= scanned_limit; + } } query_plan.select= select; query_plan.possible_keys= select? select->possible_keys: key_map(0); - query_plan.table_rows= table->file->stats.records; /* Ok, we have generated a query plan for the DELETE. diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 9515079e75f..c5b9c1eada2 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2397,7 +2397,7 @@ public: TABLE *table; SQL_SELECT *select; uint index; - ha_rows table_rows; /* Use if select==NULL */ + ha_rows scanned_rows; /* Top-level select_lex. Most of its fields are not used, we need it only to get to the subqueries. @@ -2440,7 +2440,7 @@ public: void set_delete_all_rows(ha_rows rows_arg) { deleting_all_rows= true; - table_rows= rows_arg; + scanned_rows= rows_arg; } void save_explain_data(Explain_query *query); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b99b41915f6..a51fcd07b58 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -24126,6 +24126,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, @param table Table to find a key @param select Pointer to access/update select->quick (if any) @param limit LIMIT clause parameter + @param [out] scanned_limit How many records we expect to scan + Valid if *need_sort=FALSE. @param [out] need_sort TRUE if filesort needed @param [out] reverse TRUE if the key is reversed again given ORDER (undefined if key == MAX_KEY) @@ -24143,7 +24145,8 @@ test_if_cheaper_ordering(const JOIN_TAB *tab, ORDER *order, TABLE *table, */ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, - ha_rows limit, bool *need_sort, bool *reverse) + ha_rows limit, ha_rows *scanned_limit, + bool *need_sort, bool *reverse) { if (!order) { @@ -24185,6 +24188,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, { select->set_quick(reverse_quick); *need_sort= FALSE; + *scanned_limit= select->quick->records; return select->quick->index; } else @@ -24213,6 +24217,7 @@ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, !is_key_used(table, key, table->write_set)) { *need_sort= FALSE; + *scanned_limit= limit; *reverse= (direction < 0); return key; } diff --git a/sql/sql_select.h b/sql/sql_select.h index 71760ea76f5..48515042a02 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1833,7 +1833,8 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, SELECT_LEX *select_lex, uint8 select_options); uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, - ha_rows limit, bool *need_sort, bool *reverse); + ha_rows limit, ha_rows *scanned_limit, + bool *need_sort, bool *reverse); ORDER *simple_remove_const(ORDER *order, COND *where); bool const_expression_in_where(COND *cond, Item *comp_item, Field *comp_field= NULL, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 5b2333657e1..a5f81a9e89e 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -456,6 +456,7 @@ int mysql_update(THD *thd, table->update_const_key_parts(conds); order= simple_remove_const(order, conds); + query_plan.scanned_rows= select? select->records: table->file->stats.records; if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered"): Ok to use with key field UPDATE @@ -465,8 +466,12 @@ int mysql_update(THD *thd, } else { + ha_rows scanned_limit= query_plan.scanned_rows; query_plan.index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + &scanned_limit, &need_sort, &reverse); + if (!need_sort) + query_plan.scanned_rows= scanned_limit; + if (select && select->quick) { DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); @@ -492,7 +497,6 @@ int mysql_update(THD *thd, - if we're running EXPLAIN UPDATE, get out */ query_plan.select= select; - query_plan.table_rows= table->file->stats.records; query_plan.possible_keys= select? select->possible_keys: key_map(0); if (used_key_is_modified || order || @@ -504,7 +508,6 @@ int mysql_update(THD *thd, query_plan.using_io_buffer= true; } - query_plan.save_explain_data(thd->lex->explain); /* Ok, we have generated a query plan for the UPDATE. @@ -513,6 +516,8 @@ int mysql_update(THD *thd, */ if (thd->lex->describe) goto exit_without_my_ok; + query_plan.save_explain_data(thd->lex->explain); + thd->apc_target.enable(); apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", @@ -1041,6 +1046,7 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); + query_plan.save_explain_data(thd->lex->explain); int err2= thd->lex->explain->send_explain(thd); From 7e919c52a52d372d68b930b1cd5b763364264629 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 9 Oct 2013 17:15:34 +0400 Subject: [PATCH 53/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Produce correct `key_len` when type=index. --- mysql-test/r/explain_non_select.result | 2 +- sql/sql_delete.cc | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 7d5228de0ee..bb43fccc114 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -21,7 +21,7 @@ from t0 A, t0 B, t0 C; # This should use an index, possible_keys=NULL because there is no WHERE explain delete from t1 order by a limit 2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index NULL a NULL NULL 2 +1 SIMPLE t1 index NULL a 5 NULL 2 # This should use range, possible_keys={a,b} explain delete from t1 where a<20 and b < 10; id select_type table type possible_keys key key_len ref rows Extra diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 00193800b93..7f58526d174 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -158,8 +158,11 @@ void Update_plan::save_explain_data_intern(Explain_query *query, if (index != MAX_KEY) { explain->key_str.append(table->key_info[index].name); + char buf[64]; + size_t length; + length= longlong10_to_str(table->key_info[index].key_length, buf, 10) - buf; + explain->key_len_str.append(buf, length); } - // key_len stays NULL } explain->rows= scanned_rows; From 582ecb2a253ed2c9e26a2e61102da4bf4ede72d3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 9 Oct 2013 17:20:42 +0400 Subject: [PATCH 54/64] MDEV-3798: EXPLAIN UPDATE/DELETE: Backport mysql-test/t/myisam_explain_non_select_all.test from mysql-5.6 - the .result file was modified because MariaDB choses different query plans in a number cases. Also, we don't have some of the "incorrect EXPLAIN output" bugs that they still have. The .test file and includes were taken verbatim with one exception: two tests were disabled with --disable parsing: 1. @@sql_safe_updates is not enforced EXPLAINs of multitable updates. In MariaDB, the execution itself will produce ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE, but EXPLAIN won't. 2. Their case #71 hits some old bug in MyISAM (See their comments in explain_non_select.inc for details). --- mysql-test/include/explain_non_select.inc | 833 +++++ mysql-test/include/explain_utils.inc | 161 + .../r/myisam_explain_non_select_all.result | 2903 +++++++++++++++++ .../t/myisam_explain_non_select_all.test | 21 + 4 files changed, 3918 insertions(+) create mode 100644 mysql-test/include/explain_non_select.inc create mode 100644 mysql-test/include/explain_utils.inc create mode 100644 mysql-test/r/myisam_explain_non_select_all.result create mode 100644 mysql-test/t/myisam_explain_non_select_all.test diff --git a/mysql-test/include/explain_non_select.inc b/mysql-test/include/explain_non_select.inc new file mode 100644 index 00000000000..a0f86e744b0 --- /dev/null +++ b/mysql-test/include/explain_non_select.inc @@ -0,0 +1,833 @@ +# This file is a collection of regression and coverage tests +# for WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE. + +-- disable_query_log +-- disable_result_log +# SET GLOBAL innodb_stats_persistent=0; +-- enable_result_log +-- enable_query_log + +# set end_markers_in_json=on; + +--echo #1 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = UPDATE t1 SET a = 10 WHERE a < 10 +--let $select = SELECT * FROM t1 WHERE a < 10 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #2 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 WHERE a < 10 +--let $select = SELECT * FROM t1 WHERE a < 10 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #3 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 USING t1 WHERE a = 1 +--let $select = SELECT * FROM t1 WHERE a = 1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #4 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1, t2 SET t1.a = 10 WHERE t1.a = 1 +--let $select = SELECT * FROM t1, t2 WHERE t1.a = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #5 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a = 1 +--let $select = SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #6 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3) +--let $select = SELECT * FROM t1 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3) +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #7 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3) +--let $select = SELECT * FROM t1 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3) +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #7 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3) +--let $select = SELECT * FROM t1, t2 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3) +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #8 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = t11.a + 10 +--let $select = SELECT * FROM t1 t11, (SELECT * FROM t2) t12 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #9 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 t11, (SELECT 1 FROM DUAL) t12 SET t11.a = t11.a + 10 +--let $select = SELECT * FROM t1 t11, (SELECT 1 FROM DUAL) t12 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #10 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a > 1 +--let $select = SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a > 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #11 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 WHERE a > 1 LIMIT 1 +--let $select = SELECT * FROM t1 WHERE a > 1 LIMIT 1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #12 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 WHERE 0 +--let $select = SELECT * FROM t1 WHERE 0 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #13 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 USING t1 WHERE 0 +--let $select = SELECT * FROM t1 WHERE 0 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #14 +CREATE TABLE t1 (a INT, b INT, UNIQUE KEY (a), KEY (b)); +INSERT INTO t1 VALUES (3, 3), (7, 7); +--let $query = DELETE FROM t1 WHERE a = 3 +--let $select = SELECT * FROM t1 WHERE a = 3 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #15 +CREATE TABLE t1 (a INT, b INT, UNIQUE KEY (a), KEY (b)); +INSERT INTO t1 VALUES (3, 3), (7, 7); +--let $query = DELETE FROM t1 WHERE a < 3 +--let $select = SELECT * FROM t1 WHERE a < 3 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #16 +CREATE TABLE t1 ( a int PRIMARY KEY ); +--let $query = DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a +--let $select = SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a +--source include/explain_utils.inc +INSERT INTO t1 VALUES (1), (2), (3); +--let $query = DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a +--let $select = SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #17 +CREATE TABLE t1(a INT PRIMARY KEY); +INSERT INTO t1 VALUES (4),(3),(1),(2); +--let $query = DELETE FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1 +--let $select = SELECT * FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #18 +CREATE TABLE t1 (a DATE, b TIME, c INT, KEY c(c), KEY b(b), KEY a(a)); +INSERT INTO t1 VALUES (), (), (), (), (), (), (), (), (), (); +UPDATE t1 SET a = c, b = c; +--let $query = DELETE FROM t1 ORDER BY a ASC, b ASC LIMIT 1 +--let $select = SELECT * FROM t1 ORDER BY a ASC, b ASC LIMIT 1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #19 +CREATE TABLE t1 (a1 INT NOT NULL, b1 INT NOT NULL); +CREATE TABLE t2 (a2 INT NOT NULL, b2 INT NOT NULL, PRIMARY KEY (a2,b2)); +CREATE TABLE t3 (a3 INT NOT NULL, b3 INT NOT NULL, PRIMARY KEY (a3,b3)); +INSERT INTO t1 VALUES (1,1), (2,1), (1,3); +INSERT INTO t2 VALUES (1,1), (2,2), (3,3); +INSERT INTO t3 VALUES (1,1), (2,1), (1,3); +--let $query = DELETE t1,t2,t3 FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3 +--let $select = SELECT * FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3 +--source include/explain_utils.inc +DROP TABLE t1, t2, t3; + +--echo #20 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (a INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 SET a = 10 WHERE a IN (SELECT a FROM t2) +--let $select = SELECT * FROM t1 WHERE a IN (SELECT a FROM t2) +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #21 +CREATE TABLE t1 (a1 INT); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +CREATE TABLE t2 (a2 VARCHAR(10)); +INSERT INTO t2 VALUES (1), (2), (3), (4), (5); +SET @save_optimizer_switch= @@optimizer_switch; +--disable_query_log +if (`select locate('semijoin', @@optimizer_switch) > 0`) +{ + SET @@optimizer_switch= 'semijoin=off'; +} +--enable_query_log +--let $query = DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +--let $select = SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +--source include/explain_utils.inc +SET @@optimizer_switch= @save_optimizer_switch; +TRUNCATE t1; +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +--let $query = DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +--let $select = SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #22 +CREATE TABLE t1 (i INT, j INT); +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +--let $query = UPDATE t1 SET i = 10 +--let $select = SELECT * FROM t1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #23 +CREATE TABLE t1 (i INT, j INT); +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +--let $query = DELETE FROM t1 +--let $select = SELECT * FROM t1 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #24 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +--let $query = DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #25 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (i INT); +--let $query = INSERT INTO t2 SELECT * FROM t1 +--let $select = SELECT * FROM t1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #26 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (i INT); +--let $query = REPLACE INTO t2 SELECT * FROM t1 +--let $select = SELECT * FROM t1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #27 +CREATE TABLE t1 (i INT); +--let $query = INSERT INTO t1 SET i = 10 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #28 +CREATE TABLE t1 (i INT); +--let $query = REPLACE INTO t1 SET i = 10 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #29 +CREATE TABLE t1 (a INT, i INT PRIMARY KEY); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +--let $query = DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--let $select = SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #30 +CREATE TABLE t1(a INT, i CHAR(2), INDEX(i(1))); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +--let $query = DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--let $select = SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #31 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +--let $query = DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #32 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +--let $query = DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #33 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b(1),c)); +INSERT INTO t2 SELECT i, i, i, i FROM t1; +--let $query = DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #34 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b,c)) + ENGINE=HEAP; +INSERT INTO t2 SELECT i, i, i, i FROM t1; +--let $query = DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #35 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35),(36),(37),(38),(39), + (40),(41),(42); +CREATE TABLE t2 (i INT, key1 INT, key2 INT, INDEX (key1), INDEX (key2)); +INSERT INTO t2 (key1, key2) SELECT i, i FROM t1; +--let $query = DELETE FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +--let $select = SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #36 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +--let $query = DELETE FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +--let $select = SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #37 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), INDEX (a, b)); +INSERT INTO t2 SELECT i, i, i FROM t1; +--let $query = DELETE FROM t2 ORDER BY a, b DESC LIMIT 5 +--let $select = SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #38 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c INT, INDEX (a, b)); +INSERT INTO t2 (a, b) SELECT i, i FROM t1; +INSERT INTO t2 (a, b) SELECT t1.i, t1.i FROM t1, t1 x1, t1 x2; +--let $query = DELETE FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +--let $select = SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #39 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +--let $query = UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--let $select = SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #40 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i CHAR(2), INDEX(i(1))); +INSERT INTO t2 (i) SELECT i FROM t1; +--let $query = UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--let $select = SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #41 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +--let $query = UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #42 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +--let $query = UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #43 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b(1),c)); +INSERT INTO t2 SELECT i, i, i, i FROM t1; +--let $query = UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #44 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b,c)) + ENGINE=HEAP; +INSERT INTO t2 SELECT i, i, i, i FROM t1; +--let $query = UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +--let $select = SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #45 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35),(36),(37),(38),(39), + (40),(41),(42); +CREATE TABLE t2 (i INT, key1 INT, key2 INT, INDEX (key1), INDEX (key2)); +INSERT INTO t2 (key1, key2) SELECT i, i FROM t1; +--let $query = UPDATE t2 SET i = 123 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +--let $select = SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #46 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +--let $query = UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +--let $select = SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #47 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), INDEX (a, b)); +INSERT INTO t2 SELECT i, i, i FROM t1; +--let $query = UPDATE t2 SET c = 10 ORDER BY a, b DESC LIMIT 5 +--let $select = SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #48 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), + (20),(21),(22),(23),(24),(25),(26),(27),(28),(29), + (30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c INT, INDEX (a, b)); +INSERT INTO t2 (a, b) SELECT i, i FROM t1; +INSERT INTO t2 (a, b) SELECT t1.i, t1.i FROM t1, t1 x1, t1 x2; +--let $query = UPDATE t2 SET c = 10 ORDER BY a DESC, b DESC LIMIT 5 +--let $select = SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +--let $no_rows = 1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #49 +CREATE TABLE t1 ( + pk INT NOT NULL AUTO_INCREMENT, + c1_idx CHAR(1) DEFAULT 'y', + c2 INT, + PRIMARY KEY (pk), + INDEX c1_idx (c1_idx) +); +INSERT INTO t1 VALUES (1,'y',1), (2,'n',2), (3,'y',3), (4,'n',4); +--let $query = UPDATE t1 SET c2 = 0 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +--let $select = SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +--source include/explain_utils.inc +--let $query = DELETE FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +--let $select = SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #50 +CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY); +INSERT INTO t1 VALUES (),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(); +--let $query = UPDATE t1 SET a=a+10 WHERE a > 34 +--let $select = SELECT * FROM t1 WHERE a > 34 +--source include/explain_utils.inc +DROP TABLE t1; + +--echo #51 +CREATE TABLE t1 (c1 INT, c2 INT, c3 INT); +CREATE TABLE t2 (c1 INT, c2 INT); +INSERT INTO t1 VALUES (1, 1, 10), (2, 2, 20); +--let $query = UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 +--let $select = SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 +--source include/explain_utils.inc +--let $query = UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 WHERE t1.c3 = 10 +--let $select = SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE t1.c3 = 10 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #52 +CREATE TABLE t1(f1 INT, f2 INT); +CREATE TABLE t2(f3 INT, f4 INT); +CREATE INDEX IDX ON t2(f3); +INSERT INTO t1 VALUES(1,0),(2,0); +INSERT INTO t2 VALUES(1,1),(2,2); +--let $query = UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1) +--let $select = SELECT (SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1) FROM t1 +--source include/explain_utils.inc +DROP TABLE t1, t2; + +--echo #55 +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES (1); +SET @a = NULL; +EXPLAIN DELETE FROM t1 WHERE (@a:= a); +if (`SELECT @a IS NOT NULL`) { + die Unexpectedly modified user variable; +} +DROP TABLE t1; + +--echo #56 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +--error ER_BAD_FIELD_ERROR + DELETE FROM t1 USING t1 WHERE uknown_column = 12345; +--error ER_BAD_FIELD_ERROR +EXPLAIN EXTENDED DELETE FROM t1 USING t1 WHERE uknown_column = 12345; +DROP TABLE t1; + +--echo #57 +CREATE TABLE t1(f1 INT); +--error ER_BAD_FIELD_ERROR +EXPLAIN EXTENDED UPDATE t1 SET f2=1 ORDER BY f2; +--error ER_BAD_FIELD_ERROR +UPDATE t1 SET f2=1 ORDER BY f2; +DROP TABLE t1; + +--disable_parsing +--echo #59 +CREATE TABLE t1 ( a INT, KEY( a ) ); +INSERT INTO t1 VALUES (0), (1); +CREATE VIEW v1 AS SELECT t11.a, t12.a AS b FROM t1 t11, t1 t12; +SET SESSION sql_safe_updates = 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +EXPLAIN EXTENDED UPDATE IGNORE v1 SET a = 1; +--error ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE +UPDATE IGNORE v1 SET a = 1; +SET SESSION sql_safe_updates = DEFAULT; +DROP TABLE t1; +DROP VIEW v1; +--enable_parsing + +--echo #62 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0), (1); +CREATE VIEW v1 AS SELECT t11.a, t12.a AS b FROM t1 t11, t1 t12; +--let $query = UPDATE v1 SET a = 1 WHERE a > 0 +--let $select = SELECT * FROM v1 WHERE a > 0 +--source include/explain_utils.inc +--let $query = UPDATE t1, v1 SET v1.a = 1 WHERE t1.a = v1.a +--let $select = SELECT * FROM t1, v1 WHERE t1.a = v1.a +--source include/explain_utils.inc +DROP TABLE t1; +DROP VIEW v1; + +--echo #63 +CREATE TABLE t1 (a INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +CREATE VIEW v1 (a) AS SELECT a FROM t1; +--let $query = DELETE FROM v1 WHERE a < 4 +--let $select = SELECT * FROM v1 WHERE a < 4 +--source include/explain_utils.inc +DROP TABLE t1; +DROP VIEW v1; + +--echo #64 +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1,2), (2,3), (3,4), (4,5), (5,10); +CREATE TABLE t2 (x INT); +INSERT INTO t2 VALUES (1), (2), (3), (4); +CREATE VIEW v1 (a,c) AS SELECT a, b+1 FROM t1; +--let $query = DELETE v1 FROM t2, v1 WHERE t2.x = v1.a +--let $select = SELECT * FROM t2, v1 WHERE t2.x = v1.a +--source include/explain_utils.inc +DROP TABLE t1,t2; +DROP VIEW v1; + +--echo #65 +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1,2), (2,3), (3,4), (4,5), (5,10); +CREATE TABLE t2 (x INT); +INSERT INTO t2 VALUES (1), (2), (3), (4); +CREATE VIEW v1 (a,c) AS SELECT a, b+1 FROM t1; +--let $query = DELETE v1 FROM t2, v1 WHERE t2.x = v1.a +--let $select = SELECT * FROM t2, v1 WHERE t2.x = v1.a +--source include/explain_utils.inc +DROP TABLE t1,t2; +DROP VIEW v1; + +--echo #66 +CREATE TABLE t1 (a INT); +CREATE VIEW v1 (x) AS SELECT a FROM t1; +--let $query = INSERT INTO v1 VALUES (10) +--let $select = SELECT NULL +--source include/explain_utils.inc +DROP TABLE t1; +DROP VIEW v1; + +--echo #67 +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +CREATE VIEW v1 (x) AS SELECT b FROM t2; +--let $query = INSERT INTO v1 SELECT * FROM t1 +--let $select = SELECT * FROM t1 +--source include/explain_utils.inc +DROP TABLE t1, t2; +DROP VIEW v1; + +--echo #68 +CREATE TABLE t1 (i INT); +EXPLAIN INSERT DELAYED INTO t1 VALUES (1); +DROP TABLE t1; + +--echo #69 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +--let $query = UPDATE t1 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--let $select = SELECT * FROM t1 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--source include/explain_utils.inc +--let $query = UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--let $select = SELECT * FROM t1, t2 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--source include/explain_utils.inc +--let $query = UPDATE t1, (SELECT * FROM t2) y SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--let $select = SELECT * FROM t1, (SELECT * FROM t2) y WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +--source include/explain_utils.inc +DROP TABLE t1,t2; + +--echo #70 +CREATE TABLE t1 (c1 INT KEY); +CREATE TABLE t2 (c2 INT); +CREATE TABLE t3 (c3 INT); +EXPLAIN EXTENDED UPDATE t3 SET c3 = ( + SELECT COUNT(d1.c1) + FROM ( + SELECT a11.c1 FROM t1 AS a11 + STRAIGHT_JOIN t2 AS a21 ON a21.c2 = a11.c1 + JOIN t1 AS a12 ON a12.c1 = a11.c1 + ) d1 +); + +DROP TABLE t1, t2, t3; + +--disable_parsing +--echo #71 +# +# Bug: after EXPLAIN bulk INSERT...SELECT and bulk INSERT...SELECT +# to a # MyISAM table the SELECT query may fail with the +# "1030: Got error 124 from storage engine" error message. +# +CREATE TABLE t1 (c1 INT NOT NULL, c2 INT NOT NULL, INDEX i1(c1)); +INSERT INTO t1 VALUES (1,0),(2,0),(3,0),(4,0),(5,0),(6,0),(7,0),(8,0); +--disable_query_log +let $1=7; +SET @d=8; +while ($1) { + eval INSERT INTO t1 SELECT c1 + @d, c2 + @d FROM t1; + eval SET @d = @d*2; + dec $1; +} +--enable_query_log +CREATE TABLE t2 LIKE t1; + +# replace "rows" column for InnoDB +--replace_column 9 X +EXPLAIN INSERT INTO t2 SELECT * FROM t1; +INSERT INTO t2 SELECT * FROM t1; +--disable_result_log +SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1; +--enable_result_log + +DROP TABLE t1, t2; +--enable_parsing + +--echo #73 + +CREATE TABLE t1 (id INT); +CREATE TABLE t2 (id INT); +INSERT INTO t1 VALUES (1), (2); + +EXPLAIN SELECT * FROM t1 LEFT JOIN t2 USING(id) GROUP BY t1.id; + +DROP TABLE t1,t2; + +--echo #74 + +CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); + +--echo # used key is modified & Using temporary + +--let $query = UPDATE t1 SET a=a+1 WHERE a>10 +--let $select = SELECT a t1 FROM t1 WHERE a>10 +--source include/explain_utils.inc + +--echo # used key is modified & Using filesort + +--let $query = UPDATE t1 SET a=a+1 WHERE a>10 ORDER BY a+20 +--let $select = SELECT a t1 FROM t1 WHERE a>10 ORDER BY a+20 +--source include/explain_utils.inc + +DROP TABLE t1; + +--echo # +--echo # Bug #12949629: CLIENT LOSES CONNECTION AFTER EXECUTING A PROCEDURE WITH +--echo # EXPLAIN UPDATE/DEL/INS +--echo # + +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); + +--delimiter | +CREATE PROCEDURE p1() BEGIN EXPLAIN INSERT INTO t1 VALUES (1);END| +CREATE PROCEDURE p2() BEGIN INSERT INTO t1 VALUES (1);END| +CREATE PROCEDURE p3() BEGIN EXPLAIN INSERT INTO t1 SELECT 1;END| +CREATE PROCEDURE p4() BEGIN INSERT INTO t1 SELECT 1;END| +CREATE PROCEDURE p5() BEGIN EXPLAIN REPLACE INTO t1 VALUES (1);END| +CREATE PROCEDURE p6() BEGIN REPLACE INTO t1 VALUES (1);END| +CREATE PROCEDURE p7() BEGIN EXPLAIN REPLACE INTO t1 SELECT 1;END| +CREATE PROCEDURE p8() BEGIN REPLACE INTO t1 SELECT 1;END| +CREATE PROCEDURE p9() BEGIN EXPLAIN UPDATE t1 SET i = 10;END| +CREATE PROCEDURE p10() BEGIN UPDATE t1 SET i = 10;END| +CREATE PROCEDURE p11() BEGIN EXPLAIN UPDATE t1,t2 SET t1.i = 10 WHERE t1.i = t2.i ;END| +CREATE PROCEDURE p12() BEGIN UPDATE t1,t2 SET t1.i = 10 WHERE t1.i = t2.i ;END| +CREATE PROCEDURE p13() BEGIN EXPLAIN DELETE FROM t1;END| +CREATE PROCEDURE p14() BEGIN DELETE FROM t1;END| +CREATE PROCEDURE p15() BEGIN EXPLAIN DELETE FROM t1 USING t1;END| +CREATE PROCEDURE p16() BEGIN DELETE FROM t1 USING t1;END| +--delimiter ; + +let $i=16; +while($i) { + eval CALL p$i(); + eval DROP PROCEDURE p$i; + dec $i; +} + +DROP TABLE t1, t2; + +--echo # + +-- disable_query_log +-- disable_result_log +# SET GLOBAL innodb_stats_persistent=default; +-- enable_result_log +-- enable_query_log diff --git a/mysql-test/include/explain_utils.inc b/mysql-test/include/explain_utils.inc new file mode 100644 index 00000000000..505798e432a --- /dev/null +++ b/mysql-test/include/explain_utils.inc @@ -0,0 +1,161 @@ +# This file is a collection of utility tests +# for WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE. +# +# Since MTR doesn't have functions, we use this file instead +# including it many times. +# +# Parameters: +# +# $query: INSERT/REPLACE/UPDATE/DELETE query to explain +# NOTE: this file resets this variable +# +# $select: may be empty; the SELECT query similar to $query +# We use it to compare: +# 1) table data before and after EXPLAIN $query evaluation; +# 2) EXPLAIN $query and EXPLAIN $select output and +# handler/filesort statistics +# NOTE: this file resets this variable +# $innodb: take $no_rows parameter into account if not 0; +# $no_rows: filter out "rows" and "filtered" columns of EXPLAIN if not 0; +# it may be necessary for InnoDB tables since InnoDB's table row +# counter can't return precise and repeatable values; +# NOTE: ANALYZE TABLE doesn't help +# NOTE: this file resets this variable + +--echo # +--echo # query: $query +--echo # select: $select +--echo # + +if ($select) { +--disable_query_log +--eval $select INTO OUTFILE '$MYSQLTEST_VARDIR/tmp/before_explain.txt' +--enable_query_log +} + +if ($innodb) { + if ($no_rows) { +--replace_column 9 X + } +} +--eval EXPLAIN $query +if (`SELECT ROW_COUNT() > 0`) { +--echo # Erroneous query: EXPLAIN $query +--die Unexpected ROW_COUNT() <> 0 +} + +FLUSH STATUS; +FLUSH TABLES; +if ($innodb) { + if ($no_rows) { +--replace_column 9 X 10 X + } +} +--eval EXPLAIN EXTENDED $query +if (`SELECT ROW_COUNT() > 0`) { +--echo # Erroneous query: EXPLAIN EXTENDED $query +--die Unexpected ROW_COUNT() <> 0 +} +--echo # Status of EXPLAIN EXTENDED query +--disable_query_log +SHOW STATUS WHERE (Variable_name LIKE 'Sort%' OR + Variable_name LIKE 'Handler_read_%' OR + Variable_name = 'Handler_write' OR + Variable_name = 'Handler_update' OR + Variable_name = 'Handler_delete') AND Value <> 0; +--enable_query_log + +if ($json) { +if ($innodb) { + if ($no_rows) { +--replace_regex /"rows": [0-9]+/"rows": "X"/ /"filtered": [0-9.]+/"filtered": "X"/ + } +} +--eval EXPLAIN FORMAT=JSON $query; +if ($validation) { +--disable_query_log +--replace_result $MASTER_MYSOCK MASTER_MYSOCK +--exec $MYSQL -S $MASTER_MYSOCK -u root -r test -e "EXPLAIN FORMAT=JSON $query;" > $MYSQLTEST_VARDIR/tmp/explain.json +--replace_regex /[-]*// /FILE.[\/\\:_\.0-9A-Za-z]*/Validation:/ +--exec python $MYSQL_TEST_DIR/suite/opt_trace/validate_json.py $MYSQLTEST_VARDIR/tmp/explain.json +--remove_file '$MYSQLTEST_VARDIR/tmp/explain.json' +--enable_query_log +} +} + +if ($select) { +FLUSH STATUS; +FLUSH TABLES; +if ($innodb) { + if ($no_rows) { +--replace_column 9 X 10 X + } +} +--eval EXPLAIN EXTENDED $select +--echo # Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +--disable_query_log +SHOW STATUS WHERE (Variable_name LIKE 'Sort%' OR + Variable_name LIKE 'Handler_read_%' OR + Variable_name = 'Handler_write' OR + Variable_name = 'Handler_update' OR + Variable_name = 'Handler_delete') AND Value <> 0; +--enable_query_log +if ($json) { +if ($innodb) { + if ($no_rows) { +--replace_regex /"rows": [0-9]+/"rows": "X"/ /"filtered": [0-9.]+/"filtered": "X"/ + } +} +--eval EXPLAIN FORMAT=JSON $select; +if ($validation) { +--disable_query_log +--replace_result $MASTER_MYSOCK MASTER_MYSOCK +--exec $MYSQL -S $MASTER_MYSOCK -u root -r test -e "EXPLAIN FORMAT=JSON $select;" > $MYSQLTEST_VARDIR/tmp/explain.json +--replace_regex /[-]*// /FILE.[\/\\:_\.0-9A-Za-z]*/Validation:/ +--exec python $MYSQL_TEST_DIR/suite/opt_trace/validate_json.py $MYSQLTEST_VARDIR/tmp/explain.json +--remove_file '$MYSQLTEST_VARDIR/tmp/explain.json' +--enable_query_log +} +} +} + +--disable_query_log + +if ($select) { +--eval $select INTO OUTFILE '$MYSQLTEST_VARDIR/tmp/after_explain.txt' +--diff_files '$MYSQLTEST_VARDIR/tmp/before_explain.txt' '$MYSQLTEST_VARDIR/tmp/after_explain.txt' +--remove_file '$MYSQLTEST_VARDIR/tmp/before_explain.txt' +--remove_file '$MYSQLTEST_VARDIR/tmp/after_explain.txt' +} + +FLUSH STATUS; +FLUSH TABLES; +if ($select) { +--disable_result_log +--eval $select +--enable_result_log +--echo # Status of "equivalent" SELECT query execution: +SHOW STATUS WHERE (Variable_name LIKE 'Sort%' OR + Variable_name LIKE 'Handler_read_%' OR + Variable_name = 'Handler_write' OR + Variable_name = 'Handler_update' OR + Variable_name = 'Handler_delete') AND Value <> 0; +} + +FLUSH STATUS; +FLUSH TABLES; +--eval $query +--echo # Status of testing query execution: +SHOW STATUS WHERE (Variable_name LIKE 'Sort%' OR + Variable_name LIKE 'Handler_read_%' OR + Variable_name = 'Handler_write' OR + Variable_name = 'Handler_update' OR + Variable_name = 'Handler_delete') AND Value <> 0; + +--let $query= +--let $select= +--let $no_rows= + +--enable_query_log + +--echo diff --git a/mysql-test/r/myisam_explain_non_select_all.result b/mysql-test/r/myisam_explain_non_select_all.result new file mode 100644 index 00000000000..167e9129957 --- /dev/null +++ b/mysql-test/r/myisam_explain_non_select_all.result @@ -0,0 +1,2903 @@ +set @save_storage_engine= @@session.default_storage_engine; +set session default_storage_engine = MyISAM; +#1 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: UPDATE t1 SET a = 10 WHERE a < 10 +# select: SELECT * FROM t1 WHERE a < 10 +# +EXPLAIN UPDATE t1 SET a = 10 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` < 10) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 4 +Handler_update 3 + +DROP TABLE t1; +#2 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 WHERE a < 10 +# select: SELECT * FROM t1 WHERE a < 10 +# +EXPLAIN DELETE FROM t1 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a < 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` < 10) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 3 +Handler_read_rnd_next 4 + +DROP TABLE t1; +#3 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 USING t1 WHERE a = 1 +# select: SELECT * FROM t1 WHERE a = 1 +# +EXPLAIN DELETE FROM t1 USING t1 WHERE a = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 USING t1 WHERE a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` = 1) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd_next 4 + +DROP TABLE t1; +#4 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1, t2 SET t1.a = 10 WHERE t1.a = 1 +# select: SELECT * FROM t1, t2 WHERE t1.a = 1 +# +EXPLAIN UPDATE t1, t2 SET t1.a = 10 WHERE t1.a = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1, t2 SET t1.a = 10 WHERE t1.a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1, t2 WHERE t1.a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` join `test`.`t2` where (`test`.`t1`.`a` = 1) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 8 +Handler_update 1 + +DROP TABLE t1, t2; +#5 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a = 1 +# select: SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a = 1 +# +EXPLAIN UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a = 1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 Using where +1 PRIMARY ALL NULL NULL NULL NULL 3 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 100.00 Using where +1 PRIMARY ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a = 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t11`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` `t11` join `test`.`t2` where (`test`.`t11`.`a` = 1) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 12 +Handler_update 1 + +DROP TABLE t1, t2; +#6 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3) +# select: SELECT * FROM t1 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3) +# +EXPLAIN UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +2 SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +2 SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +Handler_read_rnd_next 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE 1 IN (SELECT 1 FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` semi join (`test`.`t2`) where (`test`.`t2`.`b` < 3) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 5 +Handler_update 3 + +DROP TABLE t1, t2; +#7 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3) +# select: SELECT * FROM t1 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3) +# +EXPLAIN UPDATE t1 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a IN (SELECT b FROM t2 WHERE t1.a < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using where; FirstMatch(t1); Using join buffer (flat, BNL join) +Warnings: +Note 1276 Field or reference 'test.t1.a' of SELECT #2 was resolved in SELECT #1 +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` semi join (`test`.`t2`) where ((`test`.`t2`.`b` = `test`.`t1`.`a`) and (`test`.`t1`.`a` < 3)) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 7 +Handler_update 2 + +DROP TABLE t1, t2; +#7 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3) +# select: SELECT * FROM t1, t2 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3) +# +EXPLAIN UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1, t2 WHERE a IN (SELECT b FROM t2 WHERE t2.b < 3); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` semi join (`test`.`t2`) join `test`.`t2` where ((`test`.`t2`.`b` < 3)) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 12 +# Status of testing query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 16 +Handler_update 2 + +DROP TABLE t1, t2; +#8 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = t11.a + 10 +# select: SELECT * FROM t1 t11, (SELECT * FROM t2) t12 +# +EXPLAIN UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = t11.a + 10; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 +1 PRIMARY ALL NULL NULL NULL NULL 3 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = t11.a + 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 t11, (SELECT * FROM t2) t12; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t11`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` `t11` join `test`.`t2` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 3 +Handler_read_rnd_deleted 1 +Handler_read_rnd_next 24 +Handler_update 3 + +DROP TABLE t1, t2; +#9 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 t11, (SELECT 1 FROM DUAL) t12 SET t11.a = t11.a + 10 +# select: SELECT * FROM t1 t11, (SELECT 1 FROM DUAL) t12 +# +EXPLAIN UPDATE t1 t11, (SELECT 1 FROM DUAL) t12 SET t11.a = t11.a + 10; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY system NULL NULL NULL NULL 1 +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 +2 DERIVED NULL NULL NULL NULL NULL NULL NULL No tables used +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 t11, (SELECT 1 FROM DUAL) t12 SET t11.a = t11.a + 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY system NULL NULL NULL NULL 1 100.00 +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED NULL NULL NULL NULL NULL NULL NULL NULL No tables used +# Status of EXPLAIN EXTENDED query +Variable_name Value +Handler_read_rnd_next 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 t11, (SELECT 1 FROM DUAL) t12; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY system NULL NULL NULL NULL 1 100.00 +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select `test`.`t11`.`a` AS `a`,1 AS `1` from `test`.`t1` `t11` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_rnd_next 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 5 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 3 +Handler_read_rnd_next 9 +Handler_update 3 + +DROP TABLE t1, t2; +#10 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a > 1 +# select: SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a > 1 +# +EXPLAIN UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a > 1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 Using where +1 PRIMARY ALL NULL NULL NULL NULL 3 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 t11, (SELECT * FROM t2) t12 SET t11.a = 10 WHERE t11.a > 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t11 ALL NULL NULL NULL NULL 3 100.00 Using where +1 PRIMARY ALL NULL NULL NULL NULL 3 100.00 +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 t11, (SELECT * FROM t2) t12 WHERE t11.a > 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 3 100.00 Using where +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t11`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` `t11` join `test`.`t2` where (`test`.`t11`.`a` > 1) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 16 +Handler_update 2 + +DROP TABLE t1, t2; +#11 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 WHERE a > 1 LIMIT 1 +# select: SELECT * FROM t1 WHERE a > 1 LIMIT 1 +# +EXPLAIN DELETE FROM t1 WHERE a > 1 LIMIT 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a > 1 LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a > 1 LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` > 1) limit 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 2 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd_next 2 + +DROP TABLE t1; +#12 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 WHERE 0 +# select: SELECT * FROM t1 WHERE 0 +# +EXPLAIN DELETE FROM t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where 0 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +# Status of testing query execution: +Variable_name Value + +DROP TABLE t1; +#13 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 USING t1 WHERE 0 +# select: SELECT * FROM t1 WHERE 0 +# +EXPLAIN DELETE FROM t1 USING t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 USING t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where 0 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +# Status of testing query execution: +Variable_name Value + +DROP TABLE t1; +#14 +CREATE TABLE t1 (a INT, b INT, UNIQUE KEY (a), KEY (b)); +INSERT INTO t1 VALUES (3, 3), (7, 7); +# +# query: DELETE FROM t1 WHERE a = 3 +# select: SELECT * FROM t1 WHERE a = 3 +# +EXPLAIN DELETE FROM t1 WHERE a = 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a = 3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range a a 5 NULL 1 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a = 3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 const a a 5 const 1 100.00 +Warnings: +Note 1003 select 3 AS `a`,3 AS `b` from `test`.`t1` where 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_key 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_key 1 + +DROP TABLE t1; +#15 +CREATE TABLE t1 (a INT, b INT, UNIQUE KEY (a), KEY (b)); +INSERT INTO t1 VALUES (3, 3), (7, 7); +# +# query: DELETE FROM t1 WHERE a < 3 +# select: SELECT * FROM t1 WHERE a < 3 +# +EXPLAIN DELETE FROM t1 WHERE a < 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a < 3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range a a 5 NULL 1 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a < 3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range a a 5 NULL 1 100.00 Using index condition +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` where (`test`.`t1`.`a` < 3) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 + +DROP TABLE t1; +#16 +CREATE TABLE t1 ( a int PRIMARY KEY ); +# +# query: DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a +# select: SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a +# +EXPLAIN DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +Warnings: +Note 1003 select NULL AS `a` from `test`.`t1` where 0 order by NULL +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_rnd_next 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 + +INSERT INTO t1 VALUES (1), (2), (3); +# +# query: DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a +# select: SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a +# +EXPLAIN DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a > 0 ORDER BY t1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 3 100.00 Using where; Using index +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` > 0) order by `test`.`t1`.`a` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 3 +# Status of testing query execution: +Variable_name Value +Handler_delete 3 +Handler_read_key 1 +Handler_read_next 3 + +DROP TABLE t1; +#17 +CREATE TABLE t1(a INT PRIMARY KEY); +INSERT INTO t1 VALUES (4),(3),(1),(2); +# +# query: DELETE FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1 +# select: SELECT * FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1 +# +EXPLAIN DELETE FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL PRIMARY 4 NULL 1 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 index NULL PRIMARY 4 NULL 1 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE (@a:= a) ORDER BY a LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 index NULL PRIMARY 4 NULL 1 100.00 Using where; Using index +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (@a:=`test`.`t1`.`a`) order by `test`.`t1`.`a` limit 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_first 1 + +DROP TABLE t1; +#18 +CREATE TABLE t1 (a DATE, b TIME, c INT, KEY c(c), KEY b(b), KEY a(a)); +INSERT INTO t1 VALUES (), (), (), (), (), (), (), (), (), (); +UPDATE t1 SET a = c, b = c; +# +# query: DELETE FROM t1 ORDER BY a ASC, b ASC LIMIT 1 +# select: SELECT * FROM t1 ORDER BY a ASC, b ASC LIMIT 1 +# +EXPLAIN DELETE FROM t1 ORDER BY a ASC, b ASC LIMIT 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 ORDER BY a ASC, b ASC LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 100.00 Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 ORDER BY a ASC, b ASC LIMIT 1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 10 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t1`.`c` AS `c` from `test`.`t1` order by `test`.`t1`.`a`,`test`.`t1`.`b` limit 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 11 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd 1 +Handler_read_rnd_next 11 +Sort_rows 10 +Sort_scan 1 + +DROP TABLE t1; +#19 +CREATE TABLE t1 (a1 INT NOT NULL, b1 INT NOT NULL); +CREATE TABLE t2 (a2 INT NOT NULL, b2 INT NOT NULL, PRIMARY KEY (a2,b2)); +CREATE TABLE t3 (a3 INT NOT NULL, b3 INT NOT NULL, PRIMARY KEY (a3,b3)); +INSERT INTO t1 VALUES (1,1), (2,1), (1,3); +INSERT INTO t2 VALUES (1,1), (2,2), (3,3); +INSERT INTO t3 VALUES (1,1), (2,1), (1,3); +# +# query: DELETE t1,t2,t3 FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3 +# select: SELECT * FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3 +# +EXPLAIN DELETE t1,t2,t3 FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 +1 SIMPLE t2 ref PRIMARY PRIMARY 4 test.t1.a1 1 +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 8 test.t2.b2,test.t1.b1 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE t1,t2,t3 FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ref PRIMARY PRIMARY 4 test.t1.a1 1 100.00 +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 8 test.t2.b2,test.t1.b1 1 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1,t2,t3 WHERE a1=a2 AND b2=a3 AND b1=b3; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +1 SIMPLE t2 ref PRIMARY PRIMARY 4 test.t1.a1 1 100.00 Using index +1 SIMPLE t3 eq_ref PRIMARY PRIMARY 8 test.t2.b2,test.t1.b1 1 100.00 Using index +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`b1` AS `b1`,`test`.`t2`.`a2` AS `a2`,`test`.`t2`.`b2` AS `b2`,`test`.`t3`.`a3` AS `a3`,`test`.`t3`.`b3` AS `b3` from `test`.`t1` join `test`.`t2` join `test`.`t3` where ((`test`.`t2`.`a2` = `test`.`t1`.`a1`) and (`test`.`t3`.`a3` = `test`.`t2`.`b2`) and (`test`.`t3`.`b3` = `test`.`t1`.`b1`)) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 6 +Handler_read_next 3 +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 8 +Handler_read_key 6 +Handler_read_next 3 +Handler_read_rnd 5 +Handler_read_rnd_next 4 + +DROP TABLE t1, t2, t3; +#20 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (a INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 SET a = 10 WHERE a IN (SELECT a FROM t2) +# select: SELECT * FROM t1 WHERE a IN (SELECT a FROM t2) +# +EXPLAIN UPDATE t1 SET a = 10 WHERE a IN (SELECT a FROM t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE a IN (SELECT a FROM t2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a IN (SELECT a FROM t2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +2 MATERIALIZED t2 ALL NULL NULL NULL NULL 3 100.00 +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` semi join (`test`.`t2`) where 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 8 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 10 +Handler_update 3 + +DROP TABLE t1, t2; +#21 +CREATE TABLE t1 (a1 INT); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +CREATE TABLE t2 (a2 VARCHAR(10)); +INSERT INTO t2 VALUES (1), (2), (3), (4), (5); +SET @save_optimizer_switch= @@optimizer_switch; +# +# query: DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +# select: SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +# +EXPLAIN DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1` from `test`.`t1` where <`test`.`t1`.`a1`>((`test`.`t1`.`a1`,(select `test`.`t2`.`a2` from `test`.`t2` where ((`test`.`t2`.`a2` > 2) and ((`test`.`t1`.`a1`) = `test`.`t2`.`a2`))))) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 5 +Handler_read_rnd_next 30 +# Status of testing query execution: +Variable_name Value +Handler_delete 3 +Handler_read_rnd_next 30 + +SET @@optimizer_switch= @save_optimizer_switch; +TRUNCATE t1; +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +# +# query: DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +# select: SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2) +# +EXPLAIN DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a1 IN (SELECT a2 FROM t2 WHERE a2 > 2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 5 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; FirstMatch(t1); Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1` from `test`.`t1` semi join (`test`.`t2`) where ((`test`.`t2`.`a2` > 2) and (`test`.`t1`.`a1` = `test`.`t2`.`a2`)) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 12 +# Status of testing query execution: +Variable_name Value +Handler_delete 3 +Handler_read_rnd_next 30 + +DROP TABLE t1, t2; +#22 +CREATE TABLE t1 (i INT, j INT); +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +# +# query: UPDATE t1 SET i = 10 +# select: SELECT * FROM t1 +# +EXPLAIN UPDATE t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 100.00 +Warnings: +Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`j` AS `j` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 6 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 6 +Handler_update 5 + +DROP TABLE t1; +#23 +CREATE TABLE t1 (i INT, j INT); +INSERT INTO t1 VALUES (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +# +# query: DELETE FROM t1 +# select: SELECT * FROM t1 +# +EXPLAIN DELETE FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL 5 Deleting all rows +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL 5 NULL Deleting all rows +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 5 100.00 +Warnings: +Note 1003 select `test`.`t1`.`i` AS `i`,`test`.`t1`.`j` AS `j` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 6 +# Status of testing query execution: +Variable_name Value + +DROP TABLE t1; +#24 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +# +# query: DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index NULL a 15 NULL 5 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_first 1 +Handler_read_next 4 + +DROP TABLE t1, t2; +#25 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (i INT); +# +# query: INSERT INTO t2 SELECT * FROM t1 +# select: SELECT * FROM t1 +# +EXPLAIN INSERT INTO t2 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED INSERT INTO t2 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +Warnings: +Note 1003 select `test`.`t1`.`i` AS `i` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 4 +Handler_write 3 + +DROP TABLE t1, t2; +#26 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (i INT); +# +# query: REPLACE INTO t2 SELECT * FROM t1 +# select: SELECT * FROM t1 +# +EXPLAIN REPLACE INTO t2 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED REPLACE INTO t2 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 +Warnings: +Note 1003 select `test`.`t1`.`i` AS `i` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 4 +Handler_write 3 + +DROP TABLE t1, t2; +#27 +CREATE TABLE t1 (i INT); +# +# query: INSERT INTO t1 SET i = 10 +# select: +# +EXPLAIN INSERT INTO t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED INSERT INTO t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL 100.00 NULL +# Status of EXPLAIN EXTENDED query +Variable_name Value +# Status of testing query execution: +Variable_name Value +Handler_write 1 + +DROP TABLE t1; +#28 +CREATE TABLE t1 (i INT); +# +# query: REPLACE INTO t1 SET i = 10 +# select: +# +EXPLAIN REPLACE INTO t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED REPLACE INTO t1 SET i = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL 100.00 NULL +# Status of EXPLAIN EXTENDED query +Variable_name Value +# Status of testing query execution: +Variable_name Value +Handler_write 1 + +DROP TABLE t1; +#29 +CREATE TABLE t1 (a INT, i INT PRIMARY KEY); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +# +# query: DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# select: SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# +EXPLAIN DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 8 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 8 100.00 Using index condition +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`i` AS `i` from `test`.`t1` where ((`test`.`t1`.`i` > 10) and (`test`.`t1`.`i` <= 18)) order by `test`.`t1`.`i` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_key 1 +Handler_read_next 4 + +DROP TABLE t1; +#30 +CREATE TABLE t1(a INT, i CHAR(2), INDEX(i(1))); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +# +# query: DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# select: SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# +EXPLAIN DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL i NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`i` AS `i` from `test`.`t1` where ((`test`.`t1`.`i` > 10) and (`test`.`t1`.`i` <= 18)) order by `test`.`t1`.`i` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 5 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_rnd 5 +Handler_read_rnd_next 27 +Sort_rows 8 +Sort_scan 1 + +DROP TABLE t1; +#31 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +# +# query: DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#32 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +# +# query: DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index NULL a 15 NULL 5 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_first 1 +Handler_read_next 4 + +DROP TABLE t1, t2; +#33 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b(1),c)); +INSERT INTO t2 SELECT i, i, i, i FROM t1; +# +# query: DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#34 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b,c)) +ENGINE=HEAP; +INSERT INTO t2 SELECT i, i, i, i FROM t1; +# +# query: DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 1 +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#35 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35),(36),(37),(38),(39), +(40),(41),(42); +CREATE TABLE t2 (i INT, key1 INT, key2 INT, INDEX (key1), INDEX (key2)); +INSERT INTO t2 (key1, key2) SELECT i, i FROM t1; +# +# query: DELETE FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +# select: SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +# +EXPLAIN DELETE FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 Using sort_union(key1,key2); Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 100.00 Using sort_union(key1,key2); Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 100.00 Using sort_union(key1,key2); Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`i` AS `i`,`test`.`t2`.`key1` AS `key1`,`test`.`t2`.`key2` AS `key2` from `test`.`t2` where ((`test`.`t2`.`key1` < 13) or (`test`.`t2`.`key2` < 14)) order by `test`.`t2`.`key1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 2 +Handler_read_next 7 +Handler_read_rnd 4 +Sort_range 1 +Sort_rows 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 4 +Handler_read_key 2 +Handler_read_next 7 +Handler_read_rnd 8 +Sort_range 1 +Sort_rows 4 + +DROP TABLE t1, t2; +#36 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +# +# query: DELETE FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +# select: SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +# +EXPLAIN DELETE FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where ((`test`.`t2`.`i` > 10) and (`test`.`t2`.`i` <= 18)) order by `test`.`t2`.`i` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_prev 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_key 1 +Handler_read_prev 4 + +DROP TABLE t1, t2; +#37 +CREATE TABLE t1 (i INT); +INSERT INTO t1 (i) VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), INDEX (a, b)); +INSERT INTO t2 SELECT i, i, i FROM t1; +# +# query: DELETE FROM t2 ORDER BY a, b DESC LIMIT 5 +# select: SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5 +# +EXPLAIN DELETE FROM t2 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` order by `test`.`t2`.`a`,`test`.`t2`.`b` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 5 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_rnd 5 +Handler_read_rnd_next 27 +Sort_rows 26 +Sort_scan 1 + +DROP TABLE t1, t2; +#38 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c INT, INDEX (a, b)); +INSERT INTO t2 (a, b) SELECT i, i FROM t1; +INSERT INTO t2 (a, b) SELECT t1.i, t1.i FROM t1, t1 x1, t1 x2; +# +# query: DELETE FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +# select: SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +# +EXPLAIN DELETE FROM t2 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index NULL a 6 NULL 5 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t2 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 6 NULL 5 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 6 NULL 5 100.00 +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` order by `test`.`t2`.`a` desc,`test`.`t2`.`b` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_last 1 +Handler_read_prev 4 +# Status of testing query execution: +Variable_name Value +Handler_delete 5 +Handler_read_last 1 +Handler_read_prev 4 + +DROP TABLE t1, t2; +#39 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +# +# query: UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# select: SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# +EXPLAIN UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 Using where; Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where; Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using index condition +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where ((`test`.`t2`.`i` > 10) and (`test`.`t2`.`i` <= 18)) order by `test`.`t2`.`i` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 4 +Handler_read_rnd 5 +Handler_update 5 + +DROP TABLE t1, t2; +#40 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i CHAR(2), INDEX(i(1))); +INSERT INTO t2 (i) SELECT i FROM t1; +# +# query: UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# select: SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5 +# +EXPLAIN UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL i NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where ((`test`.`t2`.`i` > 10) and (`test`.`t2`.`i` <= 18)) order by `test`.`t2`.`i` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 5 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 5 +Handler_read_rnd_next 27 +Handler_update 5 +Sort_rows 5 +Sort_scan 1 + +DROP TABLE t1, t2; +#41 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +# +# query: UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Handler_update 1 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#42 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, b INT, c INT, d INT, INDEX(a, b, c)); +INSERT INTO t2 (a, b, c) SELECT i, i, i FROM t1; +INSERT INTO t2 (a, b, c) SELECT t1.i, t1.i, t1.i FROM t1, t1 x1, t1 x2; +# +# query: UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index NULL a 15 NULL 5 Using where; Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where; Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 15 NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 4 +Handler_read_rnd 5 +Handler_update 5 + +DROP TABLE t1, t2; +#43 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b(1),c)); +INSERT INTO t2 SELECT i, i, i, i FROM t1; +# +# query: UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#44 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), d CHAR(2), INDEX (a,b,c)) +ENGINE=HEAP; +INSERT INTO t2 SELECT i, i, i, i FROM t1; +# +# query: UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5 +# select: SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5 +# +EXPLAIN UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET d = 10 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE b = 10 ORDER BY a, c LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c`,`test`.`t2`.`d` AS `d` from `test`.`t2` where (`test`.`t2`.`b` = 10) order by `test`.`t2`.`a`,`test`.`t2`.`c` limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_next 27 +Sort_rows 1 +Sort_scan 1 + +DROP TABLE t1, t2; +#45 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35),(36),(37),(38),(39), +(40),(41),(42); +CREATE TABLE t2 (i INT, key1 INT, key2 INT, INDEX (key1), INDEX (key2)); +INSERT INTO t2 (key1, key2) SELECT i, i FROM t1; +# +# query: UPDATE t2 SET i = 123 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +# select: SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1 +# +EXPLAIN UPDATE t2 SET i = 123 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 Using sort_union(key1,key2); Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET i = 123 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 100.00 Using sort_union(key1,key2); Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE key1 < 13 or key2 < 14 ORDER BY key1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index_merge key1,key2 key1,key2 5,5 NULL 7 100.00 Using sort_union(key1,key2); Using where; Using filesort +Warnings: +Note 1003 select `test`.`t2`.`i` AS `i`,`test`.`t2`.`key1` AS `key1`,`test`.`t2`.`key2` AS `key2` from `test`.`t2` where ((`test`.`t2`.`key1` < 13) or (`test`.`t2`.`key2` < 14)) order by `test`.`t2`.`key1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 2 +Handler_read_next 7 +Handler_read_rnd 4 +Sort_range 1 +Sort_rows 4 +# Status of testing query execution: +Variable_name Value +Handler_read_key 2 +Handler_read_next 7 +Handler_read_rnd 8 +Handler_update 4 +Sort_range 1 +Sort_rows 4 + +DROP TABLE t1, t2; +#46 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2(a INT, i INT PRIMARY KEY); +INSERT INTO t2 (i) SELECT i FROM t1; +# +# query: UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +# select: SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5 +# +EXPLAIN UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 Using where; Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET a = 10 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where; Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 WHERE i > 10 AND i <= 18 ORDER BY i DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 range PRIMARY PRIMARY 4 NULL 8 100.00 Using where +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`i` AS `i` from `test`.`t2` where ((`test`.`t2`.`i` > 10) and (`test`.`t2`.`i` <= 18)) order by `test`.`t2`.`i` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_prev 4 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_prev 4 +Handler_read_rnd 5 +Handler_update 5 + +DROP TABLE t1, t2; +#47 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c CHAR(2), INDEX (a, b)); +INSERT INTO t2 SELECT i, i, i FROM t1; +# +# query: UPDATE t2 SET c = 10 ORDER BY a, b DESC LIMIT 5 +# select: SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5 +# +EXPLAIN UPDATE t2 SET c = 10 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET c = 10 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 ORDER BY a, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 26 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` order by `test`.`t2`.`a`,`test`.`t2`.`b` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 27 +Sort_rows 5 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 5 +Handler_read_rnd_next 27 +Handler_update 4 +Sort_rows 5 +Sort_scan 1 + +DROP TABLE t1, t2; +#48 +CREATE TABLE t1 (i INT); +INSERT INTO t1 VALUES (10),(11),(12),(13),(14),(15),(16),(17),(18),(19), +(20),(21),(22),(23),(24),(25),(26),(27),(28),(29), +(30),(31),(32),(33),(34),(35); +CREATE TABLE t2 (a CHAR(2), b CHAR(2), c INT, INDEX (a, b)); +INSERT INTO t2 (a, b) SELECT i, i FROM t1; +INSERT INTO t2 (a, b) SELECT t1.i, t1.i FROM t1, t1 x1, t1 x2; +# +# query: UPDATE t2 SET c = 10 ORDER BY a DESC, b DESC LIMIT 5 +# select: SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5 +# +EXPLAIN UPDATE t2 SET c = 10 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 index NULL a 6 NULL 5 Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t2 SET c = 10 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 6 NULL 5 100.00 Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2 ORDER BY a DESC, b DESC LIMIT 5; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 index NULL a 6 NULL 5 100.00 +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b`,`test`.`t2`.`c` AS `c` from `test`.`t2` order by `test`.`t2`.`a` desc,`test`.`t2`.`b` desc limit 5 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_last 1 +Handler_read_prev 4 +# Status of testing query execution: +Variable_name Value +Handler_read_last 1 +Handler_read_prev 4 +Handler_read_rnd 5 +Handler_update 5 + +DROP TABLE t1, t2; +#49 +CREATE TABLE t1 ( +pk INT NOT NULL AUTO_INCREMENT, +c1_idx CHAR(1) DEFAULT 'y', +c2 INT, +PRIMARY KEY (pk), +INDEX c1_idx (c1_idx) +); +INSERT INTO t1 VALUES (1,'y',1), (2,'n',2), (3,'y',3), (4,'n',4); +# +# query: UPDATE t1 SET c2 = 0 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +# select: SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +# +EXPLAIN UPDATE t1 SET c2 = 0 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range c1_idx c1_idx 2 NULL 2 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET c2 = 0 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range c1_idx c1_idx 2 NULL 2 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ref c1_idx c1_idx 2 const 2 100.00 Using index condition; Using where; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`pk` AS `pk`,`test`.`t1`.`c1_idx` AS `c1_idx`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` where (`test`.`t1`.`c1_idx` = 'y') order by `test`.`t1`.`pk` desc limit 2 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 2 +Sort_range 1 +Sort_rows 2 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 2 +Handler_read_rnd 2 +Handler_update 2 +Sort_range 1 +Sort_rows 2 + +# +# query: DELETE FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +# select: SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2 +# +EXPLAIN DELETE FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range c1_idx c1_idx 2 NULL 2 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range c1_idx c1_idx 2 NULL 2 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE c1_idx = 'y' ORDER BY pk DESC LIMIT 2; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ref c1_idx c1_idx 2 const 2 100.00 Using index condition; Using where; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`pk` AS `pk`,`test`.`t1`.`c1_idx` AS `c1_idx`,`test`.`t1`.`c2` AS `c2` from `test`.`t1` where (`test`.`t1`.`c1_idx` = 'y') order by `test`.`t1`.`pk` desc limit 2 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 2 +Sort_range 1 +Sort_rows 2 +# Status of testing query execution: +Variable_name Value +Handler_delete 2 +Handler_read_key 1 +Handler_read_next 2 +Handler_read_rnd 2 +Sort_range 1 +Sort_rows 2 + +DROP TABLE t1; +#50 +CREATE TABLE t1 (a INT AUTO_INCREMENT PRIMARY KEY); +INSERT INTO t1 VALUES (),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(),(); +# +# query: UPDATE t1 SET a=a+10 WHERE a > 34 +# select: SELECT * FROM t1 WHERE a > 34 +# +EXPLAIN UPDATE t1 SET a=a+10 WHERE a > 34; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 Using where; Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a=a+10 WHERE a > 34; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 100.00 Using where; Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a > 34; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 100.00 Using where; Using index +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` > 34) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 2 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 +Handler_read_next 2 +Handler_read_rnd 2 +Handler_update 2 + +DROP TABLE t1; +#51 +CREATE TABLE t1 (c1 INT, c2 INT, c3 INT); +CREATE TABLE t2 (c1 INT, c2 INT); +INSERT INTO t1 VALUES (1, 1, 10), (2, 2, 20); +# +# query: UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 +# select: SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 +# +EXPLAIN UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 0.00 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +Handler_read_rnd_next 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 0.00 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +Warnings: +Note 1003 select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2`,`test`.`t1`.`c3` AS `c3`,NULL AS `c1`,NULL AS `c2` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_rnd_next 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 4 + +# +# query: UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 WHERE t1.c3 = 10 +# select: SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE t1.c3 = 10 +# +EXPLAIN UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 WHERE t1.c3 = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 LEFT JOIN t2 ON t1.c1 = t2.c1 SET t2.c2 = 10 WHERE t1.c3 = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 0.00 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +Handler_read_rnd_next 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1 WHERE t1.c3 = 10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 0.00 const row not found +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`c1` AS `c1`,`test`.`t1`.`c2` AS `c2`,`test`.`t1`.`c3` AS `c3`,NULL AS `c1`,NULL AS `c2` from `test`.`t1` where (`test`.`t1`.`c3` = 10) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_rnd_next 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 4 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 4 + +DROP TABLE t1, t2; +#52 +CREATE TABLE t1(f1 INT, f2 INT); +CREATE TABLE t2(f3 INT, f4 INT); +CREATE INDEX IDX ON t2(f3); +INSERT INTO t1 VALUES(1,0),(2,0); +INSERT INTO t2 VALUES(1,1),(2,2); +# +# query: UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1) +# select: SELECT (SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1) FROM t1 +# +EXPLAIN UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 +2 DEPENDENT SUBQUERY t2 ALL IDX NULL NULL NULL 2 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET t1.f2=(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 100.00 +2 DEPENDENT SUBQUERY t2 ALL IDX NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1276 Field or reference 'test.t1.f1' of SELECT #2 was resolved in SELECT #1 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT (SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1) FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 100.00 +2 DEPENDENT SUBQUERY t2 ALL IDX NULL NULL NULL 2 100.00 Using where +Warnings: +Note 1276 Field or reference 'test.t1.f1' of SELECT #2 was resolved in SELECT #1 +Note 1003 select <`test`.`t1`.`f1`>((select max(`test`.`t2`.`f4`) from `test`.`t2` where (`test`.`t2`.`f3` = `test`.`t1`.`f1`))) AS `(SELECT MAX(t2.f4) FROM t2 WHERE t2.f3=t1.f1)` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 2 +Handler_read_rnd_next 9 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 9 +Handler_update 2 + +DROP TABLE t1, t2; +#55 +CREATE TABLE t1(a INT); +INSERT INTO t1 VALUES (1); +SET @a = NULL; +EXPLAIN DELETE FROM t1 WHERE (@a:= a); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 1 Using where +DROP TABLE t1; +#56 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +DELETE FROM t1 USING t1 WHERE uknown_column = 12345; +ERROR 42S22: Unknown column 'uknown_column' in 'where clause' +EXPLAIN EXTENDED DELETE FROM t1 USING t1 WHERE uknown_column = 12345; +ERROR 42S22: Unknown column 'uknown_column' in 'where clause' +DROP TABLE t1; +#57 +CREATE TABLE t1(f1 INT); +EXPLAIN EXTENDED UPDATE t1 SET f2=1 ORDER BY f2; +ERROR 42S22: Unknown column 'f2' in 'order clause' +UPDATE t1 SET f2=1 ORDER BY f2; +ERROR 42S22: Unknown column 'f2' in 'order clause' +DROP TABLE t1; +#62 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (0), (1); +CREATE VIEW v1 AS SELECT t11.a, t12.a AS b FROM t1 t11, t1 t12; +# +# query: UPDATE v1 SET a = 1 WHERE a > 0 +# select: SELECT * FROM v1 WHERE a > 0 +# +EXPLAIN UPDATE v1 SET a = 1 WHERE a > 0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 Using where +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE v1 SET a = 1 WHERE a > 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM v1 WHERE a > 0; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t11`.`a` AS `a`,`test`.`t12`.`a` AS `b` from `test`.`t1` `t11` join `test`.`t1` `t12` where (`test`.`t11`.`a` > 0) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 6 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 1 +Handler_read_rnd_deleted 1 +Handler_read_rnd_next 8 + +# +# query: UPDATE t1, v1 SET v1.a = 1 WHERE t1.a = v1.a +# select: SELECT * FROM t1, v1 WHERE t1.a = v1.a +# +EXPLAIN UPDATE t1, v1 SET v1.a = 1 WHERE t1.a = v1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 Using where +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1, v1 SET v1.a = 1 WHERE t1.a = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 100.00 Using where +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1, v1 WHERE t1.a = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 100.00 +1 SIMPLE t11 ALL NULL NULL NULL NULL 2 100.00 Using where; Using join buffer (flat, BNL join) +1 SIMPLE t12 ALL NULL NULL NULL NULL 2 100.00 Using join buffer (incremental, BNL join) +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t11`.`a` AS `a`,`test`.`t12`.`a` AS `b` from `test`.`t1` join `test`.`t1` `t11` join `test`.`t1` `t12` where (`test`.`t11`.`a` = `test`.`t1`.`a`) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 9 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd 2 +Handler_read_rnd_deleted 1 +Handler_read_rnd_next 18 +Handler_update 1 + +DROP TABLE t1; +DROP VIEW v1; +#63 +CREATE TABLE t1 (a INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +CREATE VIEW v1 (a) AS SELECT a FROM t1; +# +# query: DELETE FROM v1 WHERE a < 4 +# select: SELECT * FROM v1 WHERE a < 4 +# +EXPLAIN DELETE FROM v1 WHERE a < 4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 Using where +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE FROM v1 WHERE a < 4; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 100.00 Using where +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM v1 WHERE a < 4; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 3 100.00 Using where; Using index +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` where (`test`.`t1`.`a` < 4) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 3 +# Status of testing query execution: +Variable_name Value +Handler_delete 3 +Handler_read_first 1 +Handler_read_next 3 + +DROP TABLE t1; +DROP VIEW v1; +#64 +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1,2), (2,3), (3,4), (4,5), (5,10); +CREATE TABLE t2 (x INT); +INSERT INTO t2 VALUES (1), (2), (3), (4); +CREATE VIEW v1 (a,c) AS SELECT a, b+1 FROM t1; +# +# query: DELETE v1 FROM t2, v1 WHERE t2.x = v1.a +# select: SELECT * FROM t2, v1 WHERE t2.x = v1.a +# +EXPLAIN DELETE v1 FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE v1 FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 100.00 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 100.00 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 100.00 +Warnings: +Note 1003 select `test`.`t2`.`x` AS `x`,`test`.`t1`.`a` AS `a`,(`test`.`t1`.`b` + 1) AS `c` from `test`.`t2` join `test`.`t1` where (`test`.`t1`.`a` = `test`.`t2`.`x`) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 4 +Handler_read_rnd_next 5 +# Status of testing query execution: +Variable_name Value +Handler_delete 4 +Handler_read_key 4 +Handler_read_rnd 4 +Handler_read_rnd_next 5 + +DROP TABLE t1,t2; +DROP VIEW v1; +#65 +CREATE TABLE t1 (a INT, b INT, PRIMARY KEY(a)); +INSERT INTO t1 VALUES (1,2), (2,3), (3,4), (4,5), (5,10); +CREATE TABLE t2 (x INT); +INSERT INTO t2 VALUES (1), (2), (3), (4); +CREATE VIEW v1 (a,c) AS SELECT a, b+1 FROM t1; +# +# query: DELETE v1 FROM t2, v1 WHERE t2.x = v1.a +# select: SELECT * FROM t2, v1 WHERE t2.x = v1.a +# +EXPLAIN DELETE v1 FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED DELETE v1 FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 100.00 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t2, v1 WHERE t2.x = v1.a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 4 100.00 Using where +1 SIMPLE t1 eq_ref PRIMARY PRIMARY 4 test.t2.x 1 100.00 +Warnings: +Note 1003 select `test`.`t2`.`x` AS `x`,`test`.`t1`.`a` AS `a`,(`test`.`t1`.`b` + 1) AS `c` from `test`.`t2` join `test`.`t1` where (`test`.`t1`.`a` = `test`.`t2`.`x`) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 4 +Handler_read_rnd_next 5 +# Status of testing query execution: +Variable_name Value +Handler_delete 4 +Handler_read_key 4 +Handler_read_rnd 4 +Handler_read_rnd_next 5 + +DROP TABLE t1,t2; +DROP VIEW v1; +#66 +CREATE TABLE t1 (a INT); +CREATE VIEW v1 (x) AS SELECT a FROM t1; +# +# query: INSERT INTO v1 VALUES (10) +# select: SELECT NULL +# +EXPLAIN INSERT INTO v1 VALUES (10); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED INSERT INTO v1 VALUES (10); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL 100.00 NULL +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT NULL; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1003 select NULL AS `NULL` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +# Status of testing query execution: +Variable_name Value +Handler_write 1 + +DROP TABLE t1; +DROP VIEW v1; +#67 +CREATE TABLE t1 (a INT); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +CREATE VIEW v1 (x) AS SELECT b FROM t2; +# +# query: INSERT INTO v1 SELECT * FROM t1 +# select: SELECT * FROM t1 +# +EXPLAIN INSERT INTO v1 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 const row not found +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED INSERT INTO v1 SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 0.00 const row not found +# Status of EXPLAIN EXTENDED query +Variable_name Value +Handler_read_rnd_next 1 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 0.00 const row not found +Warnings: +Note 1003 select NULL AS `a` from `test`.`t1` +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +Handler_read_rnd_next 1 +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 1 +# Status of testing query execution: +Variable_name Value +Handler_read_rnd_next 1 + +DROP TABLE t1, t2; +DROP VIEW v1; +#68 +CREATE TABLE t1 (i INT); +EXPLAIN INSERT DELAYED INTO t1 VALUES (1); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +DROP TABLE t1; +#69 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1), (2), (3); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2), (3); +# +# query: UPDATE t1 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# select: SELECT * FROM t1 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# +EXPLAIN UPDATE t1 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY index_subquery key0 key0 5 func 2 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY index_subquery key0 key0 5 func 2 100.00 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +2 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a` from `test`.`t1` semi join ((select `test`.`t2`.`b` AS `b` from `test`.`t2` order by `test`.`t2`.`b` limit 2,2) `x`) where 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 10 +Sort_rows 3 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 8 +Handler_update 1 +Sort_rows 3 +Sort_scan 1 + +# +# query: UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# select: SELECT * FROM t1, t2 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# +EXPLAIN UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 +2 MATERIALIZED ALL NULL NULL NULL NULL 3 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 +2 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1, t2 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +2 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 +3 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` semi join ((select `test`.`t2`.`b` AS `b` from `test`.`t2` order by `test`.`t2`.`b` limit 2,2) `x`) join `test`.`t2` where 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 10 +Sort_rows 3 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 10 +Sort_rows 3 +Sort_scan 1 + +# +# query: UPDATE t1, (SELECT * FROM t2) y SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# select: SELECT * FROM t1, (SELECT * FROM t2) y WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x) +# +EXPLAIN UPDATE t1, (SELECT * FROM t2) y SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 +1 PRIMARY ALL NULL NULL NULL NULL 3 +3 MATERIALIZED ALL NULL NULL NULL NULL 3 +4 DERIVED t2 ALL NULL NULL NULL NULL 3 Using filesort +2 DERIVED t2 ALL NULL NULL NULL NULL 3 +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1, (SELECT * FROM t2) y SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY ALL NULL NULL NULL NULL 3 100.00 +3 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 +4 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +2 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT * FROM t1, (SELECT * FROM t2) y WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 +1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 Using join buffer (flat, BNL join) +3 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 +4 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` semi join ((select `test`.`t2`.`b` AS `b` from `test`.`t2` order by `test`.`t2`.`b` limit 2,2) `x`) join `test`.`t2` where 1 +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 10 +Sort_rows 3 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 3 +Handler_read_rnd_next 10 +Sort_rows 3 +Sort_scan 1 + +DROP TABLE t1,t2; +#70 +CREATE TABLE t1 (c1 INT KEY); +CREATE TABLE t2 (c2 INT); +CREATE TABLE t3 (c3 INT); +EXPLAIN EXTENDED UPDATE t3 SET c3 = ( +SELECT COUNT(d1.c1) +FROM ( +SELECT a11.c1 FROM t1 AS a11 +STRAIGHT_JOIN t2 AS a21 ON a21.c2 = a11.c1 +JOIN t1 AS a12 ON a12.c1 = a11.c1 +) d1 +); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t3 ALL NULL NULL NULL NULL 0 100.00 +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +DROP TABLE t1, t2, t3; +#73 +CREATE TABLE t1 (id INT); +CREATE TABLE t2 (id INT); +INSERT INTO t1 VALUES (1), (2); +EXPLAIN SELECT * FROM t1 LEFT JOIN t2 USING(id) GROUP BY t1.id; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 system NULL NULL NULL NULL 0 const row not found; Using temporary; Using filesort +1 SIMPLE t1 ALL NULL NULL NULL NULL 2 +DROP TABLE t1,t2; +#74 +CREATE TABLE t1(a INT PRIMARY KEY) ENGINE=InnoDB; +Warnings: +Warning 1286 Unknown storage engine 'InnoDB' +Warning 1266 Using storage engine MyISAM for table 't1' +INSERT INTO t1 VALUES (1), (2), (3), (4), (5); +# used key is modified & Using temporary +# +# query: UPDATE t1 SET a=a+1 WHERE a>10 +# select: SELECT a t1 FROM t1 WHERE a>10 +# +EXPLAIN UPDATE t1 SET a=a+1 WHERE a>10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 Using where; Using buffer +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a=a+1 WHERE a>10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 100.00 Using where; Using buffer +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT a t1 FROM t1 WHERE a>10; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 5 20.00 Using where; Using index +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1` from `test`.`t1` where (`test`.`t1`.`a` > 10) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_first 1 +Handler_read_next 5 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 + +# used key is modified & Using filesort +# +# query: UPDATE t1 SET a=a+1 WHERE a>10 ORDER BY a+20 +# select: SELECT a t1 FROM t1 WHERE a>10 ORDER BY a+20 +# +EXPLAIN UPDATE t1 SET a=a+1 WHERE a>10 ORDER BY a+20; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 Using where; Using filesort +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED UPDATE t1 SET a=a+1 WHERE a>10 ORDER BY a+20; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 range PRIMARY PRIMARY 4 NULL 1 100.00 Using where; Using filesort +# Status of EXPLAIN EXTENDED query +Variable_name Value +FLUSH STATUS; +FLUSH TABLES; +EXPLAIN EXTENDED SELECT a t1 FROM t1 WHERE a>10 ORDER BY a+20; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 5 20.00 Using where; Using index; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a` AS `t1` from `test`.`t1` where (`test`.`t1`.`a` > 10) order by (`test`.`t1`.`a` + 20) +# Status of EXPLAIN EXTENDED "equivalent" SELECT query execution +Variable_name Value +# Status of "equivalent" SELECT query execution: +Variable_name Value +Handler_read_rnd_next 6 +Sort_scan 1 +# Status of testing query execution: +Variable_name Value +Handler_read_key 1 +Sort_range 1 + +DROP TABLE t1; +# +# Bug #12949629: CLIENT LOSES CONNECTION AFTER EXECUTING A PROCEDURE WITH +# EXPLAIN UPDATE/DEL/INS +# +CREATE TABLE t1 (i INT); +CREATE TABLE t2 (i INT); +CREATE PROCEDURE p1() BEGIN EXPLAIN INSERT INTO t1 VALUES (1);END| +CREATE PROCEDURE p2() BEGIN INSERT INTO t1 VALUES (1);END| +CREATE PROCEDURE p3() BEGIN EXPLAIN INSERT INTO t1 SELECT 1;END| +CREATE PROCEDURE p4() BEGIN INSERT INTO t1 SELECT 1;END| +CREATE PROCEDURE p5() BEGIN EXPLAIN REPLACE INTO t1 VALUES (1);END| +CREATE PROCEDURE p6() BEGIN REPLACE INTO t1 VALUES (1);END| +CREATE PROCEDURE p7() BEGIN EXPLAIN REPLACE INTO t1 SELECT 1;END| +CREATE PROCEDURE p8() BEGIN REPLACE INTO t1 SELECT 1;END| +CREATE PROCEDURE p9() BEGIN EXPLAIN UPDATE t1 SET i = 10;END| +CREATE PROCEDURE p10() BEGIN UPDATE t1 SET i = 10;END| +CREATE PROCEDURE p11() BEGIN EXPLAIN UPDATE t1,t2 SET t1.i = 10 WHERE t1.i = t2.i ;END| +CREATE PROCEDURE p12() BEGIN UPDATE t1,t2 SET t1.i = 10 WHERE t1.i = t2.i ;END| +CREATE PROCEDURE p13() BEGIN EXPLAIN DELETE FROM t1;END| +CREATE PROCEDURE p14() BEGIN DELETE FROM t1;END| +CREATE PROCEDURE p15() BEGIN EXPLAIN DELETE FROM t1 USING t1;END| +CREATE PROCEDURE p16() BEGIN DELETE FROM t1 USING t1;END| +CALL p16(); +DROP PROCEDURE p16; +CALL p15(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 system NULL NULL NULL NULL 0 const row not found +DROP PROCEDURE p15; +CALL p14(); +DROP PROCEDURE p14; +CALL p13(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL 0 Deleting all rows +DROP PROCEDURE p13; +CALL p12(); +DROP PROCEDURE p12; +CALL p11(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +DROP PROCEDURE p11; +CALL p10(); +DROP PROCEDURE p10; +CALL p9(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 0 +DROP PROCEDURE p9; +CALL p8(); +DROP PROCEDURE p8; +CALL p7(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used +DROP PROCEDURE p7; +CALL p6(); +DROP PROCEDURE p6; +CALL p5(); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +DROP PROCEDURE p5; +CALL p4(); +DROP PROCEDURE p4; +CALL p3(); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL No tables used +DROP PROCEDURE p3; +CALL p2(); +DROP PROCEDURE p2; +CALL p1(); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +DROP PROCEDURE p1; +DROP TABLE t1, t2; +# +set default_storage_engine= @save_storage_engine; +set optimizer_switch=default; diff --git a/mysql-test/t/myisam_explain_non_select_all.test b/mysql-test/t/myisam_explain_non_select_all.test new file mode 100644 index 00000000000..ccd4ebd6297 --- /dev/null +++ b/mysql-test/t/myisam_explain_non_select_all.test @@ -0,0 +1,21 @@ +# +# Run explain_non_select.inc on MyISAM with all of the so-called 6.0 features. +# + +#--source include/have_semijoin.inc +#--source include/have_materialization.inc +#--source include/have_firstmatch.inc +#--source include/have_loosescan.inc +#--source include/have_index_condition_pushdown.inc +#--source include/have_mrr.inc + +#set optimizer_switch='semijoin=on,materialization=on,firstmatch=on,loosescan=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=off'; + +set @save_storage_engine= @@session.default_storage_engine; +set session default_storage_engine = MyISAM; +--let $json = 0 +--let $validation = 0 +--source include/explain_non_select.inc +set default_storage_engine= @save_storage_engine; + +set optimizer_switch=default; From 094f70fd1908c17c0ebc4b6942e3bebed0a236ba Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 10 Oct 2013 20:30:32 +0400 Subject: [PATCH 55/64] MDEV-5106: Server crashes in Explain_union::print_explain on ER_TOO_BIG_SELECT - Don't save UNION's EXPLAIN data if optimization failed with an error. We could end up saving incomplete plan, which will cause a crash when we attempt to print it. --- mysql-test/r/explain_slowquerylog.result | 13 +++++++++++++ mysql-test/t/explain_non_select.test | 1 - mysql-test/t/explain_slowquerylog.test | 12 ++++++++++++ sql/sql_explain.cc | 4 ++++ sql/sql_union.cc | 2 +- 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/explain_slowquerylog.result b/mysql-test/r/explain_slowquerylog.result index 62c742c5932..9d25cf06275 100644 --- a/mysql-test/r/explain_slowquerylog.result +++ b/mysql-test/r/explain_slowquerylog.result @@ -26,3 +26,16 @@ SELECT 1; EXPLAIN PARTITIONS SELECT 1 ; id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No tables used +# +# MDEV-5106: Server crashes in Explain_union::print_explain on ER_TOO_BIG_SELECT with explain in slow log +# +CREATE TABLE t1 (i INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(1),(2),(3),(4),(5),(6),(7),(8),(9),(10); +SET max_join_size = 10; +( SELECT ta.* FROM t1 ta, t1 tb ) UNION ( SELECT * FROM t1 ); +ERROR 42000: The SELECT would examine more than MAX_JOIN_SIZE rows; check your WHERE and use SET SQL_BIG_SELECTS=1 or SET MAX_JOIN_SIZE=# if the SELECT is okay +SELECT 'Server still alive?' as 'Yes'; +Yes +Server still alive? +DROP TABLE t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 7220a00bd2a..316cc1bf8d2 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -169,4 +169,3 @@ explain insert into t2 values (11, 1+(select max(a+1) from t1)); drop table t1,t2; - diff --git a/mysql-test/t/explain_slowquerylog.test b/mysql-test/t/explain_slowquerylog.test index 23e31166e32..8c8be555640 100644 --- a/mysql-test/t/explain_slowquerylog.test +++ b/mysql-test/t/explain_slowquerylog.test @@ -35,3 +35,15 @@ SELECT 1; EXPLAIN PARTITIONS SELECT 1 ; +--echo # +--echo # MDEV-5106: Server crashes in Explain_union::print_explain on ER_TOO_BIG_SELECT with explain in slow log +--echo # +CREATE TABLE t1 (i INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(1),(2),(3),(4),(5),(6),(7),(8),(9),(10); +SET max_join_size = 10; +--error ER_TOO_BIG_SELECT +( SELECT ta.* FROM t1 ta, t1 tb ) UNION ( SELECT * FROM t1 ); +SELECT 'Server still alive?' as 'Yes'; + +DROP TABLE t1; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 91a73cfb7d3..f8d544a52e9 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -157,6 +157,10 @@ bool print_explain_query(LEX *lex, THD *thd, String *str) } +/* + Return tabular EXPLAIN output as a text string +*/ + bool Explain_query::print_explain_str(THD *thd, String *out_str) { List fields; diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 72ba3853110..8ffda3190b6 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -630,7 +630,7 @@ bool st_select_lex_unit::exec() saved_error= optimize(); - if (!was_executed && thd->lex->explain) + if (!saved_error && !was_executed && thd->lex->explain) save_union_explain(thd->lex->explain); if (uncacheable || !item || !item->assigned() || describe) From a1705b762e26eb86ded808d426c43eafb47193ec Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 11 Oct 2013 12:40:25 +0400 Subject: [PATCH 56/64] Update tests: log_slow_verbosity now supports query_plan=explan. --- .../r/log_slow_verbosity_basic.result | 28 +++++++++++++++++-- .../sys_vars/t/log_slow_verbosity_basic.test | 16 ++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/mysql-test/suite/sys_vars/r/log_slow_verbosity_basic.result b/mysql-test/suite/sys_vars/r/log_slow_verbosity_basic.result index 24b805d4d9c..a291e276e4b 100644 --- a/mysql-test/suite/sys_vars/r/log_slow_verbosity_basic.result +++ b/mysql-test/suite/sys_vars/r/log_slow_verbosity_basic.result @@ -37,6 +37,22 @@ set session log_slow_verbosity=3; select @@session.log_slow_verbosity; @@session.log_slow_verbosity innodb,query_plan +set session log_slow_verbosity=4; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +explain +set session log_slow_verbosity=5; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +innodb,explain +set session log_slow_verbosity=6; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +query_plan,explain +set session log_slow_verbosity=7; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +innodb,query_plan,explain set session log_slow_verbosity='innodb'; select @@session.log_slow_verbosity; @@session.log_slow_verbosity @@ -49,6 +65,14 @@ set session log_slow_verbosity='innodb,query_plan'; select @@session.log_slow_verbosity; @@session.log_slow_verbosity innodb,query_plan +set session log_slow_verbosity='explain'; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +explain +set session log_slow_verbosity='innodb,query_plan,explain'; +select @@session.log_slow_verbosity; +@@session.log_slow_verbosity +innodb,query_plan,explain set session log_slow_verbosity=''; select @@session.log_slow_verbosity; @@session.log_slow_verbosity @@ -59,6 +83,6 @@ set session log_slow_verbosity=1e1; ERROR 42000: Incorrect argument type to variable 'log_slow_verbosity' set session log_slow_verbosity="foo"; ERROR 42000: Variable 'log_slow_verbosity' can't be set to the value of 'foo' -set session log_slow_verbosity=4; -ERROR 42000: Variable 'log_slow_verbosity' can't be set to the value of '4' +set session log_slow_verbosity=8; +ERROR 42000: Variable 'log_slow_verbosity' can't be set to the value of '8' SET @@global.log_slow_verbosity = @start_global_value; diff --git a/mysql-test/suite/sys_vars/t/log_slow_verbosity_basic.test b/mysql-test/suite/sys_vars/t/log_slow_verbosity_basic.test index 9302d5f1210..f45fa5da9c5 100644 --- a/mysql-test/suite/sys_vars/t/log_slow_verbosity_basic.test +++ b/mysql-test/suite/sys_vars/t/log_slow_verbosity_basic.test @@ -29,12 +29,26 @@ set session log_slow_verbosity=2; select @@session.log_slow_verbosity; set session log_slow_verbosity=3; select @@session.log_slow_verbosity; +set session log_slow_verbosity=4; +select @@session.log_slow_verbosity; +set session log_slow_verbosity=5; +select @@session.log_slow_verbosity; +set session log_slow_verbosity=6; +select @@session.log_slow_verbosity; +set session log_slow_verbosity=7; +select @@session.log_slow_verbosity; + + set session log_slow_verbosity='innodb'; select @@session.log_slow_verbosity; set session log_slow_verbosity='query_plan'; select @@session.log_slow_verbosity; set session log_slow_verbosity='innodb,query_plan'; select @@session.log_slow_verbosity; +set session log_slow_verbosity='explain'; +select @@session.log_slow_verbosity; +set session log_slow_verbosity='innodb,query_plan,explain'; +select @@session.log_slow_verbosity; set session log_slow_verbosity=''; select @@session.log_slow_verbosity; @@ -48,6 +62,6 @@ set session log_slow_verbosity=1e1; --error ER_WRONG_VALUE_FOR_VAR set session log_slow_verbosity="foo"; --error ER_WRONG_VALUE_FOR_VAR -set session log_slow_verbosity=4; +set session log_slow_verbosity=8; SET @@global.log_slow_verbosity = @start_global_value; From 3e8f7c8516d643a6e6fae95596cb426082c2c7f5 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 11 Oct 2013 19:27:53 +0400 Subject: [PATCH 57/64] MDEV-5122: "Commands out of sync", "Malformed packet" or client hang up... - When INSERT catches an error, it should not attempt to send parts of EXPLAIN output. --- mysql-test/r/explain_non_select.result | 21 +++++++++++++++++++ mysql-test/t/explain_non_select.test | 29 ++++++++++++++++++++++++++ sql/sql_insert.cc | 7 +++---- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index bb43fccc114..42ae7bd4ebd 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -198,3 +198,24 @@ id select_type table type possible_keys key key_len ref rows Extra 3 SUBQUERY t1 index NULL a 5 NULL 3 Using index 2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away drop table t1,t2; +# +# MDEV-5122: "Commands out of sync", "Malformed packet" or client hang up on unique key violation +# +drop table if exists t1; +Warnings: +Note 1051 Unknown table 't1' +drop function if exists f1; +create table t1 (a int, unique(a)); +create function f1(x int) +returns int +begin +insert into t1 values(x),(x); +return 10; +end| +select f1(100); +ERROR 23000: Duplicate entry '100' for key 'a' +select 'OK'; +OK +OK +drop function f1; +drop table t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 316cc1bf8d2..f05373c312e 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -169,3 +169,32 @@ explain insert into t2 values (11, 1+(select max(a+1) from t1)); drop table t1,t2; + +--echo # +--echo # MDEV-5122: "Commands out of sync", "Malformed packet" or client hang up on unique key violation +--echo # +drop table if exists t1; + +--disable_warnings +drop function if exists f1; +--enable_warnings + +create table t1 (a int, unique(a)); + +delimiter |; +create function f1(x int) + returns int +begin + insert into t1 values(x),(x); + return 10; +end| +delimiter ;| + +--error ER_DUP_ENTRY +select f1(100); +select 'OK'; + +drop function f1; +drop table t1; + + diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 10cd8a6a7f6..56882f7798c 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -819,8 +819,8 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, save_insert_query_plan(thd, table_list); if (thd->lex->describe) { - retval= 0; - goto exit_without_my_ok; + retval= thd->lex->explain->send_explain(thd); + goto free_and_exit; } /* @@ -1170,15 +1170,14 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, DBUG_RETURN(FALSE); abort: -exit_without_my_ok: #ifndef EMBEDDED_LIBRARY if (lock_type == TL_WRITE_DELAYED) end_delayed_insert(thd); #endif if (table != NULL) table->file->ha_release_auto_increment(); - retval= thd->lex->explain->send_explain(thd); +free_and_exit: if (!joins_freed) free_underlaid_joins(thd, &thd->lex->select_lex); thd->abort_on_warning= 0; From f67f8fd00fa6cf4bb38b10094060e6842a7d8daa Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 14 Oct 2013 20:01:28 +0400 Subject: [PATCH 58/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Fix EXPLAIN INSERT DELAYED ... : do call end_delayed_insert(). --- sql/sql_insert.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 56882f7798c..cbc44700b17 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -820,7 +820,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, if (thd->lex->describe) { retval= thd->lex->explain->send_explain(thd); - goto free_and_exit; + goto abort; } /* @@ -1177,7 +1177,6 @@ abort: if (table != NULL) table->file->ha_release_auto_increment(); -free_and_exit: if (!joins_freed) free_underlaid_joins(thd, &thd->lex->select_lex); thd->abort_on_warning= 0; From 105e3ae6c93f57498fa6c504dfbb92203b0d1056 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 14 Oct 2013 20:09:33 +0400 Subject: [PATCH 59/64] MDEV-3798: EXPLAIN UPDATE/DELETE Update the SHOW EXPLAIN code to work with the new architecture (part#1): Before, SHOW EXPLAIN operated on real query plan structures, which meant it had to check when SELECTs are created/deleted. SELECTs would call apc_target->enable() when they got a query plan and disable() when their query plan was deleted. Now, Explain data structure becomes available at once (and we call apc_target->enable()) and then it stays until it is deleted (when that happens, we call apc_target->disable()). --- mysql-test/r/show_explain.result | 11 ++++------- mysql-test/t/show_explain.test | 4 ++++ sql/sp_head.cc | 2 +- sql/sql_delete.cc | 11 +++-------- sql/sql_explain.cc | 34 ++++++++++++++++++++++++++++---- sql/sql_explain.h | 24 ++++++++++++++-------- sql/sql_insert.cc | 5 +++-- sql/sql_lex.cc | 8 ++++++-- sql/sql_lex.h | 4 ++++ sql/sql_select.cc | 10 +++++----- sql/sql_update.cc | 8 -------- 11 files changed, 76 insertions(+), 45 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index c124048aca5..7e0ac0b277c 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -181,6 +181,8 @@ set debug_dbug='+d,show_explain_probe_join_exec_start'; set @foo= (select max(a) from t0 where sin(a) >0); show explain for $thr2; ERROR HY000: Target is not running an EXPLAINable command +kill query $thr2; +ERROR 70100: Query execution was interrupted set debug_dbug=@old_debug; # # Attempt SHOW EXPLAIN for an UPDATE @@ -405,13 +407,8 @@ set debug_dbug='+d,show_explain_probe_join_exec_end'; SELECT * FROM v1, t2; show explain for $thr2; ERROR HY000: Target is not running an EXPLAINable command -a b -8 4 -8 5 -8 6 -8 7 -8 8 -8 9 +kill query $thr2; +ERROR 70100: Query execution was interrupted set debug_dbug=@old_debug; DROP VIEW v1; DROP TABLE t2, t3; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 91e3be34408..6735a0e851e 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -224,7 +224,9 @@ connection default; --source include/wait_condition.inc --error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; +evalp kill query $thr2; connection con1; +--error ER_QUERY_INTERRUPTED reap; set debug_dbug=@old_debug; @@ -399,7 +401,9 @@ connection default; --source include/wait_condition.inc --error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; +evalp kill query $thr2; connection con1; +--error ER_QUERY_INTERRUPTED reap; set debug_dbug=@old_debug; DROP VIEW v1; diff --git a/sql/sp_head.cc b/sql/sp_head.cc index ffc77224c12..5926f56d644 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -3037,7 +3037,7 @@ sp_lex_keeper::reset_lex_and_exec_core(THD *thd, uint *nextp, else if (! thd->in_sub_stmt) thd->mdl_context.release_statement_locks(); } - + //TODO: why is this here if log_slow_query is in sp_instr_stmt_execute? delete_explain_query(m_lex); if (m_lex->query_tables_own_last) diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 7f58526d174..fae44a72205 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -66,8 +66,8 @@ void Delete_plan::save_explain_data(Explain_query *query) explain->deleting_all_rows= false; Update_plan::save_explain_data_intern(query, explain); } - - query->upd_del_plan= explain; + + query->add_upd_del_plan(explain); } @@ -75,7 +75,7 @@ void Update_plan::save_explain_data(Explain_query *query) { Explain_update* explain= new Explain_update; save_explain_data_intern(query, explain); - query->upd_del_plan= explain; + query->add_upd_del_plan(explain); } @@ -459,7 +459,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, goto exit_without_my_ok; query_plan.save_explain_data(thd->lex->explain); - thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", dbug_serve_apcs(thd, 1);); @@ -486,7 +485,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, &thd->lex->select_lex); - thd->apc_target.disable(); DBUG_RETURN(TRUE); } thd->examined_row_count+= examined_rows; @@ -505,7 +503,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, select_lex); - thd->apc_target.disable(); DBUG_RETURN(TRUE); } if (query_plan.index == MAX_KEY || (select && select->quick)) @@ -514,7 +511,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, select_lex); - thd->apc_target.disable(); DBUG_RETURN(TRUE); } } @@ -624,7 +620,6 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_NORMAL); - thd->apc_target.disable(); cleanup: /* Invalidate the table in the query cache if something changed. This must diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index f8d544a52e9..b9f75d3a97e 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -22,7 +22,8 @@ #include "sql_select.h" -Explain_query::Explain_query() : upd_del_plan(NULL), insert_plan(NULL) +Explain_query::Explain_query(THD *thd_arg) : + upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false) { operations= 0; } @@ -30,6 +31,9 @@ Explain_query::Explain_query() : upd_del_plan(NULL), insert_plan(NULL) Explain_query::~Explain_query() { + if (apc_enabled) + thd->apc_target.disable(); + delete upd_del_plan; delete insert_plan; uint i; @@ -62,11 +66,12 @@ Explain_select *Explain_query::get_select(uint select_id) void Explain_query::add_node(Explain_node *node) { + uint select_id; operations++; if (node->get_type() == Explain_node::EXPLAIN_UNION) { Explain_union *u= (Explain_union*)node; - uint select_id= u->get_select_id(); + select_id= u->get_select_id(); if (unions.elements() <= select_id) unions.resize(max(select_id+1, unions.elements()*2), NULL); @@ -85,7 +90,7 @@ void Explain_query::add_node(Explain_node *node) } else { - uint select_id= sel->select_id; + select_id= sel->select_id; Explain_select *old_node; if (selects.elements() <= select_id) @@ -100,6 +105,27 @@ void Explain_query::add_node(Explain_node *node) } +void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg) +{ + insert_plan= insert_plan_arg; + query_plan_ready(); +} + + +void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg) +{ + upd_del_plan= upd_del_plan_arg; + query_plan_ready(); +} + + +void Explain_query::query_plan_ready() +{ + if (!apc_enabled) + thd->apc_target.enable(); + apc_enabled= true; +} + /* Send EXPLAIN output to the client. */ @@ -915,7 +941,7 @@ void delete_explain_query(LEX *lex) void create_explain_query(LEX *lex, MEM_ROOT *mem_root) { DBUG_ASSERT(!lex->explain); - lex->explain= new Explain_query; + lex->explain= new Explain_query(lex->thd); DBUG_ASSERT(mem_root == current_thd->mem_root); lex->explain->mem_root= mem_root; } diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 2eb9e528a67..12621b88d23 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -221,10 +221,13 @@ class Explain_insert; class Explain_query : public Sql_alloc { public: - Explain_query(); + Explain_query(THD *thd); ~Explain_query(); + /* Add a new node */ void add_node(Explain_node *node); + void add_insert_plan(Explain_insert *insert_plan_arg); + void add_upd_del_plan(Explain_update *upd_del_plan_arg); /* This will return a select, or a union */ Explain_node *get_node(uint select_id); @@ -233,13 +236,7 @@ public: Explain_select *get_select(uint select_id); Explain_union *get_union(uint select_id); - - /* Explain_delete inherits from Explain_update */ - Explain_update *upd_del_plan; - - /* Query "plan" for INSERTs */ - Explain_insert *insert_plan; - + /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); @@ -251,11 +248,22 @@ public: /* If true, at least part of EXPLAIN can be printed */ bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } + + void query_plan_ready(); + MEM_ROOT *mem_root; private: + /* Explain_delete inherits from Explain_update */ + Explain_update *upd_del_plan; + + /* Query "plan" for INSERTs */ + Explain_insert *insert_plan; + Dynamic_array unions; Dynamic_array selects; + THD *thd; // for APC start/stop + bool apc_enabled; /* Debugging aid: count how many times add_node() was called. Ideally, it should be one, we currently allow O(1) query plan saves for each diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index cbc44700b17..24b43442e2a 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -461,7 +461,8 @@ void upgrade_lock_type(THD *thd, thr_lock_type *lock_type, if (specialflag & (SPECIAL_NO_NEW_FUNC | SPECIAL_SAFE_MODE) || thd->variables.max_insert_delayed_threads == 0 || thd->locked_tables_mode > LTM_LOCK_TABLES || - thd->lex->uses_stored_routines()) + thd->lex->uses_stored_routines() /*|| + thd->lex->describe*/) { *lock_type= TL_WRITE; return; @@ -649,7 +650,7 @@ static void save_insert_query_plan(THD* thd, TABLE_LIST *table_list) Explain_insert* explain= new Explain_insert; explain->table_name.append(table_list->table->alias); - thd->lex->explain->insert_plan= explain; + thd->lex->explain->add_insert_plan(explain); /* See Update_plan::updating_a_view for details */ bool skip= test(table_list->view); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index bd31200e749..09cb8f06afc 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4276,11 +4276,15 @@ int st_select_lex_unit::save_union_explain(Explain_query *output) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) eu->add_select(sl->select_number); + eu->fake_select_type= "UNION RESULT"; + eu->using_filesort= test(global_parameters->order_list.first); + // Save the UNION node output->add_node(eu); - eu->fake_select_type= "UNION RESULT"; - eu->using_filesort= test(global_parameters->order_list.first); + if (eu->get_select_id() == 1) + output->query_plan_ready(); + return 0; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index c5b9c1eada2..0d22e815f5f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1031,6 +1031,10 @@ public: void clear_index_hints(void) { index_hints= NULL; } bool is_part_of_union() { return master_unit()->is_union(); } + bool is_top_level_node() + { + return (select_number == 1) && !is_part_of_union(); + } bool optimize_unflattened_subqueries(bool const_only); /* Set the EXPLAIN type for this subquery. */ void set_explain_type(bool on_the_fly); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a51fcd07b58..f7e3dcad7ed 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2298,8 +2298,6 @@ JOIN::save_join_tab() void join_save_qpf(JOIN *join) { THD *thd= join->thd; -//TODO: why not call st_select_lex::save_qpf here? - if (join->select_lex->select_number != UINT_MAX && join->select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && join->have_query_plan != JOIN::QEP_NOT_PRESENT_YET && @@ -2315,7 +2313,7 @@ void join_save_qpf(JOIN *join) /* It's a degenerate join */ message= join->zero_result_cause ? join->zero_result_cause : "No tables used"; } - + join->save_explain_data(thd->lex->explain, join->need_tmp, !join->skip_sort_order && !join->no_order && @@ -2328,7 +2326,6 @@ void join_save_qpf(JOIN *join) void JOIN::exec() { - thd->apc_target.enable(); DBUG_EXECUTE_IF("show_explain_probe_join_exec_start", if (dbug_user_var_equals_int(thd, "show_explain_probe_select_id", @@ -2368,7 +2365,6 @@ void JOIN::exec() select_lex->select_number)) dbug_serve_apcs(thd, 1); ); - thd->apc_target.disable(); } @@ -22997,6 +22993,10 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, } } + if (!error && select_lex->is_top_level_node()) + output->query_plan_ready(); + + DBUG_RETURN(error); } diff --git a/sql/sql_update.cc b/sql/sql_update.cc index a5f81a9e89e..6a58b4e8029 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -279,7 +279,6 @@ int mysql_update(THD *thd, Update_plan query_plan(thd->mem_root); query_plan.index= MAX_KEY; query_plan.using_filesort= FALSE; - bool apc_target_enabled= false; // means was enabled *by code this function* DBUG_ENTER("mysql_update"); if (open_tables(thd, &table_list, &table_count, 0)) @@ -518,8 +517,6 @@ int mysql_update(THD *thd, goto exit_without_my_ok; query_plan.save_explain_data(thd->lex->explain); - thd->apc_target.enable(); - apc_target_enabled= true; DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", dbug_serve_apcs(thd, 1);); @@ -960,8 +957,6 @@ int mysql_update(THD *thd, if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; - thd->apc_target.disable(); - apc_target_enabled= false; end_read_record(&info); delete select; thd_proc_info(thd, "end"); @@ -1035,8 +1030,6 @@ int mysql_update(THD *thd, DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); err: - if (apc_target_enabled) - thd->apc_target.disable(); delete select; free_underlaid_joins(thd, select_lex); @@ -1045,7 +1038,6 @@ err: DBUG_RETURN(1); exit_without_my_ok: - DBUG_ASSERT(!apc_target_enabled); query_plan.save_explain_data(thd->lex->explain); int err2= thd->lex->explain->send_explain(thd); From 1ec399ce03d3adb5b31b63673bbad424b48f4f2b Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 Oct 2013 00:30:32 +0400 Subject: [PATCH 60/64] Code cleanup. --- sql/sql_lex.cc | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 09cb8f06afc..60ab090ddf0 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4250,29 +4250,6 @@ int st_select_lex_unit::save_union_explain(Explain_query *output) SELECT_LEX *first= first_select(); Explain_union *eu= new (output->mem_root) Explain_union; - /* - TODO: The following code should be eliminated. If we have a capability to - save Query Plan Footprints, we should just save them, and never need to - print "query plan already deleted". - */ - if (first && !first->next_select() && !first->join) - { - /* - If there is only one child, 'first', and it has join==NULL, emit "not in - EXPLAIN state" error. - */ - const char *msg="Query plan already deleted"; - first->set_explain_type(TRUE/* on_the_fly */); - - Explain_select *explain= new (output->mem_root)Explain_select; - explain->select_id= first->select_number; - explain->select_type= first->type; - explain->message= msg; - output->add_node(explain); - eu->add_select(explain->select_id); - return 0; - } - for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) eu->add_select(sl->select_number); From a356cfbefb67427e678230068a2ace194a69fc01 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 Oct 2013 08:00:48 +0400 Subject: [PATCH 61/64] MDEV-3798: EXPLAIN UPDATE/DELETE - eliminate join_save_qpf() function. --- sql/sql_select.cc | 74 ++++++++++++++++++----------------------------- sql/sql_select.h | 7 +++-- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f7e3dcad7ed..a2b51872764 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1002,8 +1002,6 @@ err: DBUG_RETURN(res); /* purecov: inspected */ } -void join_save_qpf(JOIN *join); - int JOIN::optimize() { bool was_optimized= optimized; @@ -1023,7 +1021,10 @@ int JOIN::optimize() if (was_optimized != optimized && !res && have_query_plan != QEP_DELETED) { have_query_plan= QEP_AVAILABLE; - join_save_qpf(this); + save_explain_data(thd->lex->explain, false /* can overwrite */, + need_tmp, + !skip_sort_order && !no_order && (order || group_list), + select_distinct); } return res; } @@ -2295,31 +2296,27 @@ JOIN::save_join_tab() } -void join_save_qpf(JOIN *join) +void JOIN::save_explain_data(Explain_query *output, bool can_overwrite, + bool need_tmp_table, bool need_order, + bool distinct) { - THD *thd= join->thd; - if (join->select_lex->select_number != UINT_MAX && - join->select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && - join->have_query_plan != JOIN::QEP_NOT_PRESENT_YET && - join->have_query_plan != JOIN::QEP_DELETED && // this happens when there was no QEP ever, but then - //cleanup() is called multiple times - thd->lex->explain && // for "SET" command in SPs. - !thd->lex->explain->get_select(join->select_lex->select_number)) + if (select_lex->select_number != UINT_MAX && + select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && + have_query_plan != JOIN::QEP_NOT_PRESENT_YET && + have_query_plan != JOIN::QEP_DELETED && // this happens when there was + // no QEP ever, but then + //cleanup() is called multiple times + output && // for "SET" command in SPs. + (can_overwrite? true: !output->get_select(select_lex->select_number))) { const char *message= NULL; - - if (!join->table_count || !join->tables_list || join->zero_result_cause) + if (!table_count || !tables_list || zero_result_cause) { /* It's a degenerate join */ - message= join->zero_result_cause ? join->zero_result_cause : "No tables used"; + message= zero_result_cause ? zero_result_cause : "No tables used"; } - - join->save_explain_data(thd->lex->explain, - join->need_tmp, - !join->skip_sort_order && !join->no_order && - (join->order || join->group_list), - join->select_distinct, - message); + save_explain_data_intern(thd->lex->explain, need_tmp_table, need_order, + distinct, message); } } @@ -2336,26 +2333,10 @@ void JOIN::exec() if (!exec_saved_explain) { - if (select_lex->select_number != UINT_MAX && - select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ && - have_query_plan != QEP_NOT_PRESENT_YET && - have_query_plan != QEP_DELETED && // this happens when there was no QEP ever, but then - //cleanup() is called multiple times - thd->lex->explain)// for "SET" command in SPs. - { - const char *message= NULL; - if (!table_count || !tables_list || zero_result_cause) - { - /* It's a degenerate join */ - message= zero_result_cause ? zero_result_cause : "No tables used"; - } - - save_explain_data(thd->lex->explain, - need_tmp, - order != 0 && !skip_sort_order, - select_distinct, - message); - } + save_explain_data(thd->lex->explain, true /* can overwrite */, + need_tmp, + order != 0 && !skip_sort_order, + select_distinct); exec_saved_explain= true; } @@ -22497,6 +22478,7 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) } } + /* Save Query Plan Footprint @@ -22504,9 +22486,9 @@ void append_possible_keys(String *str, TABLE *table, key_map possible_keys) Currently, this function may be called multiple times */ -int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, - bool need_order, bool distinct, - const char *message) +int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message) { Explain_node *explain_node; JOIN *join= this; /* Legacy: this code used to be a non-member function */ @@ -22514,7 +22496,7 @@ int JOIN::save_explain_data(Explain_query *output, bool need_tmp_table, const CHARSET_INFO *cs= system_charset_info; int quick_type; int error= 0; - DBUG_ENTER("JOIN::save_explain_data"); + DBUG_ENTER("JOIN::save_explain_data_intern"); DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", (ulong)join->select_lex, join->select_lex->type, message ? message : "NULL")); diff --git a/sql/sql_select.h b/sql/sql_select.h index 48515042a02..99e5edd3580 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1476,8 +1476,11 @@ public: { return (unit->item && unit->item->is_in_predicate()); } - int save_explain_data(Explain_query *output, bool need_tmp_table, - bool need_order, bool distinct, const char *message); + void save_explain_data(Explain_query *output, bool can_overwrite, + bool need_tmp_table, bool need_order, bool distinct); + int save_explain_data_intern(Explain_query *output, bool need_tmp_table, + bool need_order, bool distinct, + const char *message); private: /** TRUE if the query contains an aggregate function but has no GROUP From a06170c8d816df4650847470dba46d199f1bba25 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 Oct 2013 10:34:46 +0400 Subject: [PATCH 62/64] MDEV-3798: EXPLAIN UPDATE/DELETE - Fix a problem with EXPLAIN multi_table UPDATE: = Do use multi_update object, because multi_update::prepare() does various setup, e.g. it disables index-only for the tables to be updated. = Protect multi_update::prepare() from being invoked multiple times. If the query has subqueries, they may try to invoke it, for some reason. --- mysql-test/r/explain_non_select.result | 2 +- sql/sql_class.h | 4 ++- sql/sql_update.cc | 44 ++++++++------------------ 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 42ae7bd4ebd..4cc36a642a5 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -100,7 +100,7 @@ id select_type table type possible_keys key key_len ref rows Extra explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where -1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +1 SIMPLE t1 ref a a 5 test.t0.a 4 drop table t0, t1; # # Try DELETE ... RETURNING ... diff --git a/sql/sql_class.h b/sql/sql_class.h index ba14af850be..9cb32eaa54a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -4181,7 +4181,9 @@ class multi_update :public select_result_interceptor so that afterward send_error() needs to find out that. */ bool error_handled; - + + /* Need this to protect against multiple prepare() calls */ + bool prepared; public: multi_update(TABLE_LIST *ut, List *leaves_list, List *fields, List *values, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 6a58b4e8029..786e058c99d 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1464,32 +1464,14 @@ bool mysql_multi_update(THD *thd, multi_update **result) { bool res; - select_result *output; - bool explain= test(thd->lex->describe); DBUG_ENTER("mysql_multi_update"); - if (explain) + if (!(*result= new multi_update(table_list, + &thd->lex->select_lex.leaf_tables, + fields, values, + handle_duplicates, ignore))) { - /* Handle EXPLAIN UPDATE */ - if (!(output= new select_send()) || - thd->send_explain_fields(output)) - { - delete output; - DBUG_RETURN(TRUE); - } - select_lex->set_explain_type(FALSE); - *result= NULL; /* no multi_update object */ - } - else - { - if (!(*result= new multi_update(table_list, - &thd->lex->select_lex.leaf_tables, - fields, values, - handle_duplicates, ignore))) - { - DBUG_RETURN(TRUE); - } - output= *result; + DBUG_RETURN(TRUE); } thd->abort_on_warning= test(thd->variables.sql_mode & @@ -1504,7 +1486,7 @@ bool mysql_multi_update(THD *thd, (ORDER *)NULL, options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | OPTION_SETUP_TABLES_DONE, - output, unit, select_lex); + *result, unit, select_lex); DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); @@ -1512,12 +1494,8 @@ bool mysql_multi_update(THD *thd, (*result)->abort_result_set(); else { - if (explain) - { - thd->lex->explain->print_explain(output, thd->lex->describe); - output->send_eof(); - delete output; - } + if (thd->lex->describe) + res= thd->lex->explain->send_explain(thd); } thd->abort_on_warning= 0; DBUG_RETURN(res); @@ -1533,7 +1511,7 @@ multi_update::multi_update(TABLE_LIST *table_list, tmp_tables(0), updated(0), found(0), fields(field_list), values(value_list), table_count(0), copy_field(0), handle_duplicates(handle_duplicates_arg), do_update(1), trans_safe(1), - transactional_tables(0), ignore(ignore_arg), error_handled(0) + transactional_tables(0), ignore(ignore_arg), error_handled(0), prepared(0) {} @@ -1556,6 +1534,10 @@ int multi_update::prepare(List ¬_used_values, List_iterator ti(*leaves); DBUG_ENTER("multi_update::prepare"); + if (prepared) + DBUG_RETURN(0); + prepared= true; + thd->count_cuted_fields= CHECK_FIELD_WARN; thd->cuted_fields=0L; thd_proc_info(thd, "updating main table"); From e522ebc221358f541b8a1301529611c8652dbeb0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 Oct 2013 10:36:39 +0400 Subject: [PATCH 63/64] Update test results for the last cset. --- mysql-test/r/myisam_explain_non_select_all.result | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mysql-test/r/myisam_explain_non_select_all.result b/mysql-test/r/myisam_explain_non_select_all.result index 167e9129957..86e3ffbff6c 100644 --- a/mysql-test/r/myisam_explain_non_select_all.result +++ b/mysql-test/r/myisam_explain_non_select_all.result @@ -2641,6 +2641,8 @@ Sort_scan 1 # EXPLAIN UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 2 MATERIALIZED ALL NULL NULL NULL NULL 3 3 DERIVED t2 ALL NULL NULL NULL NULL 3 Using filesort @@ -2648,6 +2650,8 @@ FLUSH STATUS; FLUSH TABLES; EXPLAIN EXTENDED UPDATE t1, t2 SET a = 10 WHERE a IN (SELECT * FROM (SELECT b FROM t2 ORDER BY b LIMIT 2,2) x); id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 +1 PRIMARY eq_ref distinct_key distinct_key 4 func 1 100.00 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 100.00 2 MATERIALIZED ALL NULL NULL NULL NULL 3 100.00 3 DERIVED t2 ALL NULL NULL NULL NULL 3 100.00 Using filesort From 25d0175fde2bff7397628d91c30aac8ba9d56301 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 Oct 2013 11:43:34 +0400 Subject: [PATCH 64/64] MDEV-3798: EXPLAIN UPDATE/DELETE: - Port grant_explain_non_select.{test,result} from mysql-5.6 - Per Sanja's hint, fix mysql_make_view() to take into account that EXPLAIN now is not necessarily EXPLAIN SELECT. --- mysql-test/r/grant_explain_non_select.result | 178 +++++++++++++ mysql-test/t/grant_explain_non_select.test | 258 +++++++++++++++++++ sql/sql_view.cc | 3 +- 3 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 mysql-test/r/grant_explain_non_select.result create mode 100644 mysql-test/t/grant_explain_non_select.test diff --git a/mysql-test/r/grant_explain_non_select.result b/mysql-test/r/grant_explain_non_select.result new file mode 100644 index 00000000000..c18f5334e35 --- /dev/null +++ b/mysql-test/r/grant_explain_non_select.result @@ -0,0 +1,178 @@ +CREATE DATABASE privtest_db; +CREATE TABLE privtest_db.t1 (a INT); +CREATE TABLE privtest_db.t2 (a INT); +INSERT INTO privtest_db.t2 VALUES (1), (2), (3); +GRANT USAGE ON *.* TO 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t2 TO 'privtest'@'localhost'; +USE privtest_db; +EXPLAIN INSERT INTO t1 VALUES (10); +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +INSERT INTO t1 VALUES (10); +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN INSERT INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +INSERT INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +GRANT INSERT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN INSERT INTO t1 VALUES (10); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +INSERT INTO t1 VALUES (10); +EXPLAIN INSERT INTO t1 SELECT * FROM t2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 +INSERT INTO t1 SELECT * FROM t2; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +EXPLAIN REPLACE INTO t1 VALUES (10); +ERROR 42000: INSERT,DELETE command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 VALUES (10); +ERROR 42000: INSERT,DELETE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT,DELETE command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT,DELETE command denied to user 'privtest'@'localhost' for table 't1' +GRANT INSERT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN REPLACE INTO t1 VALUES (10); +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 VALUES (10); +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +REVOKE INSERT ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN REPLACE INTO t1 VALUES (10); +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 VALUES (10); +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +REPLACE INTO t1 SELECT * FROM t2; +ERROR 42000: INSERT command denied to user 'privtest'@'localhost' for table 't1' +GRANT INSERT, DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN REPLACE INTO t1 VALUES (10); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +REPLACE INTO t1 VALUES (10); +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 +REPLACE INTO t1 SELECT * FROM t2; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +EXPLAIN UPDATE t1 SET a = a + 1; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +UPDATE t1 SET a = a + 1; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +GRANT UPDATE ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN UPDATE t1 SET a = a + 1; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +UPDATE t1 SET a = a + 1; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +REVOKE UPDATE ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN UPDATE t1 SET a = a + 1; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +UPDATE t1 SET a = a + 1; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +ERROR 42000: UPDATE command denied to user 'privtest'@'localhost' for table 't1' +GRANT UPDATE, SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN UPDATE t1 SET a = a + 1; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 +UPDATE t1 SET a = a + 1; +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 Using where +UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +EXPLAIN DELETE FROM t1 WHERE a = 10; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +DELETE FROM t1 WHERE a = 10; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +GRANT DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN DELETE FROM t1 WHERE a = 10; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +DELETE FROM t1 WHERE a = 10; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for column 'a' in table 't1' +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: SELECT command denied to user 'privtest'@'localhost' for table 't1' +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN DELETE FROM t1 WHERE a = 10; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +DELETE FROM t1 WHERE a = 10; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +ERROR 42000: DELETE command denied to user 'privtest'@'localhost' for table 't1' +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT DELETE, SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +EXPLAIN DELETE FROM t1 WHERE a = 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 Using where +DELETE FROM t1 WHERE a = 10; +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 3 +1 SIMPLE t1 ALL NULL NULL NULL NULL 8 Using where +DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +CREATE VIEW privtest_db.v1 (a) AS SELECT a FROM privtest_db.t1; +GRANT SELECT, INSERT, UPDATE, DELETE ON privtest_db.v1 TO 'privtest'@'localhost'; +EXPLAIN SELECT * FROM v1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +SELECT * FROM v1; +a +11 +4 +4 +11 +4 +4 +EXPLAIN INSERT INTO v1 VALUES (10); +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +INSERT INTO v1 VALUES (10); +EXPLAIN INSERT INTO v1 SELECT * FROM t2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +INSERT INTO v1 SELECT * FROM t2; +EXPLAIN REPLACE INTO v1 VALUES (10); +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +REPLACE INTO v1 VALUES (10); +EXPLAIN REPLACE INTO v1 SELECT * FROM t2; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +REPLACE INTO v1 SELECT * FROM t2; +EXPLAIN UPDATE v1 SET a = a + 1; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +UPDATE v1 SET a = a + 1; +EXPLAIN UPDATE v1, t2 SET v1.a = v1.a + 1 WHERE v1.a = t2.a; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +UPDATE v1, t2 SET v1.a = v1.a + 1 WHERE v1.a = t2.a; +EXPLAIN DELETE FROM v1 WHERE a = 10; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +DELETE FROM v1 WHERE a = 10; +EXPLAIN DELETE FROM v1 USING v1, t2 WHERE v1.a = t2.a; +ERROR HY000: EXPLAIN/SHOW can not be issued; lacking privileges for underlying table +DELETE FROM v1 USING v1, t2 WHERE v1.a = t2.a; +DROP USER 'privtest'@localhost; +USE test; +DROP DATABASE privtest_db; diff --git a/mysql-test/t/grant_explain_non_select.test b/mysql-test/t/grant_explain_non_select.test new file mode 100644 index 00000000000..d59a8f3d8ce --- /dev/null +++ b/mysql-test/t/grant_explain_non_select.test @@ -0,0 +1,258 @@ +# +# Privilege-specific tests for WL#4897: Add EXPLAIN INSERT/UPDATE/DELETE +# + +# Grant tests not performed with embedded server +-- source include/not_embedded.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +CREATE DATABASE privtest_db; + +CREATE TABLE privtest_db.t1 (a INT); +CREATE TABLE privtest_db.t2 (a INT); +INSERT INTO privtest_db.t2 VALUES (1), (2), (3); + +GRANT USAGE ON *.* TO 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t2 TO 'privtest'@'localhost'; + +connect(con1,localhost,privtest,,); +connection con1; + +USE privtest_db; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN INSERT INTO t1 VALUES (10); +--error ER_TABLEACCESS_DENIED_ERROR + INSERT INTO t1 VALUES (10); + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN INSERT INTO t1 SELECT * FROM t2; +--error ER_TABLEACCESS_DENIED_ERROR + INSERT INTO t1 SELECT * FROM t2; + +connection default; +GRANT INSERT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +EXPLAIN INSERT INTO t1 VALUES (10); + INSERT INTO t1 VALUES (10); + +EXPLAIN INSERT INTO t1 SELECT * FROM t2; + INSERT INTO t1 SELECT * FROM t2; + + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 VALUES (10); +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 VALUES (10); + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 SELECT * FROM t2; + +connection default; +GRANT INSERT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 VALUES (10); +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 VALUES (10); + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 SELECT * FROM t2; + +connection default; +REVOKE INSERT ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 VALUES (10); +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 VALUES (10); + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; +--error ER_TABLEACCESS_DENIED_ERROR + REPLACE INTO t1 SELECT * FROM t2; + +connection default; +GRANT INSERT, DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +EXPLAIN REPLACE INTO t1 VALUES (10); + REPLACE INTO t1 VALUES (10); + +EXPLAIN REPLACE INTO t1 SELECT * FROM t2; + REPLACE INTO t1 SELECT * FROM t2; + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN UPDATE t1 SET a = a + 1; +--error ER_TABLEACCESS_DENIED_ERROR + UPDATE t1 SET a = a + 1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +--error ER_TABLEACCESS_DENIED_ERROR + UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; + +connection default; +GRANT UPDATE ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_COLUMNACCESS_DENIED_ERROR +EXPLAIN UPDATE t1 SET a = a + 1; +--error ER_COLUMNACCESS_DENIED_ERROR + UPDATE t1 SET a = a + 1; + +--error ER_COLUMNACCESS_DENIED_ERROR +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +--error ER_COLUMNACCESS_DENIED_ERROR + UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; + +connection default; +REVOKE UPDATE ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN UPDATE t1 SET a = a + 1; +--error ER_TABLEACCESS_DENIED_ERROR + UPDATE t1 SET a = a + 1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; +--error ER_TABLEACCESS_DENIED_ERROR + UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; + +connection default; +GRANT UPDATE, SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +EXPLAIN UPDATE t1 SET a = a + 1; + UPDATE t1 SET a = a + 1; + +EXPLAIN UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; + UPDATE t1, t2 SET t1.a = t1.a + 1 WHERE t1.a = t2.a; + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 WHERE a = 10; +--error ER_TABLEACCESS_DENIED_ERROR + DELETE FROM t1 WHERE a = 10; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +--error ER_TABLEACCESS_DENIED_ERROR + DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; + +connection default; +GRANT DELETE ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_COLUMNACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 WHERE a = 10; +--error ER_COLUMNACCESS_DENIED_ERROR + DELETE FROM t1 WHERE a = 10; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +--error ER_TABLEACCESS_DENIED_ERROR + DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 WHERE a = 10; +--error ER_TABLEACCESS_DENIED_ERROR + DELETE FROM t1 WHERE a = 10; + +--error ER_TABLEACCESS_DENIED_ERROR +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; +--error ER_TABLEACCESS_DENIED_ERROR + DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +GRANT DELETE, SELECT ON privtest_db.t1 TO 'privtest'@'localhost'; +connection con1; + +EXPLAIN DELETE FROM t1 WHERE a = 10; + DELETE FROM t1 WHERE a = 10; + +EXPLAIN DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; + DELETE FROM t1 USING t1, t2 WHERE t1.a = t2.a; + +# Views + +connection default; +REVOKE ALL PRIVILEGES ON privtest_db.t1 FROM 'privtest'@'localhost'; +CREATE VIEW privtest_db.v1 (a) AS SELECT a FROM privtest_db.t1; +GRANT SELECT, INSERT, UPDATE, DELETE ON privtest_db.v1 TO 'privtest'@'localhost'; +connection con1; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN SELECT * FROM v1; + SELECT * FROM v1; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN INSERT INTO v1 VALUES (10); + INSERT INTO v1 VALUES (10); + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN INSERT INTO v1 SELECT * FROM t2; + INSERT INTO v1 SELECT * FROM t2; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN REPLACE INTO v1 VALUES (10); + REPLACE INTO v1 VALUES (10); + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN REPLACE INTO v1 SELECT * FROM t2; + REPLACE INTO v1 SELECT * FROM t2; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN UPDATE v1 SET a = a + 1; + UPDATE v1 SET a = a + 1; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN UPDATE v1, t2 SET v1.a = v1.a + 1 WHERE v1.a = t2.a; + UPDATE v1, t2 SET v1.a = v1.a + 1 WHERE v1.a = t2.a; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN DELETE FROM v1 WHERE a = 10; + DELETE FROM v1 WHERE a = 10; + +--error ER_VIEW_NO_EXPLAIN +EXPLAIN DELETE FROM v1 USING v1, t2 WHERE v1.a = t2.a; + DELETE FROM v1 USING v1, t2 WHERE v1.a = t2.a; + +connection default; +disconnect con1; + +DROP USER 'privtest'@localhost; +USE test; +DROP DATABASE privtest_db; + +# Wait till we reached the initial number of concurrent sessions +--source include/wait_until_count_sessions.inc diff --git a/sql/sql_view.cc b/sql/sql_view.cc index 3ed9a5fda44..a4bf0ed63d0 100644 --- a/sql/sql_view.cc +++ b/sql/sql_view.cc @@ -1300,8 +1300,7 @@ bool mysql_make_view(THD *thd, File_parser *parser, TABLE_LIST *table, underlying tables. Skip this step if we are opening view for prelocking only. */ - if (!table->prelocking_placeholder && - (old_lex->sql_command == SQLCOM_SELECT && old_lex->describe)) + if (!table->prelocking_placeholder && (old_lex->describe)) { /* The user we run EXPLAIN as (either the connected user who issued