From 7e66213444a5af73879b57ad0b5bd7476b5c6f4d Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 23 Aug 2011 19:28:32 +0400 Subject: [PATCH 01/67] MWL#182: Explain running statements First code - "Asynchronous procedure call" system - new THD::check_killed() that serves APC request is called from within most important loops - EXPLAIN code is now able to generate EXPLAIN output on-the-fly [incomplete] Parts that are still missing: - put THD::check_killed() call into every loop where we could spend significant amount of time - Make sure EXPLAIN code works for group-by queries that replace JOIN::join_tab with make_simple_join() and other such cases. - User interface: what error code to use, where to get timeout settings from, etc. --- libmysqld/Makefile.am | 2 +- sql/Makefile.am | 7 +- sql/my_apc.cc | 355 ++++++++++++++++++++++++++++++++++++++++++ sql/my_apc.h | 98 ++++++++++++ sql/mysql_priv.h | 2 + sql/mysqld.cc | 1 + sql/protocol.h | 19 +++ sql/sp_head.cc | 1 + sql/sql_class.cc | 119 +++++++++++++- sql/sql_class.h | 94 ++++++++++- sql/sql_lex.cc | 34 ++++ sql/sql_lex.h | 6 + sql/sql_parse.cc | 27 ++++ sql/sql_prepare.cc | 1 + sql/sql_select.cc | 81 +++++++--- sql/sql_select.h | 7 +- sql/sql_show.cc | 91 +++++++++++ sql/sql_yacc.yy | 6 + 18 files changed, 915 insertions(+), 36 deletions(-) create mode 100644 sql/my_apc.cc create mode 100644 sql/my_apc.h diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am index bf2231f47a1..980de9a08ba 100644 --- a/libmysqld/Makefile.am +++ b/libmysqld/Makefile.am @@ -81,7 +81,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \ rpl_injector.cc my_user.c partition_info.cc \ sql_servers.cc event_parse_data.cc opt_table_elimination.cc \ multi_range_read.cc opt_index_cond_pushdown.cc \ - sql_expression_cache.cc + sql_expression_cache.cc my_apc.cc # automake misses these sql_yacc.cc sql_yacc.h: $(top_srcdir)/sql/sql_yacc.yy diff --git a/sql/Makefile.am b/sql/Makefile.am index cde8962b8d0..8ef828dd243 100644 --- a/sql/Makefile.am +++ b/sql/Makefile.am @@ -84,7 +84,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \ multi_range_read.h sql_handler.h \ sql_join_cache.h \ create_options.h \ - sql_expression_cache.h + sql_expression_cache.h \ + my_apc.h mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ item.cc item_sum.cc item_buff.cc item_func.cc \ @@ -134,7 +135,9 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \ sql_servers.cc event_parse_data.cc \ opt_table_elimination.cc create_options.cc \ multi_range_read.cc \ - opt_index_cond_pushdown.cc sql_expression_cache.cc + opt_index_cond_pushdown.cc sql_expression_cache.cc \ + my_apc.cc + nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c client_plugin.c diff --git a/sql/my_apc.cc b/sql/my_apc.cc new file mode 100644 index 00000000000..3842947f3bb --- /dev/null +++ b/sql/my_apc.cc @@ -0,0 +1,355 @@ +/* + TODO: MP AB Copyright +*/ + + +#ifdef MY_APC_STANDALONE + +#include +#include +#include + +#else + +#include "mysql_priv.h" + +#endif + +//#include "my_apc.h" + +/* + Standalone testing: + g++ -c -DMY_APC_STANDALONE -g -I.. -I../include -o my_apc.o my_apc.cc + g++ -L../mysys -L../dbug -L../strings my_apc.o -lmysys -ldbug -lmystrings -lpthread -lrt +*/ + + +void Apc_target::init() +{ + // todo: should use my_pthread_... functions instead? + DBUG_ASSERT(!enabled); + (void)pthread_mutex_init(&LOCK_apc_queue, MY_MUTEX_INIT_SLOW); +} + + +void Apc_target::destroy() +{ + DBUG_ASSERT(!enabled); + pthread_mutex_destroy(&LOCK_apc_queue); +} + + +void Apc_target::enable() +{ + pthread_mutex_lock(&LOCK_apc_queue); + enabled++; + pthread_mutex_unlock(&LOCK_apc_queue); +} + + +void Apc_target::disable() +{ + bool process= FALSE; + pthread_mutex_lock(&LOCK_apc_queue); + if (!(--enabled)) + process= TRUE; + pthread_mutex_unlock(&LOCK_apc_queue); + if (process) + process_apc_requests(); +} + +void Apc_target::enqueue_request(Call_request *qe) +{ + //call_queue_size++; + if (apc_calls) + { + Call_request *after= apc_calls->prev; + qe->next= apc_calls; + apc_calls->prev= qe; + + qe->prev= after; + after->next= qe; + } + else + { + apc_calls= qe; + qe->next= qe->prev= qe; + } +} + +void Apc_target::dequeue_request(Call_request *qe) +{ + //call_queue_size--; + if (apc_calls == qe) + { + if ((apc_calls= apc_calls->next) == qe) + { + //DBUG_ASSERT(!call_queue_size); + apc_calls= NULL; + } + } + + qe->prev->next= qe->next; + qe->next->prev= qe->prev; +} + + +/* + Make an apc call in another thread. The caller is responsible so + that we're not calling to ourselves. + +*/ + +bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, + int timeout_sec, bool *timed_out) +{ + bool res= TRUE; + *timed_out= FALSE; + + pthread_mutex_lock(&LOCK_apc_queue); + if (enabled) + { + /* Create and post the request */ + Call_request apc_request; + apc_request.func= func; + apc_request.func_arg= func_arg; + apc_request.done= FALSE; + (void)pthread_cond_init(&apc_request.COND_request, NULL); + (void)pthread_mutex_init(&apc_request.LOCK_request, MY_MUTEX_INIT_SLOW); + pthread_mutex_lock(&apc_request.LOCK_request); + enqueue_request(&apc_request); + apc_request.what="enqueued by make_apc_call"; + pthread_mutex_unlock(&LOCK_apc_queue); + + struct timespec abstime; + const int timeout= timeout_sec; + set_timespec(abstime, timeout); + + int wait_res= 0; + /* todo: how about processing other errors here? */ + while (!apc_request.done && (wait_res != ETIMEDOUT)) + { + wait_res= pthread_cond_timedwait(&apc_request.COND_request, + &apc_request.LOCK_request, &abstime); + } + + if (!apc_request.done) + { + /* We timed out */ + apc_request.done= TRUE; + *timed_out= TRUE; + pthread_mutex_unlock(&apc_request.LOCK_request); + + pthread_mutex_lock(&LOCK_apc_queue); + dequeue_request(&apc_request); + pthread_mutex_unlock(&LOCK_apc_queue); + res= TRUE; + } + else + { + /* Request was successfully executed and dequeued by the target thread */ + pthread_mutex_unlock(&apc_request.LOCK_request); + res= FALSE; + } + + /* Destroy all APC request data */ + pthread_mutex_destroy(&apc_request.LOCK_request); + pthread_cond_destroy(&apc_request.COND_request); + } + else + { + pthread_mutex_unlock(&LOCK_apc_queue); + } + return res; +} + + +/* + Process all APC requests +*/ + +void Apc_target::process_apc_requests() +{ + while (1) + { + Call_request *request; + + pthread_mutex_lock(&LOCK_apc_queue); + if (!(request= get_first_in_queue())) + { + pthread_mutex_unlock(&LOCK_apc_queue); + break; + } + + request->what="seen by process_apc_requests"; + pthread_mutex_lock(&request->LOCK_request); + + if (request->done) + { + /* + We can get here when + - the requestor thread has been waiting for this request + - the wait has timed out + - it has set request->done=TRUE + - it has released LOCK_request, because its next action + will be to remove the request from the queue, however, + it could not attempt to lock the queue while holding the lock on + request, because that would deadlock with this function + (we here first lock the queue and then lock the request) + */ + pthread_mutex_unlock(&request->LOCK_request); + pthread_mutex_unlock(&LOCK_apc_queue); + fprintf(stderr, "Whoa rare event #1!\n"); + continue; + } + /* + Remove the request from the queue (we're holding its lock so we can be + sure that request owner won't try to remove it) + */ + request->what="dequeued by process_apc_requests"; + dequeue_request(request); + request->done= TRUE; + + pthread_mutex_unlock(&LOCK_apc_queue); + + request->func(request->func_arg); + request->what="func called by process_apc_requests"; + + pthread_cond_signal(&request->COND_request); + + pthread_mutex_unlock(&request->LOCK_request); + } +} + +/***************************************************************************** + * Testing + *****************************************************************************/ +#ifdef MY_APC_STANDALONE + +volatile bool started= FALSE; +volatile bool service_should_exit= FALSE; +volatile bool requestors_should_exit=FALSE; + +volatile int apcs_served= 0; +volatile int apcs_missed=0; +volatile int apcs_timed_out=0; + +Apc_target apc_target; + +int int_rand(int size) +{ + return round (((double)rand() / RAND_MAX) * size); +} + +/* An APC-serving thread */ +void *test_apc_service_thread(void *ptr) +{ + my_thread_init(); + apc_target.init(); + apc_target.enable(); + started= TRUE; + fprintf(stderr, "# test_apc_service_thread started\n"); + while (!service_should_exit) + { + //apc_target.disable(); + usleep(10000); + //apc_target.enable(); + for (int i = 0; i < 10 && !service_should_exit; i++) + { + apc_target.process_apc_requests(); + usleep(int_rand(30)); + } + } + apc_target.disable(); + apc_target.destroy(); + my_thread_end(); + pthread_exit(0); +} + +class Apc_order +{ +public: + int value; // The value + int *where_to; // Where to write it + Apc_order(int a, int *b) : value(a), where_to(b) {} +}; + +void test_apc_func(void *arg) +{ + Apc_order *order=(Apc_order*)arg; + usleep(int_rand(1000)); + *(order->where_to) = order->value; + __sync_fetch_and_add(&apcs_served, 1); +} + +void *test_apc_requestor_thread(void *ptr) +{ + my_thread_init(); + fprintf(stderr, "# test_apc_requestor_thread started\n"); + while (!requestors_should_exit) + { + int dst_value= 0; + int src_value= int_rand(4*1000*100); + /* Create APC to do dst_value= src_value */ + Apc_order apc_order(src_value, &dst_value); + bool timed_out; + + bool res= apc_target.make_apc_call(test_apc_func, (void*)&apc_order, 60, &timed_out); + if (res) + { + if (timed_out) + __sync_fetch_and_add(&apcs_timed_out, 1); + else + __sync_fetch_and_add(&apcs_missed, 1); + + if (dst_value != 0) + fprintf(stderr, "APC was done even though return value says it wasnt!\n"); + } + else + { + if (dst_value != src_value) + fprintf(stderr, "APC was not done even though return value says it was!\n"); + } + //usleep(300); + } + fprintf(stderr, "# test_apc_requestor_thread exiting\n"); + my_thread_end(); +} + +const int N_THREADS=23; +int main(int args, char **argv) +{ + pthread_t service_thr; + pthread_t request_thr[N_THREADS]; + int i, j; + my_thread_global_init(); + + pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL); + while (!started) + usleep(1000); + for (i = 0; i < N_THREADS; i++) + pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL); + + for (i = 0; i < 15; i++) + { + usleep(500*1000); + fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed); + } + fprintf(stderr, "# Shutting down requestors\n"); + requestors_should_exit= TRUE; + for (i = 0; i < N_THREADS; i++) + pthread_join(request_thr[i], NULL); + + fprintf(stderr, "# Shutting down service\n"); + service_should_exit= TRUE; + pthread_join(service_thr, NULL); + fprintf(stderr, "# Done.\n"); + my_thread_end(); + my_thread_global_end(); + return 0; +} + +#endif // MY_APC_STANDALONE + + + diff --git a/sql/my_apc.h b/sql/my_apc.h new file mode 100644 index 00000000000..3783bd28f54 --- /dev/null +++ b/sql/my_apc.h @@ -0,0 +1,98 @@ +/* + TODO: MP AB Copyright +*/ + +/* + Design + - Mutex-guarded request queue (it belongs to the target), which can be enabled/ + disabled (when empty). + + - After the request has been put into queue, the requestor waits for request + to be satisfied. The worker satisifes the request and signals the + requestor. +*/ + +/* + Target for asynchronous calls. +*/ +class Apc_target +{ +public: + Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {} + ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);} + + /* + Initialize the target. This must be called before anything else. Right + after initialization, the target is disabled. + */ + void init(); + + /* + Destroy the target. The target must be disabled when this call is made. + */ + void destroy(); + + /* + Enter into state where this target will be serving APC requests + */ + void enable(); + + /* + Leave the state where we could serve APC requests (will serve all already + enqueued requests) + */ + void disable(); + + /* + This should be called periodically to serve observation requests. + */ + void process_apc_requests(); + + typedef void (*apc_func_t)(void *arg); + + /* + Make an APC call: schedule it for execution and wait until the target + thread has executed it. This function must not be called from a thread + that's different from the target thread. + + @retval FALSE - Ok, the call has been made + @retval TRUE - Call wasnt made (either the target is in disabled state or + timeout occured) + */ + bool make_apc_call(apc_func_t func, void *func_arg, + int timeout_sec, bool *timed_out); + +private: + class Call_request; + int enabled; + + Call_request *apc_calls; + pthread_mutex_t LOCK_apc_queue; + //int call_queue_size; + + class Call_request + { + public: + apc_func_t func; + void *func_arg; + bool done; + + pthread_mutex_t LOCK_request; + pthread_cond_t COND_request; + + Call_request *next; + Call_request *prev; + + const char *what; + }; + + void enqueue_request(Call_request *qe); + void dequeue_request(Call_request *qe); + Call_request *get_first_in_queue() + { + return apc_calls; + } +}; + +/////////////////////////////////////////////////////////////////////// + diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 24e637df497..a19cca31d1c 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -803,6 +803,7 @@ typedef my_bool (*qc_engine_callback)(THD *thd, char *table_key, ulonglong *engine_data); #include "sql_string.h" #include "my_decimal.h" +#include "my_apc.h" /* to unify the code that differs only in the argument passed to the @@ -1544,6 +1545,7 @@ bool mysqld_show_create(THD *thd, TABLE_LIST *table_list); bool mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create); void mysqld_list_processes(THD *thd,const char *user,bool verbose); +void mysqld_show_explain(THD *thd, ulong thread_id); int mysqld_show_status(THD *thd); int mysqld_show_variables(THD *thd,const char *wild); bool mysqld_show_storage_engines(THD *thd); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1c9b6a19d38..04439b470ce 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3430,6 +3430,7 @@ SHOW_VAR com_status_vars[]= { {"show_engine_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_STATUS]), SHOW_LONG_STATUS}, {"show_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EVENTS]), SHOW_LONG_STATUS}, {"show_errors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ERRORS]), SHOW_LONG_STATUS}, + {"show_explain", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EXPLAIN]), SHOW_LONG_STATUS}, {"show_fields", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FIELDS]), SHOW_LONG_STATUS}, #ifndef DBUG_OFF {"show_function_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FUNC_CODE]), SHOW_LONG_STATUS}, diff --git a/sql/protocol.h b/sql/protocol.h index e07af5208db..7a882e8a10d 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -28,6 +28,7 @@ class Protocol protected: THD *thd; String *packet; + /* Used by net_store_data() for charset conversions */ String *convert; uint field_pos; #ifndef DBUG_OFF @@ -42,6 +43,10 @@ protected: MYSQL_FIELD *next_mysql_field; MEM_ROOT *alloc; #endif + /* + The following two are low-level functions that are invoked from + higher-level store_xxx() funcs. The data is stored into this->packet. + */ bool net_store_data(const uchar *from, size_t length, CHARSET_INFO *fromcs, CHARSET_INFO *tocs); bool store_string_aux(const char *from, size_t length, @@ -55,6 +60,20 @@ public: enum { SEND_NUM_ROWS= 1, SEND_DEFAULTS= 2, SEND_EOF= 4 }; virtual bool send_fields(List *list, uint flags); + void get_packet(const char **start, size_t *length) + { + *start= packet->ptr(); + *length= packet->length(); + } + void set_packet(const char *start, size_t len) + { + packet->length(0); + packet->append(start, len); +#ifndef DBUG_OFF + field_pos= field_count - 1; +#endif + } + bool store(I_List *str_list); bool store(const char *from, CHARSET_INFO *cs); String *storage_packet() { return packet; } diff --git a/sql/sp_head.cc b/sql/sp_head.cc index bd2dcfd8653..93743c2985b 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -204,6 +204,7 @@ sp_get_flags_for_command(LEX *lex) case SQLCOM_SHOW_CREATE_TRIGGER: case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_ERRORS: + case SQLCOM_SHOW_EXPLAIN: case SQLCOM_SHOW_FIELDS: case SQLCOM_SHOW_FUNC_CODE: case SQLCOM_SHOW_GRANTS: diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 9bb1f83b06d..12709e23a34 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -964,6 +964,7 @@ void THD::init(void) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ debug_sync_init_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ + apc_target.init(); } @@ -1122,7 +1123,8 @@ void THD::cleanup(void) pthread_mutex_unlock(&LOCK_user_locks); ull= NULL; } - + + apc_target.destroy(); cleanup_done=1; DBUG_VOID_RETURN; } @@ -1666,6 +1668,14 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) int THD::send_explain_fields(select_result *result) { List field_list; + make_explain_field_list(field_list); + return (result->send_fields(field_list, + Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); +} + + +void THD::make_explain_field_list(List &field_list) +{ Item *item; CHARSET_INFO *cs= system_charset_info; field_list.push_back(new Item_return_int("id",3, MYSQL_TYPE_LONGLONG)); @@ -1703,10 +1713,9 @@ int THD::send_explain_fields(select_result *result) } item->maybe_null= 1; field_list.push_back(new Item_empty_string("Extra", 255, cs)); - return (result->send_fields(field_list, - Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)); } + #ifdef SIGNAL_WITH_VIO_CLOSE void THD::close_active_vio() { @@ -1810,6 +1819,21 @@ void THD::rollback_item_tree_changes() } +/* + Check if the thread has been killed, and also process "APC requests" + + @retval true The thread is killed, execution should be interrupted + @retval false Not killed, continue execution +*/ + +bool THD::check_killed() +{ + if (killed) + return TRUE; + apc_target.process_apc_requests(); + return FALSE; +} + /***************************************************************************** ** Functions to provide a interface to select results *****************************************************************************/ @@ -1950,6 +1974,68 @@ int select_send::send_data(List &items) DBUG_RETURN(0); } + +////////////////////////////////////////////////////////////////////////////// +int select_result_explain_buffer::send_data(List &items) +{ + List_iterator_fast li(items); + char buff[MAX_FIELD_WIDTH]; + String buffer(buff, sizeof(buff), &my_charset_bin); + DBUG_ENTER("select_send::send_data"); + + protocol->prepare_for_resend(); + Item *item; + while ((item=li++)) + { + if (item->send(protocol, &buffer)) + { + protocol->free(); // Free used buffer + my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); + break; + } + /* + Reset buffer to its original state, as it may have been altered in + Item::send(). + */ + buffer.set(buff, sizeof(buff), &my_charset_bin); + } + //TODO: do we need the following: + if (thd->is_error()) + { + protocol->remove_last_row(); + DBUG_RETURN(1); + } + /* psergey-TODO: instead of protocol->write(), steal the packet here */ + const char *packet_data; + size_t len; + protocol->get_packet(&packet_data, &len); + + String *s= new (thd->mem_root) String; + s->append(packet_data, len); + data_rows.push_back(s); + protocol->remove_last_row(); // <-- this does nothing. Do we need it? + // prepare_for_resend() will wipe out the packet + DBUG_RETURN(0); +} + + +void select_result_explain_buffer::flush_data() +{ + List_iterator it(data_rows); + String *str; + while ((str= it++)) + { + /* TODO: write out the lines. */ + protocol->set_packet(str->ptr(), str->length()); + protocol->write(); + delete str; + } + data_rows.empty(); +} + +////////////////////////////////////////////////////////////////////////////// + + bool select_send::send_eof() { /* @@ -2810,6 +2896,10 @@ void THD::end_statement() } +/* + Start using arena specified by @set. Current arena data will be saved to + *backup. +*/ void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup) { DBUG_ENTER("THD::set_n_backup_active_arena"); @@ -2824,6 +2914,12 @@ void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup) } +/* + Stop using the temporary arena, and start again using the arena that is + specified in *backup. + The temporary arena is returned back into *set. +*/ + void THD::restore_active_arena(Query_arena *set, Query_arena *backup) { DBUG_ENTER("THD::restore_active_arena"); @@ -2836,6 +2932,23 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup) DBUG_VOID_RETURN; } +// psergey +void Show_explain_request::get_explain_data(void *arg) +{ + Show_explain_request *req= (Show_explain_request*)arg; + //TODO: change mem_root to point to request_thd->mem_root. + // Actually, change the ARENA, because we're going to allocate items! + Query_arena backup_arena; + req->target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, + &backup_arena); + + req->target_thd->lex->unit.print_explain(req->explain_buf); + + req->target_thd->restore_active_arena((Query_arena*)req->request_thd, + &backup_arena); +} + + Statement::~Statement() { } diff --git a/sql/sql_class.h b/sql/sql_class.h index c97cc34e166..5d55d7182fc 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1420,6 +1420,19 @@ struct Ha_data }; +class select_result_explain_buffer; + +class Show_explain_request +{ +public: + THD *target_thd; + THD *request_thd; + + select_result_explain_buffer *explain_buf; + + static void get_explain_data(void *arg); +}; + /** @class THD For each client connection we create a separate thread with THD serving as @@ -1990,6 +2003,8 @@ public: }; killed_state volatile killed; + bool check_killed(); + /* scramble - random string sent to client on handshake */ char scramble[SCRAMBLE_LENGTH+1]; @@ -2171,6 +2186,16 @@ public: void close_active_vio(); #endif void awake(THD::killed_state state_to_set); + + + /* + This is what allows this thread to serve as a target for others to + schedule Async Procedure Calls on. + + It's possible to schedule arbitrary C function call but currently this + facility is used only by SHOW EXPLAIN code (See Show_explain_request) + */ + Apc_target apc_target; #ifndef MYSQL_CLIENT enum enum_binlog_query_type { @@ -2302,6 +2327,7 @@ public: void add_changed_table(const char *key, long key_length); CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length); int send_explain_fields(select_result *result); + void make_explain_field_list(List &field_list); #ifndef EMBEDDED_LIBRARY /** Clear the current error, if any. @@ -2750,10 +2776,42 @@ public: class JOIN; -class select_result :public Sql_alloc { +/* Pure interface for sending tabular data */ +class select_result_sink: public Sql_alloc +{ +public: + /* + send_data returns 0 on ok, 1 on error and -1 if data was ignored, for + example for a duplicate row entry written to a temp table. + */ + virtual int send_data(List &items)=0; + virtual ~select_result_sink() {}; +}; + + +/* + Interface for sending tabular data, together with some other stuff: + + - Primary purpose seems to be seding typed tabular data: + = the DDL is sent with send_fields() + = the rows are sent with send_data() + Besides that, + - there seems to be an assumption that the sent data is a result of + SELECT_LEX_UNIT *unit, + - nest_level is used by SQL parser +*/ + +class select_result :public select_result_sink +{ protected: THD *thd; + /* + All descendant classes have their send_data() skip the first + unit->offset_limit_cnt rows sent. Select_materialize + also uses unit->get_unit_column_types(). + */ SELECT_LEX_UNIT *unit; + /* Something used only by the parser: */ int nest_level; public: select_result(); @@ -2772,11 +2830,6 @@ public: virtual uint field_count(List &fields) const { return fields.elements; } virtual bool send_fields(List &list, uint flags)=0; - /* - send_data returns 0 on ok, 1 on error and -1 if data was ignored, for - example for a duplicate row entry written to a temp table. - */ - virtual int send_data(List &items)=0; virtual bool initialize_tables (JOIN *join=0) { return 0; } virtual void send_error(uint errcode,const char *err); virtual bool send_eof()=0; @@ -2809,6 +2862,35 @@ public: }; +/* + A select result sink that collects the sent data and then can flush it to + network when requested. + + This class is targeted at collecting EXPLAIN output: + - Unoptimized data storage (can't handle big datasets) + - Unlike select_result class, we don't assume that the sent data is an + output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the + unit) +*/ + +class select_result_explain_buffer : public select_result_sink +{ +public: + THD *thd; + Protocol *protocol; + select_result_explain_buffer(){}; + + /* The following is called in the child thread: */ + int send_data(List &items); + + /* this will be called in the parent thread: */ + void flush_data(); + + List data_rows; +}; + + + /* Base class for select_result descendands which intercept and transform result set rows. As the rows are not sent to the client, diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 021e7a3b5e8..ae9c11c47e2 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3623,6 +3623,40 @@ bool st_select_lex::save_prep_leaf_tables(THD *thd) } +int st_select_lex::print_explain(select_result_sink *output) +{ + if (join && join->optimized == 2) + { + //psergey-TODO: any? + return join->print_explain(output, TRUE, + FALSE, // need_tmp_table, + FALSE, // bool need_order, + FALSE, // bool distinct, + NULL); //const char *message + } + else + { + DBUG_ASSERT(0); + /* produce "not yet optimized" line */ + } + return 0; +} + + +int st_select_lex_unit::print_explain(select_result_sink *output) +{ + int res= 0; + SELECT_LEX *first= first_select(); + + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) + { + if ((res= sl->print_explain(output))) + break; + } + return res; +} + + /** 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 fe79e6e2908..d5dc4dc3a1f 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -120,6 +120,7 @@ enum enum_sql_command { SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES, SQLCOM_SHOW_USER_STATS, SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS, SQLCOM_SHOW_CLIENT_STATS, + SQLCOM_SHOW_EXPLAIN, /* When a command is added here, be sure it's also added in mysqld.cc @@ -253,6 +254,8 @@ typedef uchar index_clause_map; #define INDEX_HINT_MASK_ALL (INDEX_HINT_MASK_JOIN | INDEX_HINT_MASK_GROUP | \ INDEX_HINT_MASK_ORDER) +class select_result_sink; + /* Single element of an USE/FORCE/IGNORE INDEX list specified as a SQL hint */ class Index_hint : public Sql_alloc { @@ -591,6 +594,7 @@ public: friend int subselect_union_engine::exec(); List *get_unit_column_types(); + int print_explain(select_result_sink *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -908,6 +912,8 @@ public: bool save_leaf_tables(THD *thd); bool save_prep_leaf_tables(THD *thd); + int print_explain(select_result_sink *output); + private: /* current index hint kind. used in filling up index_hints */ enum index_hint_type current_index_hint_type; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 18dad6cfff0..8e36da5f285 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -328,6 +328,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND; + sql_command_flags[SQLCOM_SHOW_EXPLAIN]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND; sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND; @@ -3402,6 +3403,32 @@ end_with_restore_list: thd->security_ctx->priv_user), lex->verbose); break; + case SQLCOM_SHOW_EXPLAIN: + { + /* Same security as SHOW PROCESSLIST (TODO check this) */ + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd,PROCESS_ACL)) + break; + + Item *it= (Item *)lex->value_list.head(); + + if (lex->table_or_sp_used()) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " + "function calls as part of this statement"); + break; + } + + if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1)) + { + my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + + mysqld_show_explain(thd, (ulong)it->val_int()); + break; + } case SQLCOM_SHOW_AUTHORS: res= mysqld_show_authors(thd); break; diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 5b5ed004006..465a026ad6e 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -2049,6 +2049,7 @@ static bool check_prepared_statement(Prepared_statement *stmt) case SQLCOM_SHOW_ENGINE_LOGS: case SQLCOM_SHOW_ENGINE_STATUS: case SQLCOM_SHOW_ENGINE_MUTEX: + case SQLCOM_SHOW_EXPLAIN: case SQLCOM_SHOW_CREATE_DB: case SQLCOM_SHOW_GRANTS: case SQLCOM_SHOW_BINLOG_EVENTS: diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5cc8078b9f0..74d2bc7c356 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -846,6 +846,12 @@ inject_jtbm_conds(JOIN *join, List *join_list, Item **join_where) DBUG_VOID_RETURN; } +int JOIN::optimize() +{ + int res= optimize_inner(); + optimized= 2; + return res; +} /** global select optimisation. @@ -859,7 +865,7 @@ inject_jtbm_conds(JOIN *join, List *join_list, Item **join_where) */ int -JOIN::optimize() +JOIN::optimize_inner() { ulonglong select_opts_for_readinfo; uint no_jbuf_after; @@ -2888,7 +2894,9 @@ mysql_select(THD *thd, Item ***rref_pointer_array, if (thd->is_error()) goto err; + thd->apc_target.enable(); join->exec(); + thd->apc_target.disable(); if (thd->cursor && thd->cursor->is_open()) { @@ -3529,7 +3537,7 @@ make_join_statistics(JOIN *join, List &tables_list, goto error; /* Generate an execution plan from the found optimal join order. */ - DBUG_RETURN(join->thd->killed || get_best_combination(join)); + DBUG_RETURN(join->thd->check_killed() || get_best_combination(join)); error: /* @@ -6276,7 +6284,7 @@ best_extension_by_limited_search(JOIN *join, DBUG_ENTER("best_extension_by_limited_search"); THD *thd= join->thd; - if (thd->killed) // Abort + if (thd->check_killed()) // Abort DBUG_RETURN(TRUE); DBUG_EXECUTE("opt", print_plan(join, idx, read_time, record_count, idx, @@ -6436,7 +6444,7 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count, { DBUG_ENTER("find_best"); THD *thd= join->thd; - if (thd->killed) + if (thd->check_killed()) DBUG_RETURN(TRUE); if (!rest_tables) { @@ -14452,7 +14460,7 @@ create_internal_tmp_table_from_heap2(THD *thd, TABLE *table, DBUG_EXECUTE_IF("raise_error", write_err= HA_ERR_FOUND_DUPP_KEY ;); if (write_err) goto err; - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); goto err_killed; @@ -14822,7 +14830,7 @@ sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) rc= sub_select(join, join_tab, end_of_records); DBUG_RETURN(rc); } - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -15121,7 +15129,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab, DBUG_RETURN(NESTED_LOOP_ERROR); if (error < 0) DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS); - if (join->thd->killed) // Aborted by user + if (join->thd->check_killed()) // Aborted by user { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -16250,7 +16258,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), TABLE *table=join->tmp_table; DBUG_ENTER("end_write"); - if (join->thd->killed) // Aborted by user + if (join->thd->check_killed()) // Aborted by user { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -16321,7 +16329,7 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (end_of_records) DBUG_RETURN(NESTED_LOOP_OK); - if (join->thd->killed) // Aborted by user + if (join->thd->check_killed()) // Aborted by user { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -16402,7 +16410,7 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), if (end_of_records) DBUG_RETURN(NESTED_LOOP_OK); - if (join->thd->killed) // Aborted by user + if (join->thd->check_killed()) // Aborted by user { join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -16449,7 +16457,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), int idx= -1; DBUG_ENTER("end_write_group"); - if (join->thd->killed) + if (join->thd->check_killed()) { // Aborted by user join->thd->send_kill_message(); DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */ @@ -18226,7 +18234,7 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field, error= file->ha_rnd_next(record); for (;;) { - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); error=0; @@ -18358,7 +18366,7 @@ static int remove_dup_with_hash_index(THD *thd, TABLE *table, for (;;) { uchar *org_key_pos; - if (thd->killed) + if (thd->check_killed()) { thd->send_kill_message(); error=0; @@ -20355,29 +20363,40 @@ void JOIN::clear() } } + /** EXPLAIN handling. Send a description about what how the select will be done to stdout. + + @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make + modifications to any select's data structures */ -static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, - bool distinct,const char *message) +int JOIN::print_explain(select_result_sink *result, 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; - select_result *result=join->result; Item *item_null= new Item_null(); CHARSET_INFO *cs= system_charset_info; int quick_type; - DBUG_ENTER("select_describe"); + 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(this->optimized == 2); /* Don't log this into the slow query log */ - thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED); - join->unit->offset_limit_cnt= 0; + + 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 @@ -20398,10 +20417,11 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, item_list.push_back(new Item_string(message,strlen(message),cs)); if (result->send_data(item_list)) - join->error= 1; + error= 1; } else if (join->select_lex == join->unit->fake_select_lex) { + join->select_lex->set_explain_type(); //psergey /* here we assume that the query will return at least two rows, so we show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong @@ -20468,12 +20488,13 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, item_list.push_back(new Item_string("", 0, cs)); if (result->send_data(item_list)) - join->error= 1; + error= 1; } else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { table_map used_tables=0; + join->select_lex->set_explain_type(); //psergey bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; @@ -20965,9 +20986,23 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, // For next iteration used_tables|=table->map; if (result->send_data(item_list)) - join->error= 1; + error= 1; } } + DBUG_RETURN(error); +} + + +static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, + bool distinct,const char *message) +{ + THD *thd=join->thd; + select_result *result=join->result; + DBUG_ENTER("select_describe"); + join->error= join->print_explain(result, FALSE, /* Not on-the-fly */ + need_tmp_table, need_order, distinct, + message); + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; unit= unit->next_unit()) @@ -21010,7 +21045,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - sl->set_explain_type(); + sl->set_explain_type(); //psergey-todo: maybe remove this from here? sl->options|= SELECT_DESCRIBE; } diff --git a/sql/sql_select.h b/sql/sql_select.h index bbf390aaf7e..e3b6b1f6cac 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -963,7 +963,7 @@ public: const char *zero_result_cause; ///< not 0 if exec must return zero result bool union_part; ///< this subselect is part of union - bool optimized; ///< flag to avoid double optimization in EXPLAIN + int optimized; ///< flag to avoid double optimization in EXPLAIN bool initialized; ///< flag to avoid double init_execution calls /* @@ -1074,6 +1074,7 @@ public: SELECT_LEX_UNIT *unit); bool prepare_stage2(); int optimize(); + int optimize_inner(); int reinit(); int init_execution(); void exec(); @@ -1167,6 +1168,10 @@ public: { return (unit->item && unit->item->is_in_predicate()); } + + int print_explain(select_result_sink *result, bool on_the_fly, + 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_show.cc b/sql/sql_show.cc index e4981701025..770e4610fd8 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2024,6 +2024,97 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) DBUG_VOID_RETURN; } + +/* + SHOW EXPLAIN FOR command handler + + @param thd Current thread's thd + @param thread_id Thread whose explain we need + + @notes + - Attempt to do "SHOW EXPLAIN FOR " will properly produce "target not + running EXPLAINable command". + - todo: check how all this can/will work when using thread pools +*/ + +void mysqld_show_explain(THD *thd, ulong thread_id) +{ + THD *tmp; + Protocol *protocol= thd->protocol; + List field_list; + DBUG_ENTER("mysqld_show_explain"); + + thd->make_explain_field_list(field_list); + if (protocol->send_fields(&field_list, Protocol::SEND_NUM_ROWS | + Protocol::SEND_EOF)) + DBUG_VOID_RETURN; + + /* + Find the thread we need EXPLAIN for. Thread search code was copied from + kill_one_thread() + */ + VOID(pthread_mutex_lock(&LOCK_thread_count)); // For unlink from list + I_List_iterator it(threads); + while ((tmp=it++)) + { + if (tmp->command == COM_DAEMON) + continue; + if (tmp->thread_id == thread_id) + { + pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete + break; + } + } + VOID(pthread_mutex_unlock(&LOCK_thread_count)); + + if (tmp) + { + bool bres; + /* + Ok we've found the thread of interest and it won't go away because + we're holding its LOCK_thd data. + Post it an EXPLAIN request. + todo: where to get timeout from? + */ + bool timed_out; + int timeout_sec= 30; + Show_explain_request explain_req; + select_result_explain_buffer *explain_buf; + + explain_buf= new select_result_explain_buffer; + explain_buf->thd=thd; + explain_buf->protocol= thd->protocol; + + explain_req.explain_buf= explain_buf; + explain_req.target_thd= tmp; + explain_req.request_thd= thd; + + bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data, + (void*)&explain_req, + timeout_sec, &timed_out); + if (bres) + { + /* TODO not enabled or time out */ + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), + "SHOW EXPLAIN", + "Target is not running EXPLAINable command"); + } + pthread_mutex_unlock(&tmp->LOCK_thd_data); + if (!bres) + { + explain_buf->flush_data(); + my_eof(thd); + } + } + else + { + my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id); + } + + DBUG_VOID_RETURN; +} + + int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond) { TABLE *table= tables->table; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index c2fea985aa1..879c502a25f 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -10852,6 +10852,12 @@ show_param: Lex->spname= $3; Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT; } + | describe_command FOR_SYM expr + { + Lex->sql_command= SQLCOM_SHOW_EXPLAIN; + Lex->value_list.empty(); + Lex->value_list.push_front($3); + } ; show_engine_param: From 84cb5de047e83ced6ac9490b9da630fe7dff3c4a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 24 Aug 2011 14:41:13 +0400 Subject: [PATCH 02/67] MWL#182: Explain running statements - Further progress with the code - Testcases. --- mysql-test/r/show_explain.result | 26 ++++++++++++++++++ mysql-test/t/show_explain.test | 46 ++++++++++++++++++++++++++++++++ sql/sql_class.cc | 9 ++++++- sql/sql_lex.cc | 21 +++++++++------ sql/sql_select.cc | 17 +++++++++--- sql/sql_select.h | 1 + 6 files changed, 107 insertions(+), 13 deletions(-) create mode 100644 mysql-test/r/show_explain.result create mode 100644 mysql-test/t/show_explain.test diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result new file mode 100644 index 00000000000..5aee9221d50 --- /dev/null +++ b/mysql-test/r/show_explain.result @@ -0,0 +1,26 @@ +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); +create table t1 (a int); +insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; +show explain for 2*1000*1000*1000; +ERROR HY000: Unknown thread id: 2000000000 +show explain for 3; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +show explain for 2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +select get_lock('optimizer_done', 10); +get_lock('optimizer_done', 10) +1 +select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer_done') +1); +select get_lock('optimizer_done', 100); +get_lock('optimizer_done', 100) +1 +show explain for 3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 1000 Using where +select release_lock('optimizer_done'); +release_lock('optimizer_done') +1 +kill query 3; +drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test new file mode 100644 index 00000000000..fda31392e46 --- /dev/null +++ b/mysql-test/t/show_explain.test @@ -0,0 +1,46 @@ +# +# Tests for SHOW EXPLAIN FOR functionality +# +--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); +create table t1 (a int); +insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; + +# +# Try killing a non-existent thread +# +--error ER_NO_SUCH_THREAD +show explain for 2*1000*1000*1000; + +# Setup two threads and their ids +let $thr1=`select connection_id()`; +connect (con1, localhost, root,,); +connection con1; +let $thr2=`select connection_id()`; +connection default; + +# SHOW EXPLAIN FOR +--error ER_ERROR_WHEN_EXECUTING_COMMAND +eval show explain for $thr2; + +# SHOW EXPLAIN FOR +--error ER_ERROR_WHEN_EXECUTING_COMMAND +eval show explain for $thr1; + +# SHOW EXPLAIN FOR +connection con1; +select get_lock('optimizer_done', 10); +send select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer_done') +1); +connection default; +select get_lock('optimizer_done', 100); +eval show explain for $thr2; +select release_lock('optimizer_done'); +eval kill query $thr2; + +#insert into t1 values ('one'),('two'),('three'); + +drop table t0,t1; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 12709e23a34..c9b99b5fd1f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2932,7 +2932,14 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup) DBUG_VOID_RETURN; } -// psergey + +/* + Produce EXPLAIN data. + + This function is APC-scheduled to be run in the context of the thread that + we're producing EXPLAIN for. +*/ + void Show_explain_request::get_explain_data(void *arg) { Show_explain_request *req= (Show_explain_request*)arg; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index ae9c11c47e2..454331a0859 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3625,19 +3625,24 @@ bool st_select_lex::save_prep_leaf_tables(THD *thd) int st_select_lex::print_explain(select_result_sink *output) { + int res; if (join && join->optimized == 2) { - //psergey-TODO: any? - return join->print_explain(output, TRUE, - FALSE, // need_tmp_table, - FALSE, // bool need_order, - FALSE, // bool distinct, - NULL); //const char *message + res= join->print_explain(output, TRUE, + FALSE, // need_tmp_table, + FALSE, // bool need_order, + FALSE, // bool distinct, + NULL); //const char *message } else { - DBUG_ASSERT(0); - /* produce "not yet optimized" line */ + /* Produce "not yet optimized" line */ + const char *msg="Not yet optimized"; + res= join->print_explain(output, TRUE, + FALSE, // need_tmp_table, + FALSE, // bool need_order, + FALSE, // bool distinct, + msg); //const char *message } return 0; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 74d2bc7c356..c3d1543b363 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2019,6 +2019,17 @@ JOIN::save_join_tab() } +void JOIN::exec() +{ + /* + Enable SHOW EXPLAIN only if we're in the top-level query. + */ + thd->apc_target.enable(); + exec_inner(); + thd->apc_target.disable(); +} + + /** Exec select. @@ -2030,8 +2041,8 @@ JOIN::save_join_tab() @todo When can we have here thd->net.report_error not zero? */ -void -JOIN::exec() + +void JOIN::exec_inner() { List *columns_list= &fields_list; int tmp_error; @@ -2894,9 +2905,7 @@ mysql_select(THD *thd, Item ***rref_pointer_array, if (thd->is_error()) goto err; - thd->apc_target.enable(); join->exec(); - thd->apc_target.disable(); if (thd->cursor && thd->cursor->is_open()) { diff --git a/sql/sql_select.h b/sql/sql_select.h index e3b6b1f6cac..f329f51707f 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1078,6 +1078,7 @@ public: int reinit(); int init_execution(); void exec(); + void exec_inner(); int destroy(); void restore_tmp(); bool alloc_func_list(); From 0a08933036f0d3c499b7110ae2883fcc1796414f Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 25 Aug 2011 12:15:29 +0400 Subject: [PATCH 03/67] MWL#182: Explain running statements - Added TODO comments --- mysql-test/t/show_explain.test | 5 +++++ sql/my_apc.cc | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index fda31392e46..e8f4a646a8a 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -43,4 +43,9 @@ eval kill query $thr2; #insert into t1 values ('one'),('two'),('three'); +## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select +## +## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a +## thread and served together. + drop table t0,t1; diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 3842947f3bb..754b7880c42 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -97,7 +97,9 @@ void Apc_target::dequeue_request(Call_request *qe) /* Make an apc call in another thread. The caller is responsible so that we're not calling to ourselves. - + + psergey-todo: Should waits here be KILLable? (it seems one needs + to use thd->enter_cond() calls to be killable) */ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, From d3052e225a511cd28a41eb3444f36d2789e5dd6c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 25 Aug 2011 13:04:09 +0400 Subject: [PATCH 04/67] Fix windows build: add my_apc.{h,cc} to CMakeLists.txt files --- libmysqld/CMakeLists.txt | 1 + sql/CMakeLists.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 7c5b6bd5917..cf72cedb644 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -146,6 +146,7 @@ SET(LIBMYSQLD_SOURCES libmysqld.c emb_qcache.cc lib_sql.cc ../sql/create_options.cc ../sql/rpl_utility.cc ../sql/rpl_reporting.cc ../sql/sql_expression_cache.cc + ../sql/my_apc.cc ../sql/my_apc.h ${GEN_SOURCES} ${LIB_SOURCES}) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 8d2aeca17db..597ca7d874b 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -83,6 +83,7 @@ SET (SQL_SOURCE opt_index_cond_pushdown.cc create_options.cc sql_expression_cache.cc + my_apc.cc my_apc.h ${CMAKE_BINARY_DIR}/sql/sql_yacc.cc ${CMAKE_BINARY_DIR}/sql/sql_yacc.h ${CMAKE_BINARY_DIR}/include/mysqld_error.h From f4dd6831f5863ea5f239ca24c5b3c03bd2575c4a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 26 Aug 2011 16:04:30 +0400 Subject: [PATCH 05/67] Fix for previous csets: let set_explain_type() produce correct types for "UNION RESULT" selects --- sql/sql_lex.cc | 2 +- sql/sql_select.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 454331a0859..82b171789bc 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3520,7 +3520,7 @@ void st_select_lex::set_explain_type() ((is_uncacheable & UNCACHEABLE_DEPENDENT) ? "DEPENDENT UNION": is_uncacheable ? "UNCACHEABLE UNION": - "UNION"))); + (this == master_unit()->fake_select_lex)? "UNION RESULT" : "UNION"))); options|= SELECT_DESCRIBE; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c3d1543b363..55ca42f797b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -20503,7 +20503,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, join->select_lex->master_unit()->derived->is_materialized_derived()) { table_map used_tables=0; - join->select_lex->set_explain_type(); //psergey + join->select_lex->set_explain_type(); //psergey-todo: this adds SELECT_DESCRIBE to options! bad for on-the-fly bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; From d9045bce1ddf1f017f0e127606c809271c953ef0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 27 Aug 2011 09:47:21 +0400 Subject: [PATCH 06/67] -Make show_explain.test stable - Fix st_select_lex::set_explain_type() to allow producing exactly the same EXPLAINs as it did before. SHOW EXPLAIN output may produce select_type=SIMPLE instead or select_type=PRIMARY or vice versa (which is ok because values of select_type weren't self-consistent in this regard to begin with) --- mysql-test/r/show_explain.result | 8 ++++---- mysql-test/t/show_explain.test | 12 ++++++++++++ sql/opt_subselect.cc | 5 ++++- sql/sql_lex.cc | 14 +++++++++++--- sql/sql_lex.h | 8 +++++++- sql/sql_select.cc | 10 +++++++--- 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 5aee9221d50..4ff89069c5c 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -5,9 +5,9 @@ create table t1 (a int); insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; show explain for 2*1000*1000*1000; ERROR HY000: Unknown thread id: 2000000000 -show explain for 3; +SHOW explain for thr2 ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command -show explain for 2; +SHOW explain for thr1 ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command select get_lock('optimizer_done', 10); get_lock('optimizer_done', 10) @@ -16,11 +16,11 @@ select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer select get_lock('optimizer_done', 100); get_lock('optimizer_done', 100) 1 -show explain for 3; +SHOW explain for thr2 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 1000 Using where select release_lock('optimizer_done'); release_lock('optimizer_done') 1 -kill query 3; +kill query thr2 drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index e8f4a646a8a..fc83613ae0e 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -24,12 +24,18 @@ let $thr2=`select connection_id()`; connection default; # SHOW EXPLAIN FOR +echo SHOW explain for thr2; +--disable_query_log --error ER_ERROR_WHEN_EXECUTING_COMMAND eval show explain for $thr2; +--enable_query_log # SHOW EXPLAIN FOR +echo SHOW explain for thr1; +--disable_query_log --error ER_ERROR_WHEN_EXECUTING_COMMAND eval show explain for $thr1; +--enable_query_log # SHOW EXPLAIN FOR connection con1; @@ -37,9 +43,15 @@ select get_lock('optimizer_done', 10); send select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer_done') +1); connection default; select get_lock('optimizer_done', 100); +echo SHOW explain for thr2; +--disable_query_log eval show explain for $thr2; +--enable_query_log select release_lock('optimizer_done'); +--disable_query_log eval kill query $thr2; +--enable_query_log +echo kill query thr2; #insert into t1 values ('one'),('two'),('three'); diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 6c5e177fe1d..f8b6f80eb14 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -1346,7 +1346,8 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) while ((ifm= li++)) parent_lex->ftfunc_list->push_front(ifm); } - + + parent_lex->have_merged_subqueries= TRUE; DBUG_RETURN(FALSE); } @@ -1458,6 +1459,8 @@ static bool convert_subq_to_jtbm(JOIN *parent_join, create_subquery_temptable_name(tbl_alias, hash_sj_engine->materialize_join-> select_lex->select_number); jtbm->alias= tbl_alias; + + parent_lex->have_merged_subqueries= TRUE; #if 0 /* Inject sj_on_expr into the parent's WHERE or ON */ if (emb_tbl_nest) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 82b171789bc..a9f07c337fa 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1640,7 +1640,8 @@ void st_select_lex::init_query() link_next= 0; lock_option= TL_READ_DEFAULT; is_prep_leaf_list_saved= FALSE; - + + have_merged_subqueries= FALSE; bzero((char*) expr_cache_may_be_used, sizeof(expr_cache_may_be_used)); } @@ -3119,7 +3120,7 @@ bool st_select_lex::optimize_unflattened_subqueries() if (options & SELECT_DESCRIBE) { /* Optimize the subquery in the context of EXPLAIN. */ - sl->set_explain_type(); + sl->set_explain_type(FALSE); sl->options|= SELECT_DESCRIBE; inner_join->select_options|= SELECT_DESCRIBE; } @@ -3482,7 +3483,7 @@ void SELECT_LEX::update_used_tables() Set the EXPLAIN type for this subquery. */ -void st_select_lex::set_explain_type() +void st_select_lex::set_explain_type(bool on_the_fly) { bool is_primary= FALSE; if (next_select()) @@ -3504,6 +3505,9 @@ void st_select_lex::set_explain_type() } } + if (on_the_fly && !is_primary && have_merged_subqueries) + is_primary= TRUE; + SELECT_LEX *first= master_unit()->first_select(); /* drop UNCACHEABLE_EXPLAIN, because it is for internal usage only */ uint8 is_uncacheable= (uncacheable & ~UNCACHEABLE_EXPLAIN); @@ -3521,6 +3525,10 @@ void st_select_lex::set_explain_type() "DEPENDENT UNION": is_uncacheable ? "UNCACHEABLE UNION": (this == master_unit()->fake_select_lex)? "UNION RESULT" : "UNION"))); + + if (this == master_unit()->fake_select_lex) + type= "UNION RESULT"; + options|= SELECT_DESCRIBE; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d5dc4dc3a1f..b5131ded136 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -646,6 +646,12 @@ public: those converted to jtbm nests. The list is emptied when conversion is done. */ List sj_subselects; + + /* + Needed to correctly generate 'PRIMARY' or 'SIMPLE' for select_type column + of EXPLAIN + */ + bool have_merged_subqueries; List leaf_tables; List leaf_tables_exec; @@ -888,7 +894,7 @@ public: */ bool optimize_unflattened_subqueries(); /* Set the EXPLAIN type for this subquery. */ - void set_explain_type(); + void set_explain_type(bool on_the_fly); bool handle_derived(struct st_lex *lex, uint phases); void append_table_to_list(TABLE_LIST *TABLE_LIST::*link, TABLE_LIST *table); bool get_free_table_map(table_map *map, uint *tablenr); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 55ca42f797b..2a895bb2798 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -20430,7 +20430,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, } else if (join->select_lex == join->unit->fake_select_lex) { - join->select_lex->set_explain_type(); //psergey + //if (!join->select_lex->type) + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); //psergey /* here we assume that the query will return at least two rows, so we show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong @@ -20503,7 +20505,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, join->select_lex->master_unit()->derived->is_materialized_derived()) { table_map used_tables=0; - join->select_lex->set_explain_type(); //psergey-todo: this adds SELECT_DESCRIBE to options! bad for on-the-fly + //if (!join->select_lex->type) + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); //psergey-todo: this adds SELECT_DESCRIBE to options! bad for on-the-fly bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; @@ -21054,7 +21058,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - sl->set_explain_type(); //psergey-todo: maybe remove this from here? + sl->set_explain_type(FALSE); //psergey-todo: maybe remove this from here? sl->options|= SELECT_DESCRIBE; } From 203bbfe5693a95e01c84d1e4b788b76645df2d11 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 24 Sep 2011 21:56:42 +0400 Subject: [PATCH 07/67] MWL#182: Explain running statements - Implement new approach to testing (the DBUG_EXECUTE_IF variant) - add an 'evalp' mysqltest command that is like 'eval' except that it prints the original query. - Fix select_describe() not to change join_tab[i]->type - More tests --- client/mysqltest.cc | 25 ++++++++++-- mysql-test/r/show_explain.result | 43 +++++++++++++-------- mysql-test/t/show_explain.test | 65 +++++++++++++++++++++----------- sql/my_apc.cc | 8 ++++ sql/my_apc.h | 6 ++- sql/mysql_priv.h | 4 ++ sql/sql_select.cc | 43 +++++++++++++++------ 7 files changed, 141 insertions(+), 53 deletions(-) diff --git a/client/mysqltest.cc b/client/mysqltest.cc index a3689a7b757..d33db945aee 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -75,6 +75,8 @@ #define QUERY_SEND_FLAG 1 #define QUERY_REAP_FLAG 2 +#define QUERY_PRINT_ORIGINAL_FLAG 4 + #ifndef HAVE_SETENV static int setenv(const char *name, const char *value, int overwrite); #endif @@ -288,7 +290,8 @@ enum enum_commands { Q_ERROR, Q_SEND, Q_REAP, Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN, - Q_PING, Q_EVAL, + Q_PING, Q_EVAL, + Q_EVALP, Q_RPL_PROBE, Q_ENABLE_RPL_PARSE, Q_DISABLE_RPL_PARSE, Q_EVAL_RESULT, Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG, @@ -353,6 +356,7 @@ const char *command_names[]= "replace_column", "ping", "eval", + "evalp", "rpl_probe", "enable_rpl_parse", "disable_rpl_parse", @@ -7532,7 +7536,8 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) /* Evaluate query if this is an eval command */ - if (command->type == Q_EVAL || command->type == Q_SEND_EVAL) + if (command->type == Q_EVAL || command->type == Q_SEND_EVAL || + command->type == Q_EVALP) { init_dynamic_string(&eval_query, "", command->query_len+256, 1024); do_eval(&eval_query, command->query, command->end, FALSE); @@ -7564,10 +7569,20 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags) */ if (!disable_query_log && (flags & QUERY_SEND_FLAG)) { - replace_dynstr_append_mem(ds, query, query_len); + char *print_query= query; + int print_len= query_len; + if (flags & QUERY_PRINT_ORIGINAL_FLAG) + { + print_query= command->query; + print_len= command->end - command->query; + } + replace_dynstr_append_mem(ds, print_query, print_len); dynstr_append_mem(ds, delimiter, delimiter_length); dynstr_append_mem(ds, "\n", 1); } + + /* We're done with this flag */ + flags &= ~QUERY_PRINT_ORIGINAL_FLAG; /* Write the command to the result file before we execute the query @@ -8420,6 +8435,7 @@ int main(int argc, char **argv) case Q_EVAL_RESULT: die("'eval_result' command is deprecated"); case Q_EVAL: + case Q_EVALP: case Q_QUERY_VERTICAL: case Q_QUERY_HORIZONTAL: if (command->query == command->query_buf) @@ -8447,6 +8463,9 @@ int main(int argc, char **argv) flags= QUERY_REAP_FLAG; } + if (command->type == Q_EVALP) + flags |= QUERY_PRINT_ORIGINAL_FLAG; + /* Check for special property for this query */ display_result_vertically|= (command->type == Q_QUERY_VERTICAL); diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 4ff89069c5c..6a142e676fa 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -3,24 +3,37 @@ create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); create table t1 (a int); insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; +alter table t1 add b int, add c int, add filler char(32); +update t1 set b=a, c=a, filler='fooo'; +alter table t1 add key(a), add key(b); show explain for 2*1000*1000*1000; ERROR HY000: Unknown thread id: 2000000000 -SHOW explain for thr2 +show explain for (select max(a) from t0); +ERROR 42000: This version of MySQL doesn't yet support 'Usage of subqueries or stored function calls as part of this statement' +show explain for $thr2; ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command -SHOW explain for thr1 +show explain for $thr1; ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command -select get_lock('optimizer_done', 10); -get_lock('optimizer_done', 10) -1 -select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer_done') +1); -select get_lock('optimizer_done', 100); -get_lock('optimizer_done', 100) -1 -SHOW explain for thr2 +set debug='d,show_explain_probe_1'; +select count(*) from t1 where a < 100000; +show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ALL NULL NULL NULL NULL 1000 Using where -select release_lock('optimizer_done'); -release_lock('optimizer_done') -1 -kill query thr2 +1 SIMPLE t1 index a a 5 NULL 1000 Using where; Using index +count(*) +1000 +set debug='d,show_explain_probe_1'; +select max(c) from t1 where a < 10; +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 10 Using where +max(c) +9 +set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; +set debug='d,show_explain_probe_1'; +explain select max(c) from t1 where a < 10; +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 10 Using index condition; Rowid-ordered scan +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index fc83613ae0e..1ee66e3ca2c 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -9,6 +9,9 @@ create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); create table t1 (a int); insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; +alter table t1 add b int, add c int, add filler char(32); +update t1 set b=a, c=a, filler='fooo'; +alter table t1 add key(a), add key(b); # # Try killing a non-existent thread @@ -16,7 +19,12 @@ insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; --error ER_NO_SUCH_THREAD show explain for 2*1000*1000*1000; +--error ER_NOT_SUPPORTED_YET +show explain for (select max(a) from t0); + +# # Setup two threads and their ids +# let $thr1=`select connection_id()`; connect (con1, localhost, root,,); connection con1; @@ -24,36 +32,47 @@ let $thr2=`select connection_id()`; connection default; # SHOW EXPLAIN FOR -echo SHOW explain for thr2; ---disable_query_log --error ER_ERROR_WHEN_EXECUTING_COMMAND -eval show explain for $thr2; ---enable_query_log +evalp show explain for $thr2; # SHOW EXPLAIN FOR -echo SHOW explain for thr1; ---disable_query_log --error ER_ERROR_WHEN_EXECUTING_COMMAND -eval show explain for $thr1; ---enable_query_log +evalp show explain for $thr1; -# SHOW EXPLAIN FOR +let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; + +# +# Test SHOW EXPLAIN for simple queries +# connection con1; -select get_lock('optimizer_done', 10); -send select count(*) from t1 where a < 100000 and sleep(a*0 + release_lock('optimizer_done') +1); -connection default; -select get_lock('optimizer_done', 100); -echo SHOW explain for thr2; ---disable_query_log -eval show explain for $thr2; ---enable_query_log -select release_lock('optimizer_done'); ---disable_query_log -eval kill query $thr2; ---enable_query_log -echo kill query thr2; +set debug='d,show_explain_probe_1'; +send select count(*) from t1 where a < 100000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + + +set debug='d,show_explain_probe_1'; +send select max(c) from t1 where a < 10; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +# We can catch EXPLAIN, too. +set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; +set debug='d,show_explain_probe_1'; +send explain select max(c) from t1 where a < 10; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; -#insert into t1 values ('one'),('two'),('three'); ## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select ## diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 754b7880c42..91559483b1f 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -29,6 +29,10 @@ void Apc_target::init() // todo: should use my_pthread_... functions instead? DBUG_ASSERT(!enabled); (void)pthread_mutex_init(&LOCK_apc_queue, MY_MUTEX_INIT_SLOW); + +#ifndef DBUG_OFF + n_calls_processed= 0; +#endif } @@ -217,6 +221,10 @@ void Apc_target::process_apc_requests() request->func(request->func_arg); request->what="func called by process_apc_requests"; +#ifndef DBUG_OFF + n_calls_processed++; +#endif + pthread_cond_signal(&request->COND_request); pthread_mutex_unlock(&request->LOCK_request); diff --git a/sql/my_apc.h b/sql/my_apc.h index 3783bd28f54..3906aa24408 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -62,13 +62,17 @@ public: bool make_apc_call(apc_func_t func, void *func_arg, int timeout_sec, bool *timed_out); +#ifndef DBUG_OFF + int n_calls_processed; + //int call_queue_size; +#endif private: class Call_request; int enabled; Call_request *apc_calls; pthread_mutex_t LOCK_apc_queue; - //int call_queue_size; + class Call_request { diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index a19cca31d1c..e20702b497d 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -2404,6 +2404,10 @@ int rea_create_table(THD *thd, const char *path, int format_number(uint inputflag,uint max_length,char * pos,uint length, char * *errpos); +#ifndef DBUG_OFF +void dbug_serve_apcs(THD *thd, int n_calls); +#endif + /* table.cc */ TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key, uint key_length); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 2a895bb2798..355b33fa353 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -245,6 +245,25 @@ Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field, JOIN_TAB *first_depth_first_tab(JOIN* join); JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab); +#ifndef DBUG_OFF +// psergey: +void dbug_serve_apcs(THD *thd, int n_calls) +{ + // TODO how do we signal that we're SHOW-EXPLAIN-READY? + const char *save_proc_info= thd->proc_info; + thd_proc_info(thd, "show_explain_trap"); + + int n_apcs= thd->apc_target.n_calls_processed + n_calls; + while (thd->apc_target.n_calls_processed < n_apcs) + { + my_sleep(300); + if (thd->check_killed()) + break; + } + thd_proc_info(thd, save_proc_info); +} +#endif + /** This handles SELECT with and without UNION. */ @@ -2047,6 +2066,8 @@ void JOIN::exec_inner() List *columns_list= &fields_list; int tmp_error; DBUG_ENTER("JOIN::exec"); + + DBUG_EXECUTE_IF("show_explain_probe_1", dbug_serve_apcs(thd, 1);); thd_proc_info(thd, "executing"); error= 0; @@ -20430,7 +20451,6 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, } else if (join->select_lex == join->unit->fake_select_lex) { - //if (!join->select_lex->type) if (on_the_fly) join->select_lex->set_explain_type(on_the_fly); //psergey /* @@ -20561,6 +20581,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, 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) { @@ -20569,9 +20590,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, (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; + 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; + tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; } /* table */ @@ -20620,8 +20641,8 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, #endif } /* "type" column */ - item_list.push_back(new Item_string(join_type_str[tab->type], - strlen(join_type_str[tab->type]), + 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()) @@ -20645,7 +20666,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, item_list.push_back(item_null); /* Build "key", "key_len", and "ref" values and add them to item_list */ - if (tab->type == JT_NEXT) + if (tab_type == JT_NEXT) { key_info= table->key_info+tab->index; key_len= key_info->key_length; @@ -20674,12 +20695,12 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, } } } - if (is_hj && tab->type != JT_HASH) + if (is_hj && tab_type != JT_HASH) { tmp2.append(':'); tmp3.append(':'); } - if (tab->type == JT_HASH_NEXT) + if (tab_type == JT_HASH_NEXT) { register uint length; key_info= table->key_info+tab->index; @@ -20701,7 +20722,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, 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) + 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); @@ -20754,7 +20775,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, ha_rows examined_rows; if (tab->select && tab->select->quick) examined_rows= tab->select->quick->records; - else if (tab->type == JT_NEXT || tab->type == JT_ALL || is_hj) + else if (tab_type == JT_NEXT || tab_type == JT_ALL || is_hj) { if (tab->limit) examined_rows= tab->limit; @@ -20793,7 +20814,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, /* Build "Extra" field and add it to item_list. */ key_read=table->key_read; - if ((tab->type == JT_NEXT || tab->type == JT_CONST) && + 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 && From 27f760143c839d4ac8ecd4fd2764728e492e921c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sun, 25 Sep 2011 13:05:58 +0400 Subject: [PATCH 08/67] - Testing: add DBUG_EXECUTE_IF("show_explain_probe_2"... which fires only for selects with given select_id. - Steps towards making SHOW EXPLAIN work for UNIONs. --- mysql-test/r/show_explain.result | 3 +-- mysql-test/t/show_explain.test | 7 ++++-- sql/item_func.cc | 2 +- sql/sql_class.h | 2 ++ sql/sql_lex.cc | 3 ++- sql/sql_select.cc | 38 +++++++++++++++++++++++++++++--- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 6a142e676fa..874af6e720e 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -14,6 +14,7 @@ show explain for $thr2; ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command show explain for $thr1; ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +set @show_explain_probe_select_id=1; set debug='d,show_explain_probe_1'; select count(*) from t1 where a < 100000; show explain for $thr2; @@ -21,7 +22,6 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 index a a 5 NULL 1000 Using where; Using index count(*) 1000 -set debug='d,show_explain_probe_1'; select max(c) from t1 where a < 10; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -29,7 +29,6 @@ id select_type table type possible_keys key key_len ref rows Extra max(c) 9 set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; -set debug='d,show_explain_probe_1'; explain select max(c) from t1 where a < 10; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 1ee66e3ca2c..4ed625c5bca 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -1,6 +1,8 @@ # # Tests for SHOW EXPLAIN FOR functionality # +--source include/have_debug.inc + --disable_warnings drop table if exists t0, t1; --enable_warnings @@ -45,6 +47,7 @@ let $wait_condition= select State='show_explain_trap' from information_schema.pr # Test SHOW EXPLAIN for simple queries # connection con1; +set @show_explain_probe_select_id=1; set debug='d,show_explain_probe_1'; send select count(*) from t1 where a < 100000; @@ -55,7 +58,6 @@ connection con1; reap; -set debug='d,show_explain_probe_1'; send select max(c) from t1 where a < 10; connection default; --source include/wait_condition.inc @@ -65,7 +67,6 @@ reap; # We can catch EXPLAIN, too. set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; -set debug='d,show_explain_probe_1'; send explain select max(c) from t1 where a < 10; connection default; --source include/wait_condition.inc @@ -73,6 +74,8 @@ evalp show explain for $thr2; connection con1; reap; +# Let's try with a subquery + ## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select ## diff --git a/sql/item_func.cc b/sql/item_func.cc index 033537092d8..2bca34f0a76 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -3871,7 +3871,7 @@ longlong Item_func_sleep::val_int() #define extra_size sizeof(double) -static user_var_entry *get_variable(HASH *hash, LEX_STRING &name, +user_var_entry *get_variable(HASH *hash, LEX_STRING &name, bool create_if_not_exists) { user_var_entry *entry; diff --git a/sql/sql_class.h b/sql/sql_class.h index 5d55d7182fc..cec37de6a61 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3474,6 +3474,8 @@ class user_var_entry DTCollation collation; }; +user_var_entry *get_variable(HASH *hash, LEX_STRING &name, + bool create_if_not_exists); /* Unique -- class for unique (removing of duplicates). diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index a9f07c337fa..3d2f7013a35 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3529,7 +3529,8 @@ void st_select_lex::set_explain_type(bool on_the_fly) if (this == master_unit()->fake_select_lex) type= "UNION RESULT"; - options|= SELECT_DESCRIBE; + if (!on_the_fly) + options|= SELECT_DESCRIBE; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 355b33fa353..1d2c8a10b75 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -262,8 +262,34 @@ void dbug_serve_apcs(THD *thd, int n_calls) } thd_proc_info(thd, save_proc_info); } + + +/* + Usage + + DBUG_EXECUTE_IF("show_explain_probe_2", + if (dbug_user_var_equals_int(thd, "select_id", select_id)) + dbug_serve_apcs(thd, 1); + ); + +*/ + +bool dbug_user_var_equals_int(THD *thd, const char *name, int value) +{ + user_var_entry *var; + LEX_STRING varname= {(char*)name, strlen(name)}; + if ((var= get_variable(&thd->user_vars, varname, FALSE))) + { + bool null_value; + longlong var_value= var->val_int(&null_value); + if (!null_value && var_value == value) + return TRUE; + } + return FALSE; +} #endif + /** This handles SELECT with and without UNION. */ @@ -2067,7 +2093,13 @@ void JOIN::exec_inner() int tmp_error; DBUG_ENTER("JOIN::exec"); - DBUG_EXECUTE_IF("show_explain_probe_1", dbug_serve_apcs(thd, 1);); + DBUG_EXECUTE_IF("show_explain_probe_2", dbug_serve_apcs(thd, 1);); + DBUG_EXECUTE_IF("show_explain_probe_1", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); thd_proc_info(thd, "executing"); error= 0; @@ -20397,8 +20429,8 @@ void JOIN::clear() /** EXPLAIN handling. - Send a description about what how the select will be done to stdout. - + 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 */ From 2bf31b094118059b6a488c7f1f8f97dfe3d829a3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 26 Sep 2011 18:24:49 +0400 Subject: [PATCH 09/67] Don't run show_explain.test for embedded server (the patch explains why) --- mysql-test/t/show_explain.test | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 4ed625c5bca..dc54ebb9c52 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -7,6 +7,28 @@ drop table if exists t0, t1; --enable_warnings +# +# Testcases in this file do not work with embedded server. The reason for this +# is that we use the following commands for synchronization: +# +# set @show_explain_probe_select_id=1; +# set debug='d,show_explain_probe_1'; +# send select count(*) from t1 where a < 100000; +# +# When ran with mysqltest_embedded, this translates into: +# +# Thread1> DBUG_PUSH("d,show_explain_probe_1"); +# Thread1> create another thread for doing "send ... reap" +# Thread2> mysql_parse("select count(*) from t1 where a < 100000"); +# +# That is, "select count(*) ..." is ran in a thread for which DBUG_PUSH(...) +# has not been called. As a result, show_explain_probe_1 does not fire, and +# "select count(*) ..." does not wait till its SHOW EXPLAIN command, and the +# test fails. +# +-- source include/not_embedded.inc + + create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); create table t1 (a int); From b7a340eeb0acceb23fbc9cb5684200aaff790f98 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Oct 2011 01:12:02 +0400 Subject: [PATCH 10/67] Fix SHOW EXPLAIN for UNIONs. --- sql/sql_lex.cc | 5 ++ sql/sql_select.cc | 148 +++++++++++++++++++++++++--------------------- sql/sql_select.h | 3 + 3 files changed, 88 insertions(+), 68 deletions(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 3d2f7013a35..f42b9d9ee0e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3667,6 +3667,11 @@ int st_select_lex_unit::print_explain(select_result_sink *output) if ((res= sl->print_explain(output))) break; } + if (!fake_select_lex->join) + { + res= print_fake_select_lex_join(output, TRUE /* on the fly */, + fake_select_lex, 0 /* flags */); + } return res; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 1d2c8a10b75..ee06ce0c8a6 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -20425,6 +20425,83 @@ void JOIN::clear() } } +int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, + SELECT_LEX *select_lex, uint8 select_options) +{ + 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); //psergey + /* + here we assume that the query will return at least two rows, so we + show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong + and no filesort will be actually done, but executing all selects in + the UNION to provide precise EXPLAIN information will hardly be + appreciated :) + */ + char table_name_buffer[SAFE_NAME_LEN]; + item_list.empty(); + /* id */ + item_list.push_back(new Item_null); + /* select_type */ + item_list.push_back(new Item_string(select_lex->type, + strlen(select_lex->type), + cs)); + /* table */ + { + SELECT_LEX *sl= select_lex->master_unit()->first_select(); + uint len= 6, lastop= 0; + memcpy(table_name_buffer, STRING_WITH_LEN("next_select()) + { + len+= lastop; + lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, + "%u,", sl->select_number); + } + if (sl || len + lastop >= NAME_LEN) + { + memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); + len+= 4; + } + else + { + len+= lastop; + table_name_buffer[len - 1]= '>'; // change ',' to '>' + } + item_list.push_back(new Item_string(table_name_buffer, len, cs)); + } + /* partitions */ + if (/*join->thd->lex->describe*/ select_options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + /* type */ + item_list.push_back(new Item_string(join_type_str[JT_ALL], + strlen(join_type_str[JT_ALL]), + cs)); + /* possible_keys */ + 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); + /* in_rows */ + if (select_options & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + /* rows */ + item_list.push_back(item_null); + /* extra */ + if (select_lex->master_unit()->global_parameters->order_list.first) + item_list.push_back(new Item_string("Using filesort", + 14, cs)); + else + item_list.push_back(new Item_string("", 0, cs)); + + if (result->send_data(item_list)) + return 1; + return 0; +} /** EXPLAIN handling. @@ -20483,74 +20560,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, } else if (join->select_lex == join->unit->fake_select_lex) { - if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); //psergey - /* - here we assume that the query will return at least two rows, so we - show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong - and no filesort will be actually done, but executing all selects in - the UNION to provide precise EXPLAIN information will hardly be - appreciated :) - */ - char table_name_buffer[SAFE_NAME_LEN]; - item_list.empty(); - /* id */ - item_list.push_back(new Item_null); - /* select_type */ - item_list.push_back(new Item_string(join->select_lex->type, - strlen(join->select_lex->type), - cs)); - /* table */ - { - SELECT_LEX *sl= join->unit->first_select(); - uint len= 6, lastop= 0; - memcpy(table_name_buffer, STRING_WITH_LEN("next_select()) - { - len+= lastop; - lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, - "%u,", sl->select_number); - } - if (sl || len + lastop >= NAME_LEN) - { - memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); - len+= 4; - } - else - { - len+= lastop; - table_name_buffer[len - 1]= '>'; // change ',' to '>' - } - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } - /* partitions */ - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) - item_list.push_back(item_null); - /* type */ - item_list.push_back(new Item_string(join_type_str[JT_ALL], - strlen(join_type_str[JT_ALL]), - cs)); - /* possible_keys */ - 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); - /* in_rows */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); - /* extra */ - if (join->unit->global_parameters->order_list.first) - item_list.push_back(new Item_string("Using filesort", - 14, cs)); - else - item_list.push_back(new Item_string("", 0, cs)); - - if (result->send_data(item_list)) + if (print_fake_select_lex_join(result, on_the_fly, + join->select_lex, + join->thd->lex->describe)) error= 1; } else if (!join->select_lex->master_unit()->derived || diff --git a/sql/sql_select.h b/sql/sql_select.h index f329f51707f..d6379f3ea92 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1465,6 +1465,9 @@ inline bool optimizer_flag(THD *thd, uint flag) return (thd->variables.optimizer_switch & flag); } +int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, + SELECT_LEX *select_lex, uint8 select_options); + /* Table elimination entry point function */ void eliminate_tables(JOIN *join); From ba09d25abc1fd3dbe55f1d7974cfe6a1ebda4df0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 27 Oct 2011 21:34:41 +0400 Subject: [PATCH 11/67] Fix typo bug in UNION handling, add tests for SHOW EXPLAIN for UNION. --- mysql-test/r/show_explain.result | 29 +++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 26 +++++++++++++++++++++++++- sql/sql_lex.cc | 6 +++++- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 874af6e720e..9197cc353c6 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -28,6 +28,8 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 10 Using where max(c) 9 +# We can catch EXPLAIN, too. +set @show_expl_tmp= @@optimizer_switch; set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; explain select max(c) from t1 where a < 10; show explain for $thr2; @@ -35,4 +37,31 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan +set optimizer_switch= @show_expl_tmp; +# UNION, first branch +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +explain select a from t0 A union select a+1 from t0 B; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY A ALL NULL NULL NULL NULL 10 +2 UNION B ALL NULL NULL NULL NULL 10 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY A ALL NULL NULL NULL NULL 10 +2 UNION B ALL NULL NULL NULL NULL 10 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +# UNION, second branch +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +explain select a from t0 A union select a+1 from t0 B; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY A ALL NULL NULL NULL NULL 10 +2 UNION B ALL NULL NULL NULL NULL 10 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY A ALL NULL NULL NULL NULL 10 +2 UNION B ALL NULL NULL NULL NULL 10 +NULL UNION RESULT ALL NULL NULL NULL NULL NULL drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index dc54ebb9c52..77d79fc78b3 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -87,7 +87,9 @@ evalp show explain for $thr2; connection con1; reap; -# We can catch EXPLAIN, too. + +--echo # We can catch EXPLAIN, too. +set @show_expl_tmp= @@optimizer_switch; set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on'; send explain select max(c) from t1 where a < 10; connection default; @@ -95,6 +97,28 @@ connection default; evalp show explain for $thr2; connection con1; reap; +set optimizer_switch= @show_expl_tmp; + + +--echo # UNION, first branch +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +send explain select a from t0 A union select a+1 from t0 B; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +--echo # UNION, second branch +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +send explain select a from t0 A union select a+1 from t0 B; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; # Let's try with a subquery diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f42b9d9ee0e..917502f2b5b 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3667,7 +3667,11 @@ int st_select_lex_unit::print_explain(select_result_sink *output) if ((res= sl->print_explain(output))) break; } - if (!fake_select_lex->join) + + /* + Note: it could be that fake_select_lex->join == NULL still at this point + */ + if (fake_select_lex && !fake_select_lex->join) { res= print_fake_select_lex_join(output, TRUE /* on the fly */, fake_select_lex, 0 /* flags */); From ca020dfa9e8668ce52eaff92c157097bba671ec1 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 28 Oct 2011 02:30:02 +0400 Subject: [PATCH 12/67] MWL#182: Explain running statements - Get subqueries to work, part #1. --- mysql-test/r/show_explain.result | 61 ++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 69 +++++++++++++++++++++++++++++++- sql/sql_lex.cc | 17 ++++++++ sql/sql_select.cc | 11 ++++- 4 files changed, 155 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 9197cc353c6..b8b732f9607 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -64,4 +64,65 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY A ALL NULL NULL NULL NULL 10 2 UNION B ALL NULL NULL NULL NULL 10 NULL UNION RESULT ALL NULL NULL NULL NULL NULL +# Uncorrelated subquery, select +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +select a, (select max(a) from t0 B) from t0 A where a<1; +show explain for $thr2; +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 SUBQUERY B ALL NULL NULL NULL NULL 10 +a (select max(a) from t0 B) +0 9 +# Uncorrelated subquery, explain +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +explain select a, (select max(a) from t0 B) from t0 A where a<1; +show explain for $thr2; +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 SUBQUERY B ALL NULL NULL NULL NULL 10 +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 SUBQUERY B ALL NULL NULL NULL NULL 10 +# correlated subquery, select +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; +show explain for $thr2; +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 +a (select max(a) from t0 b where b.a+a.a<10) +0 9 +# correlated subquery, explain +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; +show explain for $thr2; +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 +a (select max(a) from t0 b where b.a+a.a<10) +0 9 +# correlated subquery, select, while inside the subquery +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; +show explain for $thr2; +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 +a (select max(a) from t0 b where b.a+a.a<10) +0 9 +# correlated subquery, explain, while inside the subquery +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; +show explain for $thr2; +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 +a (select max(a) from t0 b where b.a+a.a<10) +0 9 drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 77d79fc78b3..5ed78be1ea4 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -110,6 +110,7 @@ evalp show explain for $thr2; connection con1; reap; + --echo # UNION, second branch set @show_explain_probe_select_id=1; set debug='d,show_explain_probe_1'; @@ -120,7 +121,73 @@ evalp show explain for $thr2; connection con1; reap; -# Let's try with a subquery + +--echo # Uncorrelated subquery, select +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +send select a, (select max(a) from t0 B) from t0 A where a<1; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + + +--echo # Uncorrelated subquery, explain +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +send explain select a, (select max(a) from t0 B) from t0 A where a<1; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +--echo # correlated subquery, select +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +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 +evalp show explain for $thr2; +connection con1; +reap; + +--echo # correlated subquery, explain +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_1'; +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 +evalp show explain for $thr2; +connection con1; +reap; + +--echo # correlated subquery, select, while inside the subquery +set @show_explain_probe_select_id=2; # <--- +set debug='d,show_explain_probe_1'; +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 +evalp show explain for $thr2; +connection con1; +reap; + +--echo # correlated subquery, explain, while inside the subquery +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +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 +evalp show explain for $thr2; +connection con1; +reap; + + +# TODO: explain in the parent subuqery when the un-correlated child has been +# run (and have done irreversible cleanups) + +# TODO: hit JOIN::optimize for non-select commands: UPDATE/DELETE, SET. ## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 9aee5caeb64..3b355a312af 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3706,6 +3706,22 @@ int st_select_lex::print_explain(select_result_sink *output) FALSE, // bool need_order, FALSE, // 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->print_explain(output); + } + } } else { @@ -3717,6 +3733,7 @@ int st_select_lex::print_explain(select_result_sink *output) FALSE, // bool distinct, msg); //const char *message } +err: return 0; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4dc99cf006c..fcbd2615b9c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -265,7 +265,9 @@ void dbug_serve_apcs(THD *thd, int n_calls) /* - Usage + Debugging: check if @name=value, comparing as integer + + Intended usage: DBUG_EXECUTE_IF("show_explain_probe_2", if (dbug_user_var_equals_int(thd, "select_id", select_id)) @@ -2102,7 +2104,6 @@ void JOIN::exec_inner() int tmp_error; DBUG_ENTER("JOIN::exec"); - DBUG_EXECUTE_IF("show_explain_probe_2", dbug_serve_apcs(thd, 1);); DBUG_EXECUTE_IF("show_explain_probe_1", if (dbug_user_var_equals_int(thd, "show_explain_probe_select_id", @@ -20516,6 +20517,7 @@ void JOIN::clear() } } + int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, SELECT_LEX *select_lex, uint8 select_options) { @@ -20594,6 +20596,7 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, return 0; } + /** EXPLAIN handling. @@ -21162,6 +21165,10 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, } +/* + See st_select_lex::print_explain() for the SHOW EXPLAIN counterpart +*/ + static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, bool distinct,const char *message) { From ee40ee9cb8c9bc673ba345abe260c9da7a05e45a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 4 Jan 2012 04:57:01 +0400 Subject: [PATCH 13/67] Apply earlier patch: correct handling of subqueries that were not yet optimized or already executed and deleted. --- mysql-test/r/show_explain.result | 29 +++++++---- mysql-test/t/show_explain.test | 37 +++++++++----- sql/sql_lex.cc | 40 ++++++++++----- sql/sql_select.cc | 88 +++++++++++++++++++++++++++----- sql/sql_select.h | 5 +- 5 files changed, 150 insertions(+), 49 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index b8b732f9607..e40c3b77780 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -15,7 +15,7 @@ ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EX show explain for $thr1; ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select count(*) from t1 where a < 100000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -25,7 +25,7 @@ count(*) select max(c) from t1 where a < 10; 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 10 Using where +1 SIMPLE t1 range a a 5 NULL 10 Using index condition max(c) 9 # We can catch EXPLAIN, too. @@ -40,7 +40,7 @@ id select_type table type possible_keys key key_len ref rows Extra set optimizer_switch= @show_expl_tmp; # UNION, first branch set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; explain select a from t0 A union select a+1 from t0 B; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -53,7 +53,7 @@ id select_type table type possible_keys key key_len ref rows Extra NULL UNION RESULT ALL NULL NULL NULL NULL NULL # UNION, second branch set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; explain select a from t0 A union select a+1 from t0 B; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -66,7 +66,7 @@ id select_type table type possible_keys key key_len ref rows Extra NULL UNION RESULT ALL NULL NULL NULL NULL NULL # Uncorrelated subquery, select set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select a, (select max(a) from t0 B) from t0 A where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -76,7 +76,7 @@ a (select max(a) from t0 B) 0 9 # Uncorrelated subquery, explain set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; explain select a, (select max(a) from t0 B) from t0 A where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -87,7 +87,7 @@ id select_type table type possible_keys key key_len ref rows Extra 2 SUBQUERY B ALL NULL NULL NULL NULL 10 # correlated subquery, select set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -97,7 +97,7 @@ a (select max(a) from t0 b where b.a+a.a<10) 0 9 # correlated subquery, explain set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -107,7 +107,7 @@ a (select max(a) from t0 b where b.a+a.a<10) 0 9 # correlated subquery, select, while inside the subquery set @show_explain_probe_select_id=2; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -117,7 +117,7 @@ a (select max(a) from t0 b where b.a+a.a<10) 0 9 # correlated subquery, explain, while inside the subquery set @show_explain_probe_select_id=2; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -125,4 +125,13 @@ id select_type table type possible_keys key key_len ref rows Extra 2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where a (select max(a) from t0 b where b.a+a.a<10) 0 9 +# correlated subquery, explain, while inside the subquery +set @show_explain_probe_select_id=1; +set debug='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; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +a (select max(a) from t0 b where b.a+a.a<10) +0 9 drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 5ed78be1ea4..4f04447d039 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -12,17 +12,17 @@ drop table if exists t0, t1; # is that we use the following commands for synchronization: # # set @show_explain_probe_select_id=1; -# set debug='d,show_explain_probe_1'; +# set debug='d,show_explain_probe_join_exec_start'; # send select count(*) from t1 where a < 100000; # # When ran with mysqltest_embedded, this translates into: # -# Thread1> DBUG_PUSH("d,show_explain_probe_1"); +# Thread1> DBUG_PUSH("d,show_explain_probe_join_exec_start"); # Thread1> create another thread for doing "send ... reap" # Thread2> mysql_parse("select count(*) from t1 where a < 100000"); # # That is, "select count(*) ..." is ran in a thread for which DBUG_PUSH(...) -# has not been called. As a result, show_explain_probe_1 does not fire, and +# has not been called. As a result, show_explain_probe_join_exec_start does not fire, and # "select count(*) ..." does not wait till its SHOW EXPLAIN command, and the # test fails. # @@ -70,7 +70,7 @@ let $wait_condition= select State='show_explain_trap' from information_schema.pr # connection con1; set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; send select count(*) from t1 where a < 100000; connection default; @@ -102,7 +102,7 @@ set optimizer_switch= @show_expl_tmp; --echo # UNION, first branch set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; send explain select a from t0 A union select a+1 from t0 B; connection default; --source include/wait_condition.inc @@ -113,7 +113,7 @@ reap; --echo # UNION, second branch set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; send explain select a from t0 A union select a+1 from t0 B; connection default; --source include/wait_condition.inc @@ -124,7 +124,7 @@ reap; --echo # Uncorrelated subquery, select set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; send select a, (select max(a) from t0 B) from t0 A where a<1; connection default; --source include/wait_condition.inc @@ -135,7 +135,7 @@ reap; --echo # Uncorrelated subquery, explain set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; send explain select a, (select max(a) from t0 B) from t0 A where a<1; connection default; --source include/wait_condition.inc @@ -145,7 +145,7 @@ reap; --echo # correlated subquery, select set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; 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 @@ -155,7 +155,7 @@ reap; --echo # correlated subquery, explain set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; 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 @@ -165,7 +165,7 @@ reap; --echo # correlated subquery, select, while inside the subquery set @show_explain_probe_select_id=2; # <--- -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; 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 @@ -175,7 +175,7 @@ reap; --echo # correlated subquery, explain, while inside the subquery set @show_explain_probe_select_id=2; -set debug='d,show_explain_probe_1'; +set debug='d,show_explain_probe_join_exec_start'; 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 @@ -184,6 +184,16 @@ connection con1; reap; +--echo # correlated subquery, explain, while inside the subquery +set @show_explain_probe_select_id=1; +set debug='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 +evalp show explain for $thr2; +connection con1; +reap; + # TODO: explain in the parent subuqery when the un-correlated child has been # run (and have done irreversible cleanups) @@ -195,4 +205,7 @@ reap; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. +## TODO: SHOW EXPLAIN while the primary query is running EXPLAIN EXTENDED/PARTITIONS +## + drop table t0,t1; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index a1560953cd5..667d5a80c96 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3555,16 +3555,11 @@ void st_select_lex::set_explain_type(bool on_the_fly) using_materialization= TRUE; } - if (this == master_unit()->fake_select_lex) - type= "UNION RESULT"; - if (&master_unit()->thd->lex->select_lex == this) { type= is_primary ? "PRIMARY" : "SIMPLE"; } - - if (!on_the_fly) - // else + else { if (this == first) { @@ -3594,9 +3589,14 @@ void st_select_lex::set_explain_type(bool on_the_fly) else { type= is_uncacheable ? "UNCACHEABLE UNION": "UNION"; + if (this == master_unit()->fake_select_lex) + type= "UNION RESULT"; + } } } + + if (!on_the_fly) options|= SELECT_DESCRIBE; } @@ -3744,10 +3744,17 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) } +int print_explain_message_line(select_result_sink *result, + SELECT_LEX *select_lex, + bool on_the_fly, + uint8 options, + const char *message); + + int st_select_lex::print_explain(select_result_sink *output) { int res; - if (join && join->optimized == 2) + if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) { res= join->print_explain(output, TRUE, FALSE, // need_tmp_table, @@ -3773,13 +3780,18 @@ int st_select_lex::print_explain(select_result_sink *output) } else { - /* Produce "not yet optimized" line */ - const char *msg="Not yet optimized"; - res= join->print_explain(output, TRUE, - FALSE, // need_tmp_table, - FALSE, // bool need_order, - FALSE, // bool distinct, - msg); //const char *message + const char *msg; + if (!join) + DBUG_ASSERT(0); // psergey: TODO: can this happen or not? + 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"; + } + res= print_explain_message_line(output, this, TRUE /* on_the_fly */, + 0, msg); } err: return 0; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6bed898ce5e..4b6ee431f05 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -931,7 +931,8 @@ err: int JOIN::optimize() { int res= optimize_inner(); - optimized= 2; + if (!res) + have_query_plan= QEP_AVAILABLE; return res; } /** @@ -2104,7 +2105,22 @@ void JOIN::exec() Enable SHOW EXPLAIN only if we're in the top-level query. */ 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", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + exec_inner(); + + DBUG_EXECUTE_IF("show_explain_probe_join_exec_end", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + thd->apc_target.disable(); } @@ -2127,13 +2143,6 @@ void JOIN::exec_inner() int tmp_error; DBUG_ENTER("JOIN::exec"); - DBUG_EXECUTE_IF("show_explain_probe_1", - if (dbug_user_var_equals_int(thd, - "show_explain_probe_select_id", - select_lex->select_number)) - dbug_serve_apcs(thd, 1); - ); - thd_proc_info(thd, "executing"); error= 0; if (procedure) @@ -2421,9 +2430,17 @@ void JOIN::exec_inner() DBUG_PRINT("info",("Creating group table")); /* Free first data from old join */ + + fprintf(stderr,"Q: %s\n", thd->query()); + //DBUG_ASSERT(0); + /* + psergey-todo: this is the place of pre-mature JOIN::free call. + */ curr_join->join_free(); + //psergey-todo: SHOW EXPLAIN probe here if (curr_join->make_simple_join(this, curr_tmp_table)) DBUG_VOID_RETURN; + //psergey-todo: SHOW EXPLAIN probe here calc_group_buffer(curr_join, group_list); count_field_types(select_lex, &curr_join->tmp_table_param, curr_join->tmp_all_fields1, @@ -2544,7 +2561,9 @@ void JOIN::exec_inner() if (curr_tmp_table->distinct) curr_join->select_distinct=0; /* Each row is unique */ + //psergey-todo: SHOW EXPLAIN probe here curr_join->join_free(); /* Free quick selects */ + //psergey-todo: SHOW EXPLAIN probe here if (curr_join->select_distinct && ! curr_join->group_list) { thd_proc_info(thd, "Removing duplicates"); @@ -10113,7 +10132,8 @@ void JOIN::cleanup(bool full) { DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); - + + have_query_plan= QEP_DELETED; if (table) { JOIN_TAB *tab; @@ -20706,6 +20726,41 @@ 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, + 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)); + for (uint i=0 ; i < 7; i++) + item_list.push_back(item_null); + if (options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + if (options & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + item_list.push_back(new Item_string(message,strlen(message),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 select_options) { @@ -20810,7 +20865,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", (ulong)join->select_lex, join->select_lex->type, message ? message : "NULL")); - DBUG_ASSERT(this->optimized == 2); + DBUG_ASSERT(this->have_query_plan == QEP_AVAILABLE); /* Don't log this into the slow query log */ if (!on_the_fly) @@ -20825,7 +20880,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, */ if (message) { - item_list.push_back(new Item_int((int32) + // join->thd->lex->describe <- as options +#if 0 + item_list.push_bace(new Item_int((int32) join->select_lex->select_number)); item_list.push_back(new Item_string(join->select_lex->type, strlen(join->select_lex->type), cs)); @@ -20839,9 +20896,16 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, item_list.push_back(new Item_string(message,strlen(message),cs)); if (result->send_data(item_list)) error= 1; +#endif + //psergey-todo: is passing join->thd->lex->describe correct for SHOW EXPLAIN? + if (print_explain_message_line(result, join->select_lex, on_the_fly, + join->thd->lex->describe, message)) + error= 1; + } else if (join->select_lex == join->unit->fake_select_lex) { + //psergey-todo: is passing join->thd->lex->describe correct for SHOW EXPLAIN? if (print_fake_select_lex_join(result, on_the_fly, join->select_lex, join->thd->lex->describe)) @@ -20853,7 +20917,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, table_map used_tables=0; //if (!join->select_lex->type) if (on_the_fly) - join->select_lex->set_explain_type(on_the_fly); //psergey-todo: this adds SELECT_DESCRIBE to options! bad for 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; diff --git a/sql/sql_select.h b/sql/sql_select.h index ed5a551b1d8..5a60b3b865b 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1137,8 +1137,10 @@ public: const char *zero_result_cause; ///< not 0 if exec must return zero result bool union_part; ///< this subselect is part of union - int optimized; ///< flag to avoid double optimization in EXPLAIN + bool optimized; ///< flag to avoid double optimization in EXPLAIN bool initialized; ///< flag to avoid double init_execution calls + + enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan; /* Additional WHERE and HAVING predicates to be considered for IN=>EXISTS @@ -1221,6 +1223,7 @@ public: ref_pointer_array_size= 0; zero_result_cause= 0; optimized= 0; + have_query_plan= QEP_NOT_PRESENT_YET; initialized= 0; cond_equal= 0; having_equal= 0; From 404a1565bfe02e3ce06843f6c5bf243503a2fe99 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 4 Jan 2012 04:58:09 +0400 Subject: [PATCH 14/67] bzr ignore libmysqld/my_apc.cc --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index a956e0c6ab4..e4fe3e8a9f2 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1982,3 +1982,4 @@ plugin/handler_socket/perl-Net-HandlerSocket/HandlerSocket.bs plugin/handler_socket/perl-Net-HandlerSocket/Makefile.PL libmysqld/gcalc_slicescan.cc libmysqld/gcalc_tools.cc +libmysqld/my_apc.cc From ca8aa3901c5596d8d393f0f0ded5ea7292ed01c8 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 26 Apr 2012 06:40:36 +0530 Subject: [PATCH 15/67] MWL#182: Explain running statements - Code cleanup --- mysql-test/r/show_explain.result | 58 +++++++++++++++++++++++++- mysql-test/t/show_explain.test | 71 ++++++++++++++++++++++++++++++-- sql/my_apc.cc | 56 ++++++++++++++++++++----- sql/my_apc.h | 63 +++++++++++++++------------- sql/sql_class.cc | 3 +- sql/sql_class.h | 2 + sql/sql_lex.cc | 9 ++++ sql/sql_show.cc | 5 ++- 8 files changed, 220 insertions(+), 47 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 6742c6eacfe..07ccab1419e 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -1,4 +1,4 @@ -drop table if exists t0, t1; +drop table if exists t0, t1, t2; create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); create table t1 (a int); @@ -125,4 +125,60 @@ id select_type table type possible_keys key key_len ref rows Extra 2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where a (select max(a) from t0 b where b.a+a.a<10) 0 9 +# Try to do SHOW EXPLAIN for a query that runs a SET command: +# I've found experimentally that select_id==2 here... +# +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +set @foo= (select max(a) from t0 where sin(a) >0); +show explain for $thr2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +# +# Attempt SHOW EXPLAIN for an UPDATE +# +create table t2 as select a as a, a as dummy from t0 limit 2; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; +show explain for $thr2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +show explain for $thr2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +drop table t2; +# +# Attempt SHOW EXPLAIN for a DELETE +# +create table t2 as select a as a, a as dummy from t0 limit 2; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; +show explain for $thr2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +show explain for $thr2; +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +drop table t2; +# +# Multiple SHOW EXPLAIN calls for one select +# +create table t2 as select a as a, a as dummy from t0 limit 3; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +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 t0 ALL NULL NULL NULL NULL 10 Using where +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 t0 ALL NULL NULL NULL NULL 10 Using where +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 t0 ALL NULL NULL NULL NULL 10 Using where +a SUBQ +0 0 +1 0 +2 0 +drop table t2; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 5ed78be1ea4..717949d5cc5 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -4,7 +4,7 @@ --source include/have_debug.inc --disable_warnings -drop table if exists t0, t1; +drop table if exists t0, t1, t2; --enable_warnings # @@ -186,12 +186,75 @@ reap; # TODO: explain in the parent subuqery when the un-correlated child has been # run (and have done irreversible cleanups) +# ^^ Is this at all possible after 5.3? +# Maybe, for 5.3 try this: +# - run before/after the parent has invoked child's optimization +# - run after materialization -# TODO: hit JOIN::optimize for non-select commands: UPDATE/DELETE, SET. +--echo # Try to do SHOW EXPLAIN for a query that runs a SET command: +--echo # I've found experimentally that select_id==2 here... +--echo # +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +send set @foo= (select max(a) from t0 where sin(a) >0); +connection default; +--source include/wait_condition.inc +--error ER_ERROR_WHEN_EXECUTING_COMMAND +evalp show explain for $thr2; +connection con1; +reap; + +--echo # +--echo # Attempt SHOW EXPLAIN for an UPDATE +--echo # +create table t2 as select a as a, a as dummy from t0 limit 2; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +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_ERROR_WHEN_EXECUTING_COMMAND +evalp show explain for $thr2; +--error ER_ERROR_WHEN_EXECUTING_COMMAND +evalp show explain for $thr2; +connection con1; +reap; +drop table t2; + +--echo # +--echo # Attempt SHOW EXPLAIN for a DELETE +--echo # +create table t2 as select a as a, a as dummy from t0 limit 2; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +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_ERROR_WHEN_EXECUTING_COMMAND +evalp show explain for $thr2; +--error ER_ERROR_WHEN_EXECUTING_COMMAND +evalp show explain for $thr2; +connection con1; +reap; +drop table t2; -## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select -## +--echo # +--echo # Multiple SHOW EXPLAIN calls for one select +--echo # +create table t2 as select a as a, a as dummy from t0 limit 3; +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_1'; +send select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +evalp show explain for $thr2; +evalp show explain for $thr2; +connection con1; +reap; +drop table t2; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 91559483b1f..58290ece56b 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -24,6 +24,14 @@ */ +/* + Initialize the target. + + @note + Initialization must be done prior to enabling/disabling the target, or making + any call requests to it. + Initial state after initialization is 'disabled'. +*/ void Apc_target::init() { // todo: should use my_pthread_... functions instead? @@ -36,6 +44,9 @@ void Apc_target::init() } +/* + Destroy the target. The target must be disabled when this call is made. +*/ void Apc_target::destroy() { DBUG_ASSERT(!enabled); @@ -43,6 +54,9 @@ void Apc_target::destroy() } +/* + Enter ther state where the target is available for serving APC requests +*/ void Apc_target::enable() { pthread_mutex_lock(&LOCK_apc_queue); @@ -51,6 +65,13 @@ void Apc_target::enable() } +/* + Make the target unavailable for serving APC requests. + + @note + This call will serve all requests that were already enqueued +*/ + void Apc_target::disable() { bool process= FALSE; @@ -62,6 +83,9 @@ void Apc_target::disable() process_apc_requests(); } + +/* (internal) Put request into the request list */ + void Apc_target::enqueue_request(Call_request *qe) { //call_queue_size++; @@ -81,6 +105,13 @@ void Apc_target::enqueue_request(Call_request *qe) } } + +/* + (internal) Remove given request from the request queue. + + The request is not necessarily first in the queue. +*/ + void Apc_target::dequeue_request(Call_request *qe) { //call_queue_size--; @@ -99,8 +130,10 @@ void Apc_target::dequeue_request(Call_request *qe) /* - Make an apc call in another thread. The caller is responsible so - that we're not calling to ourselves. + Make an APC (Async Procedure Call) in another thread. + + The caller is responsible for making sure he's not calling an Apc_target + that is serviced by the same thread it is called from. psergey-todo: Should waits here be KILLable? (it seems one needs to use thd->enter_cond() calls to be killable) @@ -119,7 +152,7 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, Call_request apc_request; apc_request.func= func; apc_request.func_arg= func_arg; - apc_request.done= FALSE; + apc_request.processed= FALSE; (void)pthread_cond_init(&apc_request.COND_request, NULL); (void)pthread_mutex_init(&apc_request.LOCK_request, MY_MUTEX_INIT_SLOW); pthread_mutex_lock(&apc_request.LOCK_request); @@ -133,19 +166,19 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, int wait_res= 0; /* todo: how about processing other errors here? */ - while (!apc_request.done && (wait_res != ETIMEDOUT)) + while (!apc_request.processed && (wait_res != ETIMEDOUT)) { wait_res= pthread_cond_timedwait(&apc_request.COND_request, &apc_request.LOCK_request, &abstime); } - if (!apc_request.done) + if (!apc_request.processed) { - /* We timed out */ - apc_request.done= TRUE; + /* The wait has timed out. Remove the request from the queue */ + apc_request.processed= TRUE; *timed_out= TRUE; pthread_mutex_unlock(&apc_request.LOCK_request); - + //psergey-todo: "Whoa rare event" refers to this part, right? put a comment. pthread_mutex_lock(&LOCK_apc_queue); dequeue_request(&apc_request); pthread_mutex_unlock(&LOCK_apc_queue); @@ -171,7 +204,8 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, /* - Process all APC requests + Process all APC requests. + This should be called periodically by the APC target thread. */ void Apc_target::process_apc_requests() @@ -190,7 +224,7 @@ void Apc_target::process_apc_requests() request->what="seen by process_apc_requests"; pthread_mutex_lock(&request->LOCK_request); - if (request->done) + if (request->processed) { /* We can get here when @@ -214,7 +248,7 @@ void Apc_target::process_apc_requests() */ request->what="dequeued by process_apc_requests"; dequeue_request(request); - request->done= TRUE; + request->processed= TRUE; pthread_mutex_unlock(&LOCK_apc_queue); diff --git a/sql/my_apc.h b/sql/my_apc.h index 3906aa24408..0698703ad40 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -3,9 +3,18 @@ */ /* - Design - - Mutex-guarded request queue (it belongs to the target), which can be enabled/ - disabled (when empty). + Interface + ~~~~~~~~~ + ( + - This is an APC request queue + - We assume there is a particular owner thread which periodically calls + process_apc_requests() to serve the call requests. + - Other threads can post call requests, and block until they are exectued. + ) + + Implementation + ~~~~~~~~~~~~~~ + - The target has a mutex-guarded request queue. - After the request has been put into queue, the requestor waits for request to be satisfied. The worker satisifes the request and signals the @@ -21,31 +30,11 @@ public: Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {} ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);} - /* - Initialize the target. This must be called before anything else. Right - after initialization, the target is disabled. - */ void init(); - - /* - Destroy the target. The target must be disabled when this call is made. - */ void destroy(); - - /* - Enter into state where this target will be serving APC requests - */ void enable(); - - /* - Leave the state where we could serve APC requests (will serve all already - enqueued requests) - */ void disable(); - /* - This should be called periodically to serve observation requests. - */ void process_apc_requests(); typedef void (*apc_func_t)(void *arg); @@ -68,18 +57,32 @@ public: #endif private: class Call_request; + + /* + Non-zero value means we're enabled. It's an int, not bool, because one can + call enable() N times (and then needs to call disable() N times before the + target is really disabled) + */ int enabled; + /* + Circular, double-linked list of all enqueued call requests. + We use this structure, because we + - process requests sequentially + - a thread that has posted a request may time out (or be KILLed) and + cancel the request, which means we'll need to remove its request at + arbitrary point in time. + */ Call_request *apc_calls; - pthread_mutex_t LOCK_apc_queue; + pthread_mutex_t LOCK_apc_queue; class Call_request { public: - apc_func_t func; - void *func_arg; - bool done; + apc_func_t func; /* Function to call */ + void *func_arg; /* Argument to pass it */ + bool processed; pthread_mutex_t LOCK_request; pthread_cond_t COND_request; @@ -87,13 +90,15 @@ private: Call_request *next; Call_request *prev; - const char *what; + const char *what; /* State of the request */ }; void enqueue_request(Call_request *qe); void dequeue_request(Call_request *qe); + + /* return the first call request in queue, or NULL if there are none enqueued */ Call_request *get_first_in_queue() - { + { return apc_calls; } }; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d6f976cb505..545c9aff5e9 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3033,7 +3033,8 @@ void Show_explain_request::get_explain_data(void *arg) req->target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, &backup_arena); - req->target_thd->lex->unit.print_explain(req->explain_buf); + if (req->target_thd->lex->unit.print_explain(req->explain_buf)) + req->failed_to_produce= TRUE; req->target_thd->restore_active_arena((Query_arena*)req->request_thd, &backup_arena); diff --git a/sql/sql_class.h b/sql/sql_class.h index 85f5d35f3d2..de7c8de6c26 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1467,6 +1467,8 @@ public: THD *target_thd; THD *request_thd; + bool failed_to_produce; + select_result_explain_buffer *explain_buf; static void get_explain_data(void *arg); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 544246a1a4d..661ca2b4383 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3792,6 +3792,15 @@ int st_select_lex_unit::print_explain(select_result_sink *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. + */ + return 1; + } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { diff --git a/sql/sql_show.cc b/sql/sql_show.cc index c30a8aad1eb..dd1b749e8bd 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2112,16 +2112,19 @@ void mysqld_show_explain(THD *thd, ulong thread_id) explain_req.explain_buf= explain_buf; explain_req.target_thd= tmp; explain_req.request_thd= thd; + explain_req.failed_to_produce= FALSE; bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data, (void*)&explain_req, timeout_sec, &timed_out); - if (bres) + + if (bres || explain_req.failed_to_produce) { /* TODO not enabled or time out */ my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SHOW EXPLAIN", "Target is not running EXPLAINable command"); + bres= TRUE; } pthread_mutex_unlock(&tmp->LOCK_thd_data); if (!bres) From 9b269ea11b49c186a12f1a877c1d7cfa26b8b5c1 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 26 Apr 2012 07:17:34 +0530 Subject: [PATCH 16/67] MWL#182: Explain running statements - Correct thd->killed checks in join cache and filesort modules. --- sql/filesort.cc | 20 +++++++++++--------- sql/sql_join_cache.cc | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sql/filesort.cc b/sql/filesort.cc index 772698c6e1a..3b551796ae1 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -493,7 +493,7 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, my_off_t record; TABLE *sort_form; THD *thd= current_thd; - volatile killed_state *killed= &thd->killed; + //volatile killed_state *killed= &thd->killed; handler *file; MY_BITMAP *save_read_set, *save_write_set, *save_vcol_set; uchar *next_sort_key= sort_keys_buf; @@ -588,7 +588,7 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, break; } - if (*killed) + if (thd->check_killed()) { DBUG_PRINT("info",("Sort killed by user")); if (!indexfile && !quick_select) @@ -1229,18 +1229,20 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, void *first_cmp_arg; element_count dupl_count= 0; uchar *src; - killed_state not_killable; + /* killed_state not_killable; */ uchar *unique_buff= param->unique_buff; - volatile killed_state *killed= ¤t_thd->killed; + /* volatile killed_state *killed= ¤t_thd->killed; */ + const bool killable= !param->not_killable; + THD* const thd=current_thd; DBUG_ENTER("merge_buffers"); - status_var_increment(current_thd->status_var.filesort_merge_passes); - current_thd->query_plan_fsort_passes++; - if (param->not_killable) + status_var_increment(thd->status_var.filesort_merge_passes); + thd->query_plan_fsort_passes++; + /*if (param->not_killable) { killed= ¬_killable; not_killable= NOT_KILLED; - } + }*/ error=0; rec_length= param->rec_length; @@ -1317,7 +1319,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, while (queue.elements > 1) { - if (*killed) + if (killable && thd->check_killed()) { error= 1; goto err; /* purecov: inspected */ } diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc index d49be2e446c..6492fb6cd61 100644 --- a/sql/sql_join_cache.cc +++ b/sql/sql_join_cache.cc @@ -2235,7 +2235,7 @@ enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last) while (!(error= join_tab_scan->next())) { - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -2505,7 +2505,7 @@ enum_nested_loop_state JOIN_CACHE::join_null_complements(bool skip_last) for ( ; cnt; cnt--) { - if (join->thd->killed) + if (join->thd->check_killed()) { /* The user has aborted the execution of the query */ join->thd->send_kill_message(); @@ -3355,7 +3355,7 @@ int JOIN_TAB_SCAN::next() update_virtual_fields(thd, table); while (!err && select && (skip_rc= select->skip_record(thd)) <= 0) { - if (thd->killed || skip_rc < 0) + if (thd->check_killed() || skip_rc < 0) return 1; /* Move to the next record if the last retrieved record does not From ff40705f89b977d966427710e1b32a0774a2f0b3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 26 Apr 2012 08:48:31 +0530 Subject: [PATCH 17/67] Make SHOW EXPLAIN FOR produce a warning with the original text of query that the EXPLAIN is for. --- mysql-test/r/show_explain.result | 28 ++++++++++++++++++++++++++++ sql/sql_class.cc | 16 +++++++++++----- sql/sql_class.h | 2 ++ sql/sql_show.cc | 5 +++++ 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 07ccab1419e..e451ef3108b 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -20,12 +20,16 @@ select count(*) from t1 where a < 100000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 index a a 5 NULL 1000 Using where; Using index +Warnings: +Note 1003 select count(*) from t1 where a < 100000 count(*) 1000 select max(c) from t1 where a < 10; 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 10 Using index condition +Warnings: +Note 1003 select max(c) from t1 where a < 10 max(c) 9 # We can catch EXPLAIN, too. @@ -35,6 +39,8 @@ explain select max(c) from t1 where a < 10; 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 10 Using index condition; Rowid-ordered scan +Warnings: +Note 1003 explain select max(c) from t1 where a < 10 id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan set optimizer_switch= @show_expl_tmp; @@ -47,6 +53,8 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY A ALL NULL NULL NULL NULL 10 2 UNION B ALL NULL NULL NULL NULL 10 NULL UNION RESULT ALL NULL NULL NULL NULL NULL +Warnings: +Note 1003 explain select a from t0 A union select a+1 from t0 B id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY A ALL NULL NULL NULL NULL 10 2 UNION B ALL NULL NULL NULL NULL 10 @@ -60,6 +68,8 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY A ALL NULL NULL NULL NULL 10 2 UNION B ALL NULL NULL NULL NULL 10 NULL UNION RESULT ALL NULL NULL NULL NULL NULL +Warnings: +Note 1003 explain select a from t0 A union select a+1 from t0 B id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY A ALL NULL NULL NULL NULL 10 2 UNION B ALL NULL NULL NULL NULL 10 @@ -72,6 +82,8 @@ show explain for $thr2; 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 SUBQUERY B ALL NULL NULL NULL NULL 10 +Warnings: +Note 1003 select a, (select max(a) from t0 B) from t0 A where a<1 a (select max(a) from t0 B) 0 9 # Uncorrelated subquery, explain @@ -82,6 +94,8 @@ show explain for $thr2; 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 SUBQUERY B ALL NULL NULL NULL NULL 10 +Warnings: +Note 1003 explain select a, (select max(a) from t0 B) from t0 A where a<1 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 SUBQUERY B ALL NULL NULL NULL NULL 10 @@ -93,6 +107,8 @@ show explain for $thr2; 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 # correlated subquery, explain @@ -103,6 +119,8 @@ show explain for $thr2; 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 # correlated subquery, select, while inside the subquery @@ -113,6 +131,8 @@ show explain for $thr2; 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 # correlated subquery, explain, while inside the subquery @@ -123,6 +143,8 @@ show explain for $thr2; 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 # Try to do SHOW EXPLAIN for a query that runs a SET command: @@ -168,14 +190,20 @@ 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 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 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 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 0 0 1 0 diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 545c9aff5e9..4e9a0507bba 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3030,14 +3030,20 @@ void Show_explain_request::get_explain_data(void *arg) //TODO: change mem_root to point to request_thd->mem_root. // Actually, change the ARENA, because we're going to allocate items! Query_arena backup_arena; - req->target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, - &backup_arena); + THD *target_thd= req->target_thd; - if (req->target_thd->lex->unit.print_explain(req->explain_buf)) + target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, + &backup_arena); + + req->query_str.copy(target_thd->query(), + target_thd->query_length(), + &my_charset_bin); + + if (target_thd->lex->unit.print_explain(req->explain_buf)) req->failed_to_produce= TRUE; - req->target_thd->restore_active_arena((Query_arena*)req->request_thd, - &backup_arena); + target_thd->restore_active_arena((Query_arena*)req->request_thd, + &backup_arena); } diff --git a/sql/sql_class.h b/sql/sql_class.h index de7c8de6c26..223a42b1d02 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1471,6 +1471,8 @@ public: select_result_explain_buffer *explain_buf; + String query_str; + static void get_explain_data(void *arg); }; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index dd1b749e8bd..a0c39ff6c8c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2126,6 +2126,11 @@ void mysqld_show_explain(THD *thd, ulong thread_id) "Target is not running EXPLAINable command"); bres= TRUE; } + else + { + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_YES, explain_req.query_str.c_ptr_safe()); + } pthread_mutex_unlock(&tmp->LOCK_thd_data); if (!bres) { From cdc9a1172d7b75b16d92a6179478c2689ac04bae Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 10 May 2012 01:45:38 +0530 Subject: [PATCH 18/67] MWL#182: Explain running statements: Make SHOW EXPLAIN work for queries that do "Using temporary" and/or "Using filesort" - Patch#1: Don't lose "Using temporary/filesort" in the SHOW EXPLAIN output. --- mysql-test/r/show_explain.result | 75 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 46 ++++++++++++++++++++ sql/sql_lex.cc | 6 +-- 3 files changed, 124 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index e451ef3108b..74def21585f 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -209,4 +209,79 @@ a SUBQ 1 0 2 0 drop table t2; +# +# SHOW EXPLAIN for SELECT ... ORDER BY with "Using filesort" +# +explain select * from t0 order by a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using filesort +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +select * from t0 order by a; +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 10 Using filesort +Warnings: +Note 1003 select * from t0 order by a +a +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +# +# SHOW EXPLAIN for SELECT ... with "Using temporary" +# +explain select distinct a from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using temporary +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +select distinct a from t0; +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 10 Using temporary +Warnings: +Note 1003 select distinct a from t0 +a +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +# +# SHOW EXPLAIN for SELECT ... with "Using temporary; Using filesort" +# +explain select distinct a from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using temporary +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +select distinct a from t0; +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 10 Using temporary +Warnings: +Note 1003 select distinct a from t0 +a +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 717949d5cc5..4fc6aaad932 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -255,6 +255,52 @@ connection con1; reap; drop table t2; +--echo # +--echo # SHOW EXPLAIN for SELECT ... ORDER BY with "Using filesort" +--echo # +explain select * from t0 order by a; + +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +send select * from t0 order by a; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +--echo # +--echo # SHOW EXPLAIN for SELECT ... with "Using temporary" +--echo # +connection default; +explain select distinct a from t0; +connection con1; + +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +send select distinct a from t0; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +--echo # +--echo # SHOW EXPLAIN for SELECT ... with "Using temporary; Using filesort" +--echo # +connection default; +explain select distinct a from t0; +connection con1; + +set debug='d,show_explain_probe_1'; +set @show_explain_probe_select_id=1; +send select distinct a from t0; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 661ca2b4383..a47ba9b5024 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3752,9 +3752,9 @@ int st_select_lex::print_explain(select_result_sink *output) if (join && join->optimized == 2) { res= join->print_explain(output, TRUE, - FALSE, // need_tmp_table, - FALSE, // bool need_order, - FALSE, // bool distinct, + join->need_tmp, // need_tmp_table + (join->order != 0 && !join->skip_sort_order), // bool need_order + join->select_distinct, // bool distinct NULL); //const char *message if (res) goto err; From 58b9164f0468768eff64841f4f066840b9ff2206 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 10 May 2012 13:43:48 +0400 Subject: [PATCH 19/67] MDEV-238: SHOW EXPLAIN: Server crashes in JOIN::print_explain with FROM subquery and GROUP BY - Support SHOW EXPLAIN for selects that have "Using temporary; Using filesort". --- mysql-test/r/show_explain.result | 25 ++++++++++++++++++ mysql-test/t/show_explain.test | 22 ++++++++++++++++ sql/filesort.cc | 6 +++++ sql/sql_select.cc | 44 ++++++++++++++++++++++++-------- sql/sql_select.h | 14 ++++++++++ sql/table.h | 7 ++++- 6 files changed, 106 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 74def21585f..db1d57b7004 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -284,4 +284,29 @@ a 7 8 9 +set debug=''; +# +# MDEV-238: SHOW EXPLAIN: Server crashes in JOIN::print_explain with FROM subquery and GROUP BY +# +CREATE TABLE t2 ( a INT ); +INSERT INTO t2 VALUES (1),(2),(1),(4),(2); +explain SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; +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) +set debug='d,show_explain_in_find_all_keys'; +SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; +# NOTE: current code will not show "Using join buffer": +show explain for $thr2; +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 +Warnings: +Note 1003 SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a +a +1 +2 +4 +set debug=''; +DROP TABLE t2; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 4fc6aaad932..8c9d5f4c1e8 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -300,8 +300,30 @@ connection default; evalp show explain for $thr2; connection con1; reap; +set debug=''; + +--echo # +--echo # MDEV-238: SHOW EXPLAIN: Server crashes in JOIN::print_explain with FROM subquery and GROUP BY +--echo # +CREATE TABLE t2 ( a INT ); +INSERT INTO t2 VALUES (1),(2),(1),(4),(2); +explain SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; + +set debug='d,show_explain_in_find_all_keys'; +send SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; + +connection default; +--source include/wait_condition.inc +--echo # NOTE: current code will not show "Using join buffer": +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. +DROP TABLE t2; + drop table t0,t1; diff --git a/sql/filesort.cc b/sql/filesort.cc index 3b551796ae1..3e6588636fa 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -515,6 +515,12 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, if (indexfile || flag) ref_pos= &file->ref[0]; next_pos=ref_pos; + + DBUG_EXECUTE_IF("show_explain_in_find_all_keys", + dbug_serve_apcs(thd, 1); + ); + + if (! indexfile && ! quick_select) { next_pos=(uchar*) 0; /* Find records in sequence */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index d739c6789c3..6a842ead2a4 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -7132,28 +7132,36 @@ prev_record_reads(POSITION *positions, uint idx, table_map found_ref) return found; } +enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS}; /* Enumerate join tabs in breadth-first fashion, including const tables. */ -JOIN_TAB *first_breadth_first_tab(JOIN *join) +JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind) { - return join->join_tab; /* There's always one (i.e. first) table */ + /* There's always one (i.e. first) table */ + return (tabs_kind == WALK_EXECUTION_TABS)? join->join_tab: + join->table_access_tabs; } -JOIN_TAB *next_breadth_first_tab(JOIN *join, JOIN_TAB *tab) +JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, + JOIN_TAB *tab) { + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, tabs_kind); + const uint n_top_tabs_count= (tabs_kind == WALK_EXECUTION_TABS)? + join->top_join_tab_count: + join->top_table_access_tabs_count; if (!tab->bush_root_tab) { /* We're at top level. Get the next top-level tab */ tab++; - if (tab < join->join_tab + join->top_join_tab_count) + if (tab < first_top_tab + n_top_tabs_count) return tab; /* No more top-level tabs. Switch to enumerating SJM nest children */ - tab= join->join_tab; + tab= first_top_tab; } else { @@ -7177,7 +7185,7 @@ JOIN_TAB *next_breadth_first_tab(JOIN *join, JOIN_TAB *tab) Ok, "tab" points to a top-level table, and we need to find the next SJM nest and enter it. */ - for (; tab < join->join_tab + join->top_join_tab_count; tab++) + for (; tab < first_top_tab + n_top_tabs_count; tab++) { if (tab->bush_children) return tab->bush_children->start; @@ -7201,7 +7209,7 @@ JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables with_const JOIN_TAB *next_top_level_tab(JOIN *join, JOIN_TAB *tab) { - tab= next_breadth_first_tab(join, tab); + tab= next_breadth_first_tab(join, WALK_EXECUTION_TABS, tab); if (tab && tab->bush_root_tab) tab= NULL; return tab; @@ -7501,6 +7509,13 @@ get_best_combination(JOIN *join) join->top_join_tab_count= join->join_tab_ranges.head()->end - join->join_tab_ranges.head()->start; + /* + Save pointers to select join tabs for SHOW EXPLAIN + */ + join->table_access_tabs= join->join_tab; + join->top_table_access_tabs_count= join->top_join_tab_count; + + update_depend_map(join); DBUG_RETURN(0); } @@ -7922,6 +7937,7 @@ JOIN::make_simple_join(JOIN *parent, TABLE *temp_table) !(parent->join_tab_reexec= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB)))) DBUG_RETURN(TRUE); /* purecov: inspected */ + // psergey-todo: here, save the pointer for original join_tabs. join_tab= parent->join_tab_reexec; table= &parent->table_reexec[0]; parent->table_reexec[0]= temp_table; table_count= top_join_tab_count= 1; @@ -10077,7 +10093,12 @@ void JOIN_TAB::cleanup() if (cache) { cache->free(); - cache= 0; + cache= 0; // psergey: this is why we don't see "Using join cache" in SHOW EXPLAIN + // when it is run for "Using temporary+filesort" queries while they + // are at reading-from-tmp-table phase. + // + // TODO ask igor if this can be just moved to later phase + // (JOIN_CACHE objects themselves are not big, arent they) } limit= 0; if (table) @@ -21204,9 +21225,10 @@ int JOIN::print_explain(select_result_sink *result, bool 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); tab; - tab= next_breadth_first_tab(join, tab)) + 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) { @@ -21643,7 +21665,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, extra.append(STRING_WITH_LEN("; End temporary")); else if (tab->do_firstmatch) { - if (tab->do_firstmatch == join->join_tab - 1) + if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) extra.append(STRING_WITH_LEN("; FirstMatch")); else { diff --git a/sql/sql_select.h b/sql/sql_select.h index 779702e4b1c..c60419f1ffd 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -896,6 +896,20 @@ protected: public: JOIN_TAB *join_tab, **best_ref; + + /* + For "Using temporary+Using filesort" queries, JOIN::join_tab can point to + either: + 1. array of join tabs describing how to run the select, or + 2. array of single join tab describing read from the temporary table. + + SHOW EXPLAIN code needs to read/show #1. This is why two next members are + there for saving it. + */ + JOIN_TAB *table_access_tabs; + uint top_table_access_tabs_count; + + JOIN_TAB **map2table; ///< mapping between table indexes and JOIN_TABs JOIN_TAB *join_tab_save; ///< saved join_tab for subquery reexecution diff --git a/sql/table.h b/sql/table.h index da2109809d4..9b6d807b844 100644 --- a/sql/table.h +++ b/sql/table.h @@ -850,7 +850,12 @@ struct st_table { See TABLE_LIST::process_index_hints(). */ bool force_index_group; - bool distinct,const_table,no_rows, used_for_duplicate_elimination; + /* + TRUE<=> this table was created with create_tmp_table(... distinct=TRUE..) + call + */ + bool distinct; + bool const_table,no_rows, used_for_duplicate_elimination; /** If set, the optimizer has found that row retrieval should access index From 5116bed06c1a1641d54d8de8bc8770ad65ab165d Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 10 May 2012 13:44:57 +0400 Subject: [PATCH 20/67] bzr ignore libmysqld/my_apc.cc --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index a956e0c6ab4..e4fe3e8a9f2 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1982,3 +1982,4 @@ plugin/handler_socket/perl-Net-HandlerSocket/HandlerSocket.bs plugin/handler_socket/perl-Net-HandlerSocket/Makefile.PL libmysqld/gcalc_slicescan.cc libmysqld/gcalc_tools.cc +libmysqld/my_apc.cc From 6fae4447f0873c159d94d1a3d8deafbd224d8100 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 10 May 2012 15:13:57 +0400 Subject: [PATCH 21/67] # MDEV-239: Assertion `field_types == 0 ... ' failed in Protocol_text::store... - Make all functions that produce parts of EXPLAIN output take explain_flags as parameter, instead of looking into thd->lex->describe --- mysql-test/r/show_explain.result | 28 ++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 27 +++++++++++++++++++++++-- sql/sql_class.cc | 2 +- sql/sql_lex.cc | 16 ++++++++------- sql/sql_lex.h | 4 ++-- sql/sql_select.cc | 34 +++++++++++++++++++++----------- sql/sql_select.h | 3 ++- 7 files changed, 89 insertions(+), 25 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index db1d57b7004..4f0e89975ff 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -309,4 +309,32 @@ a 4 set debug=''; DROP TABLE t2; +# +# MDEV-239: Assertion `field_types == 0 ... ' failed in Protocol_text::store(double, uint32, String*) with +# SHOW EXPLAIN over EXPLAIN EXTENDED +# +CREATE TABLE t2 (a INT); +INSERT INTO t2 VALUES (1),(2),(1),(4),(2); +EXPLAIN EXTENDED SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a ; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 100.00 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t2` join `test`.`t2` group by `test`.`t2`.`a` +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +EXPLAIN EXTENDED SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a ; +show explain for $thr2; +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 EXPLAIN EXTENDED SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 100.00 Using temporary; Using filesort +1 SIMPLE t2 ALL NULL NULL NULL NULL 5 100.00 Using join buffer (flat, BNL join) +Warnings: +Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t2` join `test`.`t2` group by `test`.`t2`.`a` +set debug=''; +DROP TABLE t2; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 8c9d5f4c1e8..02850efedd8 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -319,11 +319,34 @@ evalp show explain for $thr2; connection con1; reap; set debug=''; +DROP TABLE t2; + + +--echo # +--echo # MDEV-239: Assertion `field_types == 0 ... ' failed in Protocol_text::store(double, uint32, String*) with +--echo # SHOW EXPLAIN over EXPLAIN EXTENDED +--echo # + + +CREATE TABLE t2 (a INT); +INSERT INTO t2 VALUES (1),(2),(1),(4),(2); + +EXPLAIN EXTENDED SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a ; + +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +send EXPLAIN EXTENDED SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a ; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; +DROP TABLE t2; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. -DROP TABLE t2; - drop table t0,t1; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 4e9a0507bba..89d80dc1ec2 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3039,7 +3039,7 @@ void Show_explain_request::get_explain_data(void *arg) target_thd->query_length(), &my_charset_bin); - if (target_thd->lex->unit.print_explain(req->explain_buf)) + if (target_thd->lex->unit.print_explain(req->explain_buf, 0 /* explain flags */)) req->failed_to_produce= TRUE; target_thd->restore_active_arena((Query_arena*)req->request_thd, diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index a47ba9b5024..69bb822f05a 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3746,12 +3746,13 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) } -int st_select_lex::print_explain(select_result_sink *output) +int st_select_lex::print_explain(select_result_sink *output, + uint8 explain_flags) { int res; if (join && join->optimized == 2) { - res= join->print_explain(output, TRUE, + res= join->print_explain(output, explain_flags, TRUE, join->need_tmp, // need_tmp_table (join->order != 0 && !join->skip_sort_order), // bool need_order join->select_distinct, // bool distinct @@ -3769,7 +3770,7 @@ int st_select_lex::print_explain(select_result_sink *output) */ if (!(unit->item && unit->item->eliminated)) { - unit->print_explain(output); + unit->print_explain(output, explain_flags); } } } @@ -3777,7 +3778,7 @@ int st_select_lex::print_explain(select_result_sink *output) { /* Produce "not yet optimized" line */ const char *msg="Not yet optimized"; - res= join->print_explain(output, TRUE, + res= join->print_explain(output, explain_flags, TRUE, FALSE, // need_tmp_table, FALSE, // bool need_order, FALSE, // bool distinct, @@ -3788,7 +3789,8 @@ err: } -int st_select_lex_unit::print_explain(select_result_sink *output) +int st_select_lex_unit::print_explain(select_result_sink *output, + uint8 explain_flags) { int res= 0; SELECT_LEX *first= first_select(); @@ -3804,7 +3806,7 @@ int st_select_lex_unit::print_explain(select_result_sink *output) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - if ((res= sl->print_explain(output))) + if ((res= sl->print_explain(output, explain_flags))) break; } @@ -3814,7 +3816,7 @@ int st_select_lex_unit::print_explain(select_result_sink *output) if (fake_select_lex && !fake_select_lex->join) { res= print_fake_select_lex_join(output, TRUE /* on the fly */, - fake_select_lex, 0 /* flags */); + fake_select_lex, explain_flags); } return res; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 6f72c9e3f7b..8a7547691e9 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -595,7 +595,7 @@ public: friend int subselect_union_engine::exec(); List *get_unit_column_types(); - int print_explain(select_result_sink *output); + int print_explain(select_result_sink *output, uint8 explain_flags); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -918,7 +918,7 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); - int print_explain(select_result_sink *output); + int print_explain(select_result_sink *output, uint8 explain_flags); /* 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 6a842ead2a4..f48f180d1dd 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2123,6 +2123,14 @@ void JOIN::exec() */ thd->apc_target.enable(); exec_inner(); + + DBUG_EXECUTE_IF("show_explain_probe_join_exec_end", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + thd->apc_target.disable(); } @@ -21075,7 +21083,7 @@ void JOIN::clear() int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, - SELECT_LEX *select_lex, uint8 select_options) + SELECT_LEX *select_lex, uint8 explain_flags) { const CHARSET_INFO *cs= system_charset_info; Item *item_null= new Item_null(); @@ -21121,7 +21129,7 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, item_list.push_back(new Item_string(table_name_buffer, len, cs)); } /* partitions */ - if (/*join->thd->lex->describe*/ select_options & DESCRIBE_PARTITIONS) + if (explain_flags & DESCRIBE_PARTITIONS) item_list.push_back(item_null); /* type */ item_list.push_back(new Item_string(join_type_str[JT_ALL], @@ -21136,7 +21144,7 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, /* ref */ item_list.push_back(item_null); /* in_rows */ - if (select_options & DESCRIBE_EXTENDED) + if (explain_flags & DESCRIBE_EXTENDED) item_list.push_back(item_null); /* rows */ item_list.push_back(item_null); @@ -21162,7 +21170,8 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, modifications to any select's data structures */ -int JOIN::print_explain(select_result_sink *result, bool on_the_fly, +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) { @@ -21199,9 +21208,9 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, strlen(join->select_lex->type), cs)); for (uint i=0 ; i < 7; i++) item_list.push_back(item_null); - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) + if (explain_flags & DESCRIBE_PARTITIONS) item_list.push_back(item_null); - if (join->thd->lex->describe & DESCRIBE_EXTENDED) + if (explain_flags & DESCRIBE_EXTENDED) item_list.push_back(item_null); item_list.push_back(new Item_string(message,strlen(message),cs)); @@ -21212,7 +21221,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, { if (print_fake_select_lex_join(result, on_the_fly, join->select_lex, - join->thd->lex->describe)) + explain_flags)) error= 1; } else if (!join->select_lex->master_unit()->derived || @@ -21317,7 +21326,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, cs)); } /* "partitions" column */ - if (join->thd->lex->describe & DESCRIBE_PARTITIONS) + if (explain_flags & DESCRIBE_PARTITIONS) { #ifdef WITH_PARTITION_STORAGE_ENGINE partition_info *part_info; @@ -21460,7 +21469,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, table_list->schema_table) { /* in_rows */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) + if (explain_flags & DESCRIBE_EXTENDED) item_list.push_back(item_null); /* rows */ item_list.push_back(item_null); @@ -21497,7 +21506,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, MY_INT64_NUM_DECIMAL_DIGITS)); /* Add "filtered" field to item_list. */ - if (join->thd->lex->describe & DESCRIBE_EXTENDED) + if (explain_flags & DESCRIBE_EXTENDED) { float f= 0.0; if (examined_rows) @@ -21577,7 +21586,7 @@ int JOIN::print_explain(select_result_sink *result, bool on_the_fly, { extra.append(STRING_WITH_LEN("; Using where with pushed " "condition")); - if (thd->lex->describe & DESCRIBE_EXTENDED) + if (explain_flags & DESCRIBE_EXTENDED) { extra.append(STRING_WITH_LEN(": ")); ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); @@ -21732,7 +21741,8 @@ 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"); - join->error= join->print_explain(result, FALSE, /* Not on-the-fly */ + join->error= join->print_explain(result, thd->lex->describe, + FALSE, /* Not on-the-fly */ need_tmp_table, need_order, distinct, message); diff --git a/sql/sql_select.h b/sql/sql_select.h index c60419f1ffd..a15720b4b30 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1405,7 +1405,8 @@ public: return (unit->item && unit->item->is_in_predicate()); } - int print_explain(select_result_sink *result, bool on_the_fly, + 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); private: From 6bce336624e84f5ec377926b105ec2002b38c96b Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 11 May 2012 18:13:06 +0400 Subject: [PATCH 22/67] MDEV-240: SHOW EXPLAIN: Assertion `this->optimized == 2' failed - Fix the bug: SHOW EXPLAIN may hit a case where a join is partially optimized. - Change JOIN::optimized to use enum instead of numeric constants --- mysql-test/r/show_explain.result | 33 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 25 ++++++++++++++++++++++++ sql/item_subselect.cc | 6 ++++-- sql/sql_lex.cc | 2 +- sql/sql_select.cc | 17 ++++++++++------ sql/sql_select.h | 8 ++++++-- 6 files changed, 80 insertions(+), 11 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 4f0e89975ff..2051a409178 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -1,4 +1,5 @@ drop table if exists t0, t1, t2; +drop view if exists v1; create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); create table t1 (a int); @@ -337,4 +338,36 @@ Warnings: Note 1003 select `test`.`t2`.`a` AS `a` from `test`.`t2` join `test`.`t2` group by `test`.`t2`.`a` set debug=''; DROP TABLE t2; +# +# MDEV-240: SHOW EXPLAIN: Assertion `this->optimized == 2' failed in +# JOIN::print_explain on query with a JOIN, TEMPTABLE view, +# +CREATE TABLE t3 (a INT); +CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t3; +INSERT INTO t3 VALUES (8); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (4),(5),(6),(7),(8),(9); +explain SELECT * FROM v1, t2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY system NULL NULL NULL NULL 1 +1 PRIMARY t2 ALL NULL NULL NULL NULL 6 +2 DERIVED t3 system NULL NULL NULL NULL 1 +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_join_exec_end'; +SELECT * FROM v1, t2; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Not yet optimized +Warnings: +Note 1003 SELECT * FROM v1, t2 +a b +8 4 +8 5 +8 6 +8 7 +8 8 +8 9 +set debug=''; +DROP VIEW v1; +DROP TABLE t2, t3; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 02850efedd8..36c34c8df5c 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -5,6 +5,7 @@ --disable_warnings drop table if exists t0, t1, t2; +drop view if exists v1; --enable_warnings # @@ -346,6 +347,30 @@ set debug=''; DROP TABLE t2; +--echo # +--echo # MDEV-240: SHOW EXPLAIN: Assertion `this->optimized == 2' failed in +--echo # JOIN::print_explain on query with a JOIN, TEMPTABLE view, +--echo # +CREATE TABLE t3 (a INT); +CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t3; +INSERT INTO t3 VALUES (8); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (4),(5),(6),(7),(8),(9); +explain SELECT * FROM v1, t2; + +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_join_exec_end'; +send SELECT * FROM v1, t2; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; +DROP VIEW v1; +DROP TABLE t2, t3; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 8b720b350a5..1780953bd7e 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -2910,7 +2910,7 @@ int subselect_single_select_engine::exec() SELECT_LEX *save_select= thd->lex->current_select; thd->lex->current_select= select_lex; - if (!join->optimized) + if (join->optimized != JOIN::OPTIMIZATION_DONE) { SELECT_LEX_UNIT *unit= select_lex->master_unit(); @@ -4647,7 +4647,9 @@ int subselect_hash_sj_engine::exec() */ thd->lex->current_select= materialize_engine->select_lex; /* The subquery should be optimized, and materialized only once. */ - DBUG_ASSERT(materialize_join->optimized && !is_materialized); + DBUG_ASSERT(materialize_join->optimized == JOIN::OPTIMIZATION_DONE && + !is_materialized); + materialize_join->exec(); if ((res= test(materialize_join->error || thd->is_fatal_error || thd->is_error()))) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 69bb822f05a..29d0a9e9888 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3750,7 +3750,7 @@ int st_select_lex::print_explain(select_result_sink *output, uint8 explain_flags) { int res; - if (join && join->optimized == 2) + if (join && join->optimized == JOIN::OPTIMIZATION_DONE) { res= join->print_explain(output, explain_flags, TRUE, join->need_tmp, // need_tmp_table diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f48f180d1dd..af7a06e424d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -608,7 +608,7 @@ JOIN::prepare(Item ***rref_pointer_array, DBUG_ENTER("JOIN::prepare"); // to prevent double initialization on EXPLAIN - if (optimized) + if (optimized != JOIN::NOT_OPTIMIZED) DBUG_RETURN(0); conds= conds_init; @@ -936,7 +936,7 @@ err: int JOIN::optimize() { int res= optimize_inner(); - optimized= 2; + optimized= JOIN::OPTIMIZATION_DONE; return res; } /** @@ -960,9 +960,9 @@ JOIN::optimize_inner() DBUG_ENTER("JOIN::optimize"); do_send_rows = (unit->select_limit_cnt) ? 1 : 0; // to prevent double initialization on EXPLAIN - if (optimized) + if (optimized != JOIN::NOT_OPTIMIZED) DBUG_RETURN(0); - optimized= 1; + optimized= JOIN::OPTIMIZATION_IN_PROGRESS; thd_proc_info(thd, "optimizing"); set_allowed_join_cache_types(); @@ -1731,7 +1731,7 @@ int JOIN::init_execution() { DBUG_ENTER("JOIN::init_execution"); - DBUG_ASSERT(optimized); + DBUG_ASSERT(optimized == JOIN::OPTIMIZATION_DONE); DBUG_ASSERT(!(select_options & SELECT_DESCRIBE)); initialized= true; @@ -21187,7 +21187,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", (ulong)join->select_lex, join->select_lex->type, message ? message : "NULL")); - DBUG_ASSERT(this->optimized == 2); + /* Don't log this into the slow query log */ if (!on_the_fly) @@ -21204,6 +21204,10 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, { item_list.push_back(new Item_int((int32) join->select_lex->select_number)); + + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); + item_list.push_back(new Item_string(join->select_lex->type, strlen(join->select_lex->type), cs)); for (uint i=0 ; i < 7; i++) @@ -21227,6 +21231,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, else if (!join->select_lex->master_unit()->derived || join->select_lex->master_unit()->derived->is_materialized_derived()) { + DBUG_ASSERT(optimized == JOIN::OPTIMIZATION_DONE); table_map used_tables=0; //if (!join->select_lex->type) if (on_the_fly) diff --git a/sql/sql_select.h b/sql/sql_select.h index a15720b4b30..3bb007f0d93 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1178,7 +1178,11 @@ public: const char *zero_result_cause; ///< not 0 if exec must return zero result bool union_part; ///< this subselect is part of union - int optimized; ///< flag to avoid double optimization in EXPLAIN + + enum join_optimization_state { NOT_OPTIMIZED=0, + OPTIMIZATION_IN_PROGRESS=1, + OPTIMIZATION_DONE=2}; + join_optimization_state optimized; ///< flag to avoid double optimization in EXPLAIN bool initialized; ///< flag to avoid double init_execution calls /* @@ -1261,7 +1265,7 @@ public: ref_pointer_array= items0= items1= items2= items3= 0; ref_pointer_array_size= 0; zero_result_cause= 0; - optimized= 0; + optimized= JOIN::NOT_OPTIMIZED; initialized= 0; cond_equal= 0; having_equal= 0; From b3841ae9e4754e2aae1c106767d4ba50236cbf9b Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 14 May 2012 22:39:00 +0400 Subject: [PATCH 23/67] MDEV-267: SHOW EXPLAIN: Server crashes in JOIN::print_explain on most of queries - Make SHOW EXPLAIN code handle degenerate subquery cases (No tables, impossible where, etc) --- mysql-test/r/show_explain.result | 44 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 43 +++++++++++++++++++++++++++++++ sql/sql_lex.cc | 21 +++++++++++---- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 2051a409178..b09903a617a 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -370,4 +370,48 @@ a b set debug=''; DROP VIEW v1; DROP TABLE t2, t3; +# +# MDEV-267: SHOW EXPLAIN: Server crashes in JOIN::print_explain on most of queries +# +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +select sleep(1); +show explain for $thr2; +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 +Warnings: +Note 1003 select sleep(1) +sleep(1) +0 +set debug=''; +# +# Same as above, but try another reason for JOIN to be degenerate +# +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +select * from t0 where 1>10; +show explain for $thr2; +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=''; +# +# Same as above, but try another reason for JOIN to be degenerate (2) +# +create table t3(a int primary key); +insert into t3 select a from t0; +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +select * from t0,t3 where t3.a=112233; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t3 const PRIMARY PRIMARY 4 0 unique row not found +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 +Warnings: +Note 1003 select * from t0,t3 where t3.a=112233 +a a +set debug=''; +drop table t3; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 36c34c8df5c..c9f715bbb67 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -371,6 +371,49 @@ set debug=''; DROP VIEW v1; DROP TABLE t2, t3; +--echo # +--echo # MDEV-267: SHOW EXPLAIN: Server crashes in JOIN::print_explain on most of queries +--echo # +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +send select sleep(1); +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; + + +--echo # +--echo # Same as above, but try another reason for JOIN to be degenerate +--echo # +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +send select * from t0 where 1>10; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; + +--echo # +--echo # Same as above, but try another reason for JOIN to be degenerate (2) +--echo # +create table t3(a int primary key); +insert into t3 select a from t0; +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +send select * from t0,t3 where t3.a=112233; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; +drop table t3; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 29d0a9e9888..8838abae735 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3752,11 +3752,22 @@ int st_select_lex::print_explain(select_result_sink *output, int res; if (join && join->optimized == JOIN::OPTIMIZATION_DONE) { - res= join->print_explain(output, explain_flags, TRUE, - join->need_tmp, // need_tmp_table - (join->order != 0 && !join->skip_sort_order), // bool need_order - join->select_distinct, // bool distinct - NULL); //const char *message + if (!join->table_count) + { + /* 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->order != 0 && !join->skip_sort_order), // bool need_order + join->select_distinct, // bool distinct + NULL); //const char *message + } if (res) goto err; From 382e81ca84a51fded86df6f21e0b4e003fc94388 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 15 May 2012 15:56:50 +0400 Subject: [PATCH 24/67] MDEV-270: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with select tables optimized away - Take into account, that for some degenerate joins instead of "join->table_count=0" the code sets "join->tables_list=0". --- mysql-test/r/show_explain.result | 39 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 34 ++++++++++++++++++++++++++++ sql/sql_lex.cc | 2 +- sql/sql_select.cc | 9 ++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index b09903a617a..1b1c30b56df 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -414,4 +414,43 @@ Note 1003 select * from t0,t3 where t3.a=112233 a a set debug=''; drop table t3; +# +# MDEV-270: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with +# select tables optimized away +# +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT ) ENGINE=MyISAM; +INSERT INTO t2 VALUES +(1,4),(2,62),(3,7),(4,1),(5,0),(6,7),(7,7),(8,1),(9,7),(10,1), +(11,5),(12,2),(13,0),(14,1),(15,8),(16,1),(17,1),(18,9),(19,1),(20,5) ; +explain SELECT * FROM t2 WHERE a = +(SELECT MAX(a) FROM t2 +WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 3) +); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 Using where +2 SUBQUERY t2 const PRIMARY PRIMARY 4 const 1 Using where +3 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_do_select'; +SELECT * FROM t2 WHERE a = +(SELECT MAX(a) FROM t2 +WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 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 20 Using where +2 SUBQUERY t2 const PRIMARY PRIMARY 4 1 Using where +3 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +Warnings: +Note 1003 SELECT * FROM t2 WHERE a = +(SELECT MAX(a) FROM t2 +WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 3) +) +pk a +3 7 +6 7 +7 7 +9 7 +set debug=''; +drop table t2; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index c9f715bbb67..d813956a1fe 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -414,6 +414,40 @@ reap; set debug=''; drop table t3; +--echo # +--echo # MDEV-270: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with +--echo # select tables optimized away +--echo # + +CREATE TABLE t2 (pk INT PRIMARY KEY, a INT ) ENGINE=MyISAM; +INSERT INTO t2 VALUES + (1,4),(2,62),(3,7),(4,1),(5,0),(6,7),(7,7),(8,1),(9,7),(10,1), + (11,5),(12,2),(13,0),(14,1),(15,8),(16,1),(17,1),(18,9),(19,1),(20,5) ; + +explain SELECT * FROM t2 WHERE a = + (SELECT MAX(a) FROM t2 + WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 3) + ); + +set @show_explain_probe_select_id=2; +set debug='d,show_explain_probe_do_select'; +send SELECT * FROM t2 WHERE a = + (SELECT MAX(a) FROM t2 + WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 3) + ); +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; +drop table t2; + + + + + + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 8838abae735..556077220de 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3752,7 +3752,7 @@ int st_select_lex::print_explain(select_result_sink *output, int res; if (join && join->optimized == JOIN::OPTIMIZATION_DONE) { - if (!join->table_count) + if (!join->table_count || !join->tables_list) { /* It's a degenerate join */ const char *cause= join->zero_result_cause ? join-> zero_result_cause : diff --git a/sql/sql_select.cc b/sql/sql_select.cc index af7a06e424d..7af818ba147 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -15399,6 +15399,15 @@ do_select(JOIN *join,List *fields,TABLE *table,Procedure *procedure) else { DBUG_ASSERT(join->table_count); + + THD *thd= join->thd; + DBUG_EXECUTE_IF("show_explain_probe_do_select", + if (dbug_user_var_equals_int(thd, + "show_explain_probe_select_id", + join->select_lex->select_number)) + dbug_serve_apcs(thd, 1); + ); + if (join->outer_ref_cond && !join->outer_ref_cond->val_int()) error= NESTED_LOOP_NO_MORE_ROWS; else From 533e1d28459ab5aa0604560bf8885ae18e553295 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 16 May 2012 12:49:22 +0400 Subject: [PATCH 25/67] MDEV-273: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with impossible WHERE - It turns out, there is a case where the join is degenerate, but join->table_count!= && join->tables_list!=NULL. Need to also check if join->zero_result_cause!=NULL, too. - There is a slight problem: The code sets zero_result_cause= "no matching row in const table" when NOT running EXPLAIN. The result is that SHOW EXPLAIN will show this line while regular EXPLAIN will not. --- mysql-test/r/show_explain.result | 56 ++++++++++++++++++++++++++++++-- mysql-test/t/show_explain.test | 43 +++++++++++++++++++++++- sql/sql_lex.cc | 6 +++- 3 files changed, 100 insertions(+), 5 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 1b1c30b56df..d19a6f29539 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -1,4 +1,4 @@ -drop table if exists t0, t1, t2; +drop table if exists t0, t1, t2, t3, t4; drop view if exists v1; create table t0 (a int); insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); @@ -407,8 +407,7 @@ set debug='d,show_explain_probe_join_exec_end'; select * from t0,t3 where t3.a=112233; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t3 const PRIMARY PRIMARY 4 0 unique row not found -1 SIMPLE t0 ALL NULL NULL NULL NULL 10 +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 @@ -453,4 +452,55 @@ pk a 9 7 set debug=''; drop table t2; +# +# MDEV-273: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with impossible WHERE +# +CREATE TABLE t2 (a1 INT, KEY(a1)) ENGINE=MyISAM; +INSERT INTO t2 VALUES +(4),(6),(7),(1),(0),(7),(7),(1),(7),(1), +(5),(2),(0),(1),(8),(1),(1),(9),(1),(5); +CREATE TABLE t3 (b1 INT) ENGINE=MyISAM; +INSERT INTO t3 VALUES +(4),(5),(8),(4),(8),(2),(9),(6),(4),(8), +(3),(5),(9),(6),(8),(3),(2),(6),(3),(1), +(4),(3),(1),(7),(0),(0),(9),(5),(9),(0), +(2),(2),(5),(9),(1),(4),(8),(6),(5),(5), +(1),(7),(2),(8),(9),(3),(2),(6),(6),(5), +(4),(3),(2),(7),(4),(6),(0),(8),(5),(8), +(2),(9),(7),(5),(7),(0),(4),(3),(1),(0), +(6),(2),(8),(3),(7),(3),(5),(5),(1),(2), +(1),(7),(1),(9),(9),(8),(3); +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +EXPLAIN +SELECT count(*) FROM t2, t3 +WHERE a1 < ALL ( +SELECT a1 FROM t2 +WHERE a1 IN ( SELECT a1 FROM t2, t4 ) +); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 index NULL a1 5 NULL 20 Using where; Using index +1 PRIMARY t3 ALL NULL NULL NULL NULL 87 Using join buffer (flat, BNL join) +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_do_select'; +SELECT count(*) FROM t2, t3 +WHERE a1 < ALL ( +SELECT a1 FROM t2 +WHERE a1 IN ( SELECT a1 FROM t2, t4 ) +); +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 index NULL a1 5 NULL 20 Using where; Using index +1 PRIMARY t3 ALL NULL NULL NULL NULL 87 Using join buffer (flat, BNL join) +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL no matching row in const table +Warnings: +Note 1003 SELECT count(*) FROM t2, t3 +WHERE a1 < ALL ( +SELECT a1 FROM t2 +WHERE a1 IN ( SELECT a1 FROM t2, t4 ) +) +count(*) +1740 +set debug=''; +drop table t2, t3, t4; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index d813956a1fe..35694e97111 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -4,7 +4,7 @@ --source include/have_debug.inc --disable_warnings -drop table if exists t0, t1, t2; +drop table if exists t0, t1, t2, t3, t4; drop view if exists v1; --enable_warnings @@ -444,9 +444,50 @@ set debug=''; drop table t2; +--echo # +--echo # MDEV-273: SHOW EXPLAIN: server crashes in JOIN::print_explain on a query with impossible WHERE +--echo # +CREATE TABLE t2 (a1 INT, KEY(a1)) ENGINE=MyISAM; +INSERT INTO t2 VALUES + (4),(6),(7),(1),(0),(7),(7),(1),(7),(1), + (5),(2),(0),(1),(8),(1),(1),(9),(1),(5); +CREATE TABLE t3 (b1 INT) ENGINE=MyISAM; +INSERT INTO t3 VALUES + (4),(5),(8),(4),(8),(2),(9),(6),(4),(8), + (3),(5),(9),(6),(8),(3),(2),(6),(3),(1), + (4),(3),(1),(7),(0),(0),(9),(5),(9),(0), + (2),(2),(5),(9),(1),(4),(8),(6),(5),(5), + (1),(7),(2),(8),(9),(3),(2),(6),(6),(5), + (4),(3),(2),(7),(4),(6),(0),(8),(5),(8), + (2),(9),(7),(5),(7),(0),(4),(3),(1),(0), + (6),(2),(8),(3),(7),(3),(5),(5),(1),(2), + (1),(7),(1),(9),(9),(8),(3); +CREATE TABLE t4 (c1 INT) ENGINE=MyISAM; +EXPLAIN +SELECT count(*) FROM t2, t3 +WHERE a1 < ALL ( + SELECT a1 FROM t2 + WHERE a1 IN ( SELECT a1 FROM t2, t4 ) +); +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_do_select'; +send +SELECT count(*) FROM t2, t3 +WHERE a1 < ALL ( + SELECT a1 FROM t2 + WHERE a1 IN ( SELECT a1 FROM t2, t4 ) +); + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; +drop table t2, t3, t4; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 556077220de..13972d56605 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3752,7 +3752,11 @@ int st_select_lex::print_explain(select_result_sink *output, int res; if (join && join->optimized == JOIN::OPTIMIZATION_DONE) { - if (!join->table_count || !join->tables_list) + /* + 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 : From 483ae4bf81851a16e27cfc67b0eb474fed0a97dd Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 16 May 2012 20:22:54 +0400 Subject: [PATCH 26/67] Make SHOW EXPLAIN give a separate timeout error. --- sql/sql_show.cc | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index a0c39ff6c8c..2a9e5fa4954 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2121,9 +2121,18 @@ void mysqld_show_explain(THD *thd, ulong thread_id) if (bres || explain_req.failed_to_produce) { /* TODO not enabled or time out */ - my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), - "SHOW EXPLAIN", - "Target is not running EXPLAINable command"); + if (timed_out) + { + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), + "SHOW EXPLAIN", + "Timeout"); + } + else + { + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), + "SHOW EXPLAIN", + "Target is not running EXPLAINable command"); + } bres= TRUE; } else From 6f199f7c4f2fba51e938ba4b009c1bd173193ea0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 24 May 2012 22:22:39 +0400 Subject: [PATCH 27/67] MDEV-275: SHOW EXPLAIN: server crashes in JOIN::print_explain with IN subquery and aggregate function - Don't try to produce plans after JOIN::cleanup() has been called, because: = JOIN::cleanup leaves data structures in partially-cleaned state = Walking them is hazardous (see this bug), and has funny effects (See previous commits, "Using join cache" may or may not be shown) = Changing data structures to be persisted may cause unwanted side effects - The consequence is that SHOW EXPLAIN will show "Query plan already deleted" when e.g. reading data after filesort. --- mysql-test/r/show_explain.result | 36 +++++++++++++++++++++++++------- mysql-test/t/show_explain.test | 22 +++++++++++++++++++ sql/sql_select.cc | 3 ++- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 55bc875fe59..51c2a7341ee 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -154,8 +154,7 @@ 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; id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY a ALL NULL NULL NULL NULL 10 -2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted 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) @@ -208,13 +207,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 t0 ALL NULL NULL NULL NULL 10 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted 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 t0 ALL NULL NULL NULL NULL 10 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted Warnings: Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2 a SUBQ @@ -312,8 +311,7 @@ SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; # NOTE: current code will not show "Using join buffer": show explain for $thr2; 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 +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Query plan already deleted Warnings: Note 1003 SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a a @@ -404,7 +402,7 @@ set debug_dbug='d,show_explain_probe_join_exec_end'; select * from t0 where 1>10; show explain for $thr2; 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 Query plan already deleted Warnings: Note 1003 select * from t0 where 1>10 a @@ -419,7 +417,7 @@ set debug_dbug='d,show_explain_probe_join_exec_end'; select * from t0,t3 where t3.a=112233; show explain for $thr2; 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 +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Query plan already deleted Warnings: Note 1003 select * from t0,t3 where t3.a=112233 a a @@ -515,4 +513,26 @@ count(*) 1740 set debug_dbug=''; drop table t2, t3, t4; +# +# MDEV-275: SHOW EXPLAIN: server crashes in JOIN::print_explain with IN subquery and aggregate function +# +CREATE TABLE t2 ( `pk` INT NOT NULL PRIMARY KEY, `a1` INT NOT NULL, KEY(`a1`)) ENGINE=MyISAM; +INSERT INTO t2 VALUES +(1,5),(2,4),(3,6),(4,9),(5,2),(6,8),(7,4),(8,8),(9,0),(10,43), +(11,23),(12,3),(13,45),(14,16),(15,2),(16,33),(17,2),(18,5),(19,9),(20,2); +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +Warnings: +Warning 1287 The syntax '@@debug' is deprecated and will be removed in MariaDB 5.6. Please use '@@debug_dbug' instead +SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`); +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +Warnings: +Note 1003 SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`) +pk a1 +set debug=''; +Warnings: +Warning 1287 The syntax '@@debug' is deprecated and will be removed in MariaDB 5.6. Please use '@@debug_dbug' instead +DROP TABLE t2; drop table t0,t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 01dd7b8801d..57f0c48db3a 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -499,6 +499,28 @@ reap; set debug_dbug=''; drop table t2, t3, t4; +--echo # +--echo # MDEV-275: SHOW EXPLAIN: server crashes in JOIN::print_explain with IN subquery and aggregate function +--echo # +CREATE TABLE t2 ( `pk` INT NOT NULL PRIMARY KEY, `a1` INT NOT NULL, KEY(`a1`)) ENGINE=MyISAM; +INSERT INTO t2 VALUES + (1,5),(2,4),(3,6),(4,9),(5,2),(6,8),(7,4),(8,8),(9,0),(10,43), + (11,23),(12,3),(13,45),(14,16),(15,2),(16,33),(17,2),(18,5),(19,9),(20,2); + +set @show_explain_probe_select_id=1; +set debug='d,show_explain_probe_join_exec_end'; +send + SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`); +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug=''; + +DROP TABLE t2; + + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4d7f66388d9..070c5eb9e8c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10569,8 +10569,9 @@ void JOIN::cleanup(bool full) /* psergey: let's try without this first: - have_query_plan= QEP_DELETED; */ + have_query_plan= QEP_DELETED; + if (table) { JOIN_TAB *tab; From a8b2e831f06d37e1de9a8bd3bd9fee5e0a1e9b6a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 4 Jun 2012 19:48:53 +0400 Subject: [PATCH 28/67] MDEV-305: SHOW EXPLAIN: ref returned by SHOW EXPLAIN is different from the normal EXPLAIN ('const' vs empty string) - The problem was that create_ref_for_key() would act differently, depending on whether we're running EXPLAIN or the actual query. - As the first step, fixed the EXPLAIN printout not to depend on actions in create_ref_for_key(). --- mysql-test/r/show_explain.result | 31 +++++++++++++++++++++++-------- mysql-test/t/show_explain.test | 29 ++++++++++++++++++++++++++--- sql/sql_select.cc | 16 +++++++++++++--- sql/sql_select.h | 7 +++++++ 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 51c2a7341ee..18fd01e3284 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -448,7 +448,7 @@ WHERE pk= (SELECT MAX(pk) FROM t2 WHERE pk = 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 20 Using where -2 SUBQUERY t2 const PRIMARY PRIMARY 4 1 Using where +2 SUBQUERY t2 const PRIMARY PRIMARY 4 const 1 Using where 3 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away Warnings: Note 1003 SELECT * FROM t2 WHERE a = @@ -521,9 +521,7 @@ INSERT INTO t2 VALUES (1,5),(2,4),(3,6),(4,9),(5,2),(6,8),(7,4),(8,8),(9,0),(10,43), (11,23),(12,3),(13,45),(14,16),(15,2),(16,33),(17,2),(18,5),(19,9),(20,2); set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_join_exec_end'; -Warnings: -Warning 1287 The syntax '@@debug' is deprecated and will be removed in MariaDB 5.6. Please use '@@debug_dbug' instead +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; id select_type table type possible_keys key key_len ref rows Extra @@ -531,8 +529,25 @@ id select_type table type possible_keys key key_len ref rows Extra Warnings: Note 1003 SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`) pk a1 -set debug=''; -Warnings: -Warning 1287 The syntax '@@debug' is deprecated and will be removed in MariaDB 5.6. Please use '@@debug_dbug' instead +set debug_dbug=''; DROP TABLE t2; -drop table t0,t1; +DROP TABLE t1; +# +# MDEV-305: SHOW EXPLAIN: ref returned by SHOW EXPLAIN is different from the normal EXPLAIN ('const' vs empty string) +# +CREATE TABLE t1(a INT, KEY(a)); +INSERT INTO t1 VALUES (3),(1),(5),(1); +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SELECT 'test' FROM t1 WHERE a=1; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref a a 5 const 1 Using index +Warnings: +Note 1003 SELECT 'test' FROM t1 WHERE a=1 +test +test +test +set debug_dbug=''; +DROP TABLE t1; +drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 57f0c48db3a..6c087a3c0fc 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -508,7 +508,7 @@ INSERT INTO t2 VALUES (11,23),(12,3),(13,45),(14,16),(15,2),(16,33),(17,2),(18,5),(19,9),(20,2); set @show_explain_probe_select_id=1; -set debug='d,show_explain_probe_join_exec_end'; +set debug_dbug='d,show_explain_probe_join_exec_end'; send SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`); connection default; @@ -516,10 +516,33 @@ connection default; evalp show explain for $thr2; connection con1; reap; -set debug=''; +set debug_dbug=''; DROP TABLE t2; +DROP TABLE t1; + +--echo # +--echo # MDEV-305: SHOW EXPLAIN: ref returned by SHOW EXPLAIN is different from the normal EXPLAIN ('const' vs empty string) +--echo # +CREATE TABLE t1(a INT, KEY(a)); +INSERT INTO t1 VALUES (3),(1),(5),(1); + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; + +send SELECT 'test' FROM t1 WHERE a=1; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug_dbug=''; + +DROP TABLE t1; + + + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. @@ -527,4 +550,4 @@ DROP TABLE t2; ## TODO: SHOW EXPLAIN while the primary query is running EXPLAIN EXTENDED/PARTITIONS ## -drop table t0,t1; +drop table t0; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 070c5eb9e8c..e1086342636 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -7905,6 +7905,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, j->ref.null_rejecting= 0; j->ref.disable_cache= FALSE; j->ref.null_ref_part= NO_REF_PART; + j->ref.const_ref_part_map= 0; keyuse=org_keyuse; store_key **ref_key= j->ref.key_copy; @@ -7952,6 +7953,7 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, if (thd->is_fatal_error) DBUG_RETURN(TRUE); tmp.copy(); + j->ref.const_ref_part_map |= key_part_map(1) << i ; } else *ref_key++= get_store_key(thd, @@ -21521,13 +21523,21 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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) + if (tab->ref.key_parts && tab_type != JT_FT) { - for (store_key **ref=tab->ref.key_copy ; *ref ; ref++) + store_key **ref=tab->ref.key_copy; + for (uint kp= 0; kp < tab->ref.key_parts; kp++) { if (tmp4.length()) tmp4.append(','); - tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); + + 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++; + } } } } diff --git a/sql/sql_select.h b/sql/sql_select.h index 00af5e14607..84d7529de49 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -101,6 +101,13 @@ typedef struct st_table_ref uchar *key_buff; ///< value to look for with key uchar *key_buff2; ///< key_buff+key_length store_key **key_copy; // + + /* + Bitmap of key parts which refer to constants. key_copy only has copiers for + non-const key parts. + */ + key_part_map const_ref_part_map; + Item **items; ///< val()'s for each keypart /* Array of pointers to trigger variables. Some/all of the pointers may be From 13e985787e4459e867fe028018f2638326c6d52f Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 4 Jun 2012 20:07:02 +0400 Subject: [PATCH 29/67] Added comments about create_ref_for_key() --- sql/sql_select.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index e1086342636..42f475e2904 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -7941,6 +7941,13 @@ static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, if (keyuse->null_rejecting) j->ref.null_rejecting |= 1 << i; keyuse_uses_no_tables= keyuse_uses_no_tables && !keyuse->used_tables; + /* + Todo: we should remove this check for thd->lex->describe on the next + line. With SHOW EXPLAIN code, EXPLAIN printout code no longer depends + on it. However, removing the check caused change in lots of query + plans! Does the optimizer depend on the contents of + table_ref->key_copy ? If yes, do we produce incorrect EXPLAINs? + */ if (!keyuse->val->used_tables() && !thd->lex->describe) { // Compare against constant store_key_item tmp(thd, From 9a7b3bf4b7b08f9fd1d26260131696475998ab81 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 4 Jun 2012 23:37:45 +0400 Subject: [PATCH 30/67] MDEV-299: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN changes back and forth during query execution - Make SHOW EXPLAIN ignore range accesses when printing "Range checked for each record" plans. --- mysql-test/r/show_explain.result | 46 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 38 ++++++++++++++++++++++++++ sql/sql_select.cc | 26 ++++++++++++------ 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 18fd01e3284..515118b1cd8 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -550,4 +550,50 @@ test test set debug_dbug=''; DROP TABLE t1; +# +# MDEV-299: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN changes back and forth during query execution +# +create table t1 (key1 int, col1 int, col2 int, filler char(100), key(key1)); +insert into t1 select A.a+ 10 * B.a, 10, 10, 'filler-data' from t0 A, t0 B; +update t1 set col1=3, col2=10 where key1=1; +update t1 set col1=3, col2=1000 where key1=2; +update t1 set col1=3, col2=10 where key1=3; +update t1 set col1=3, col2=1000 where key1=4; +set @tmp_mdev299_jcl= @@join_cache_level; +set join_cache_level=0; +explain select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE B ALL key1 NULL NULL NULL 100 Range checked for each record (index map: 0x1) +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_test_if_quick_select'; +select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE B ALL key1 NULL NULL NULL 100 Range checked for each record (index map: 0x1) +Warnings: +Note 1003 select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100 +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE B ALL key1 NULL NULL NULL 100 Range checked for each record (index map: 0x1) +Warnings: +Note 1003 select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100 +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE B ALL key1 NULL NULL NULL 100 Range checked for each record (index map: 0x1) +Warnings: +Note 1003 select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100 +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE A ALL NULL NULL NULL NULL 100 Using where +1 SIMPLE B ALL key1 NULL NULL NULL 100 Range checked for each record (index map: 0x1) +Warnings: +Note 1003 select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100 +count(*) +212 +set debug_dbug=''; +drop table t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 6c087a3c0fc..2baccea93be 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -542,6 +542,44 @@ set debug_dbug=''; DROP TABLE t1; +--echo # +--echo # MDEV-299: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN changes back and forth during query execution +--echo # + +create table t1 (key1 int, col1 int, col2 int, filler char(100), key(key1)); +insert into t1 select A.a+ 10 * B.a, 10, 10, 'filler-data' from t0 A, t0 B; + +# Make matches 3 records +update t1 set col1=3, col2=10 where key1=1; # small range +update t1 set col1=3, col2=1000 where key1=2; # big range +update t1 set col1=3, col2=10 where key1=3; # small range again +update t1 set col1=3, col2=1000 where key1=4; # big range + +set @tmp_mdev299_jcl= @@join_cache_level; +set join_cache_level=0; + +explain select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_test_if_quick_select'; + +send +select count(*) from t1 A, t1 B where B.key1 < A.col2 and A.col1=3 AND B.col2 + 1 < 100; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +--source include/wait_condition.inc +evalp show explain for $thr2; +--source include/wait_condition.inc +evalp show explain for $thr2; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +set debug_dbug=''; +drop table t1; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 42f475e2904..aba5c0761c8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -16912,6 +16912,14 @@ int read_first_record_seq(JOIN_TAB *tab) static int test_if_quick_select(JOIN_TAB *tab) { + DBUG_EXECUTE_IF("show_explain_probe_test_if_quick_select", + if (dbug_user_var_equals_int(tab->join->thd, + "show_explain_probe_select_id", + tab->join->select_lex->select_number)) + dbug_serve_apcs(tab->join->thd, 1); + ); + + delete tab->select->quick; tab->select->quick=0; return tab->select->test_quick_select(tab->join->thd, tab->keys, @@ -21410,6 +21418,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, tmp3.length(0); tmp4.length(0); quick_type= -1; + QUICK_SELECT_I *quick= NULL; /* Don't show eliminated tables */ if (table->map & join->eliminated_tables) @@ -21428,8 +21437,9 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, enum join_type tab_type= tab->type; if ((tab->type == JT_ALL || tab->type == JT_HASH) && - tab->select && tab->select->quick) + 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) || @@ -21568,9 +21578,9 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, tab->select && tab->select->quick) =======*/ } - if (tab->type != JT_CONST && tab->select && tab->select->quick) + if (tab->type != JT_CONST && tab->select && quick) tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); - if (key_info || (tab->select && tab->select->quick)) + if (key_info || (tab->select && quick)) { if (tmp2.length()) item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); @@ -21631,8 +21641,8 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, else { ha_rows examined_rows; - if (tab->select && tab->select->quick) - examined_rows= tab->select->quick->records; + if (tab->select && quick) + examined_rows= quick->records; else if (tab_type == JT_NEXT || tab_type == JT_ALL || is_hj) { if (tab->limit) @@ -21676,7 +21686,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, table->covering_keys.is_set(tab->index)) key_read=1; if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && - !((QUICK_ROR_INTERSECT_SELECT*)tab->select->quick)->need_to_fetch_row) + !((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row) key_read=1; if (tab->info) @@ -21704,8 +21714,8 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, uint keyno= MAX_KEY; if (tab->ref.key_parts) keyno= tab->ref.key; - else if (tab->select && tab->select->quick) - keyno = tab->select->quick->index; + else if (tab->select && quick) + keyno = quick->index; if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && table->file->pushed_idx_cond) From 2c1e737c6c955ef6e670514d3bd9031727f3602c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 7 Jun 2012 12:19:06 +0400 Subject: [PATCH 31/67] MDEV-297: SHOW EXPLAIN: Server gets stuck until timeout occurs while executing SHOW INDEX and SHOW EXPLAIN in parallel - Rework locking code to use the LOCK_thd_data mutex for all synchronization. This also fixed MDEV-301. --- mysql-test/r/show_explain.result | 20 ++++++ mysql-test/t/show_explain.test | 20 ++++++ sql/my_apc.cc | 109 ++++++++++++------------------- sql/my_apc.h | 39 ++++++++--- sql/sql_class.cc | 4 +- sql/sql_show.cc | 5 +- 6 files changed, 119 insertions(+), 78 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 515118b1cd8..d6c46c81c79 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -596,4 +596,24 @@ count(*) 212 set debug_dbug=''; drop table t1; +# +# MDEV-297: SHOW EXPLAIN: Server gets stuck until timeout occurs while +# executing SHOW INDEX and SHOW EXPLAIN in parallel +# +CREATE TABLE t1(a INT, b INT, c INT, KEY(a), KEY(b), KEY(c)); +INSERT INTO t1 (a) VALUES (3),(1),(5),(1); +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SHOW INDEX FROM t1; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE STATISTICS ALL NULL NULL NULL NULL NULL Skip_open_table; Scanned all databases +Warnings: +Note 1003 SHOW INDEX FROM t1 +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment +t1 1 a 1 a A NULL NULL NULL YES BTREE +t1 1 b 1 b A NULL NULL NULL YES BTREE +t1 1 c 1 c A NULL NULL NULL YES BTREE +set debug_dbug=''; +DROP TABLE t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 2baccea93be..5605e304bcc 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -581,6 +581,26 @@ reap; set debug_dbug=''; drop table t1; +--echo # +--echo # MDEV-297: SHOW EXPLAIN: Server gets stuck until timeout occurs while +--echo # executing SHOW INDEX and SHOW EXPLAIN in parallel +--echo # +CREATE TABLE t1(a INT, b INT, c INT, KEY(a), KEY(b), KEY(c)); +INSERT INTO t1 (a) VALUES (3),(1),(5),(1); + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; + +send SHOW INDEX FROM t1; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug_dbug=''; + +DROP TABLE t1; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 30adbaad1b6..d5e3eb080a2 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -9,6 +9,8 @@ #include #include +#include "my_apc.h" + #else #include "sql_priv.h" @@ -16,7 +18,6 @@ #endif -//#include "my_apc.h" /* Standalone testing: @@ -33,12 +34,10 @@ any call requests to it. Initial state after initialization is 'disabled'. */ -void Apc_target::init() +void Apc_target::init(mysql_mutex_t *target_mutex) { - // todo: should use my_pthread_... functions instead? DBUG_ASSERT(!enabled); - (void)pthread_mutex_init(&LOCK_apc_queue, MY_MUTEX_INIT_SLOW); - + LOCK_thd_data_ptr= target_mutex; #ifndef DBUG_OFF n_calls_processed= 0; #endif @@ -51,7 +50,6 @@ void Apc_target::init() void Apc_target::destroy() { DBUG_ASSERT(!enabled); - pthread_mutex_destroy(&LOCK_apc_queue); } @@ -60,9 +58,8 @@ void Apc_target::destroy() */ void Apc_target::enable() { - pthread_mutex_lock(&LOCK_apc_queue); + /* Ok to do without getting/releasing the mutex: */ enabled++; - pthread_mutex_unlock(&LOCK_apc_queue); } @@ -76,16 +73,16 @@ void Apc_target::enable() void Apc_target::disable() { bool process= FALSE; - pthread_mutex_lock(&LOCK_apc_queue); + mysql_mutex_lock(LOCK_thd_data_ptr); if (!(--enabled)) process= TRUE; - pthread_mutex_unlock(&LOCK_apc_queue); + mysql_mutex_unlock(LOCK_thd_data_ptr); if (process) process_apc_requests(); } -/* (internal) Put request into the request list */ +/* [internal] Put request qe into the request list */ void Apc_target::enqueue_request(Call_request *qe) { @@ -108,7 +105,7 @@ void Apc_target::enqueue_request(Call_request *qe) /* - (internal) Remove given request from the request queue. + [internal] Remove request qe from the request queue. The request is not necessarily first in the queue. */ @@ -131,11 +128,14 @@ void Apc_target::dequeue_request(Call_request *qe) /* - Make an APC (Async Procedure Call) in another thread. + Make an APC (Async Procedure Call) to another thread. + + - The caller is responsible for making sure he's not calling to the same + thread. + + - The caller should have locked target_thread_mutex. + - The caller is responsible for making sure he's not calling an Apc_target - that is serviced by the same thread it is called from. - psergey-todo: Should waits here be KILLable? (it seems one needs to use thd->enter_cond() calls to be killable) */ @@ -146,7 +146,6 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, bool res= TRUE; *timed_out= FALSE; - pthread_mutex_lock(&LOCK_apc_queue); if (enabled) { /* Create and post the request */ @@ -154,51 +153,48 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, apc_request.func= func; apc_request.func_arg= func_arg; apc_request.processed= FALSE; - (void)pthread_cond_init(&apc_request.COND_request, NULL); - (void)pthread_mutex_init(&apc_request.LOCK_request, MY_MUTEX_INIT_SLOW); - pthread_mutex_lock(&apc_request.LOCK_request); + mysql_cond_init(0 /* do not track in PS */, &apc_request.COND_request, NULL); enqueue_request(&apc_request); apc_request.what="enqueued by make_apc_call"; - pthread_mutex_unlock(&LOCK_apc_queue); struct timespec abstime; const int timeout= timeout_sec; set_timespec(abstime, timeout); - + int wait_res= 0; /* todo: how about processing other errors here? */ while (!apc_request.processed && (wait_res != ETIMEDOUT)) { - wait_res= pthread_cond_timedwait(&apc_request.COND_request, - &apc_request.LOCK_request, &abstime); + /* We own LOCK_thd_data_ptr */ + wait_res= mysql_cond_timedwait(&apc_request.COND_request, + LOCK_thd_data_ptr, &abstime); + // &apc_request.LOCK_request, &abstime); } if (!apc_request.processed) { - /* The wait has timed out. Remove the request from the queue */ + /* + The wait has timed out. Remove the request from the queue (ok to do + because we own LOCK_thd_data_ptr. + */ apc_request.processed= TRUE; - *timed_out= TRUE; - pthread_mutex_unlock(&apc_request.LOCK_request); - //psergey-todo: "Whoa rare event" refers to this part, right? put a comment. - pthread_mutex_lock(&LOCK_apc_queue); dequeue_request(&apc_request); - pthread_mutex_unlock(&LOCK_apc_queue); + *timed_out= TRUE; res= TRUE; } else { /* Request was successfully executed and dequeued by the target thread */ - pthread_mutex_unlock(&apc_request.LOCK_request); res= FALSE; } + mysql_mutex_unlock(LOCK_thd_data_ptr); /* Destroy all APC request data */ - pthread_mutex_destroy(&apc_request.LOCK_request); - pthread_cond_destroy(&apc_request.COND_request); + mysql_cond_destroy(&apc_request.COND_request); } else { - pthread_mutex_unlock(&LOCK_apc_queue); + mysql_mutex_unlock(LOCK_thd_data_ptr); } return res; } @@ -211,58 +207,37 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, void Apc_target::process_apc_requests() { + if (!get_first_in_queue()) + return; + while (1) { Call_request *request; - - pthread_mutex_lock(&LOCK_apc_queue); + + mysql_mutex_lock(LOCK_thd_data_ptr); if (!(request= get_first_in_queue())) { - pthread_mutex_unlock(&LOCK_apc_queue); + /* No requests in the queue */ + mysql_mutex_unlock(LOCK_thd_data_ptr); break; } - request->what="seen by process_apc_requests"; - pthread_mutex_lock(&request->LOCK_request); - - if (request->processed) - { - /* - We can get here when - - the requestor thread has been waiting for this request - - the wait has timed out - - it has set request->done=TRUE - - it has released LOCK_request, because its next action - will be to remove the request from the queue, however, - it could not attempt to lock the queue while holding the lock on - request, because that would deadlock with this function - (we here first lock the queue and then lock the request) - */ - pthread_mutex_unlock(&request->LOCK_request); - pthread_mutex_unlock(&LOCK_apc_queue); - fprintf(stderr, "Whoa rare event #1!\n"); - continue; - } /* - Remove the request from the queue (we're holding its lock so we can be + Remove the request from the queue (we're holding queue lock so we can be sure that request owner won't try to remove it) */ request->what="dequeued by process_apc_requests"; dequeue_request(request); request->processed= TRUE; - pthread_mutex_unlock(&LOCK_apc_queue); - request->func(request->func_arg); request->what="func called by process_apc_requests"; #ifndef DBUG_OFF n_calls_processed++; #endif - - pthread_cond_signal(&request->COND_request); - - pthread_mutex_unlock(&request->LOCK_request); + mysql_cond_signal(&request->COND_request); + mysql_mutex_unlock(LOCK_thd_data_ptr); } } @@ -280,6 +255,7 @@ volatile int apcs_missed=0; volatile int apcs_timed_out=0; Apc_target apc_target; +mysql_mutex_t target_mutex; int int_rand(int size) { @@ -290,7 +266,8 @@ int int_rand(int size) void *test_apc_service_thread(void *ptr) { my_thread_init(); - apc_target.init(); + mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST); + apc_target.init(&target_mutex); apc_target.enable(); started= TRUE; fprintf(stderr, "# test_apc_service_thread started\n"); diff --git a/sql/my_apc.h b/sql/my_apc.h index 0698703ad40..8e6980fa855 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -22,15 +22,16 @@ */ /* - Target for asynchronous calls. + Target for asynchronous procedue calls (APCs). */ class Apc_target { + mysql_mutex_t *LOCK_thd_data_ptr; public: Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {} ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);} - void init(); + void init(mysql_mutex_t *target_mutex); void destroy(); void enable(); void disable(); @@ -68,14 +69,31 @@ private: /* Circular, double-linked list of all enqueued call requests. We use this structure, because we - - process requests sequentially + - process requests sequentially (i.e. they are removed from the front) - a thread that has posted a request may time out (or be KILLed) and - cancel the request, which means we'll need to remove its request at - arbitrary point in time. + cancel the request, which means we need a fast request-removal + operation. */ Call_request *apc_calls; + - pthread_mutex_t LOCK_apc_queue; + /* + This mutex is used to + - make queue put/remove operations atomic (one must be in posession of the + mutex when putting/removing something from the queue) + + - make sure that nobody enqueues a request onto an Apc_target which has + disabled==TRUE. The idea is: + = requestor must be in possession of the mutex and check that + disabled==FALSE when he is putting his request into the queue. + = When the owner (ie. service) thread changes the Apc_target from + enabled to disabled, it will acquire the mutex, disable the + Apc_target (preventing any new requests), and then serve all pending + requests. + That way, we will never have the situation where the Apc_target is + disabled, but there are some un-served requests. + */ + //pthread_mutex_t LOCK_apc_queue; class Call_request { @@ -84,13 +102,16 @@ private: void *func_arg; /* Argument to pass it */ bool processed; - pthread_mutex_t LOCK_request; - pthread_cond_t COND_request; + //pthread_mutex_t LOCK_request; + //pthread_cond_t COND_request; + + /* Condition that will be signalled when the request has been served */ + mysql_cond_t COND_request; Call_request *next; Call_request *prev; - const char *what; /* State of the request */ + const char *what; /* (debug) state of the request */ }; void enqueue_request(Call_request *qe); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 2ad4e77405d..f477c0fe0ec 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1196,7 +1196,7 @@ void THD::init(void) /* Initialize the Debug Sync Facility. See debug_sync.cc. */ debug_sync_init_thread(this); #endif /* defined(ENABLED_DEBUG_SYNC) */ - apc_target.init(); + apc_target.init(&LOCK_thd_data); } @@ -1372,6 +1372,8 @@ THD::~THD() { THD_CHECK_SENTRY(this); DBUG_ENTER("~THD()"); + //psergey-todo: assert that the queue is disabled and empty. + /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); mysys_var=0; // Safety (shouldn't be needed) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 87f46abb23c..5079e82fa40 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2063,7 +2063,8 @@ void mysqld_show_explain(THD *thd, ulong thread_id) explain_req.target_thd= tmp; explain_req.request_thd= thd; explain_req.failed_to_produce= FALSE; - + + /* Ok, we have a lock on target->LOCK_thd_data, can call: */ bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data, (void*)&explain_req, timeout_sec, &timed_out); @@ -2090,7 +2091,7 @@ void mysqld_show_explain(THD *thd, ulong thread_id) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_YES, explain_req.query_str.c_ptr_safe()); } - mysql_mutex_unlock(&tmp->LOCK_thd_data); + //mysql_mutex_unlock(&tmp->LOCK_thd_data); if (!bres) { explain_buf->flush_data(); From 5eecea8cafff69d9a3e3816d860fd9018af013b3 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 7 Jun 2012 19:55:22 +0400 Subject: [PATCH 32/67] MDEV-324: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN for a query with TEMPTABLE view loses 'DERIVED' line - Make SHOW EXPLAIN code take into account that st_select_lex object without joins can be a full-featured SELECTs which were already executed and cleaned up. --- mysql-test/r/show_explain.result | 59 ++++++++++++++++++-------------- mysql-test/t/show_explain.test | 35 ++++++++++++++++++- sql/sql_class.cc | 24 +++++++++++-- sql/sql_class.h | 2 ++ sql/sql_lex.cc | 22 ++++++++---- sql/sql_lex.h | 6 ++-- sql/sql_select.cc | 9 +++++ sql/sql_show.cc | 1 + 8 files changed, 121 insertions(+), 37 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index d6c46c81c79..aa473eafd9b 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -153,10 +153,7 @@ 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; -id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted -Warnings: -Note 1003 select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1 +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command a (select max(a) from t0 b where b.a+a.a<10) 0 9 # Try to do SHOW EXPLAIN for a query that runs a SET command: @@ -308,12 +305,10 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t2 ALL NULL NULL NULL NULL 5 Using join buffer (flat, BNL join) set debug_dbug='d,show_explain_in_find_all_keys'; SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; -# NOTE: current code will not show "Using join buffer": +# FIXED by "conservative assumptions about when QEP is available" fix: +# NOTE: current code will not show "Using join buffer": show explain for $thr2; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Query plan already deleted -Warnings: -Note 1003 SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command a 1 2 @@ -366,10 +361,7 @@ set @show_explain_probe_select_id=2; set debug_dbug='d,show_explain_probe_join_exec_end'; SELECT * FROM v1, t2; show explain for $thr2; -id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Not yet optimized -Warnings: -Note 1003 SELECT * FROM v1, t2 +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command a b 8 4 8 5 @@ -401,10 +393,7 @@ 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; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Query plan already deleted -Warnings: -Note 1003 select * from t0 where 1>10 +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command a set debug_dbug=''; # @@ -416,10 +405,7 @@ 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; -id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Query plan already deleted -Warnings: -Note 1003 select * from t0,t3 where t3.a=112233 +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command a a set debug_dbug=''; drop table t3; @@ -524,10 +510,7 @@ 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; -id select_type table type possible_keys key key_len ref rows Extra -1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted -Warnings: -Note 1003 SELECT * FROM t2 WHERE (5, 78) IN (SELECT `a1`, MAX(`a1`) FROM t2 GROUP BY `a1`) +ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command pk a1 set debug_dbug=''; DROP TABLE t2; @@ -616,4 +599,30 @@ t1 1 b 1 b A NULL NULL NULL YES BTREE t1 1 c 1 c A NULL NULL NULL YES BTREE set debug_dbug=''; DROP TABLE t1; +# +# MDEV-324: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN for a query with TEMPTABLE view +# loses 'DERIVED' line on the way without saying that the plan was already deleted +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t1; +EXPLAIN SELECT a + 1 FROM v1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY ALL NULL NULL NULL NULL 2 +2 DERIVED t1 ALL NULL NULL NULL NULL 2 +set debug_dbug='d,show_explain_probe_join_tab_preread'; +set @show_explain_probe_select_id=1; +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 +Warnings: +Note 1003 SELECT a + 1 FROM v1 +a + 1 +2 +3 +set debug_dbug=''; +DROP VIEW v1; +DROP TABLE t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 5605e304bcc..8f6b6301671 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -191,6 +191,7 @@ 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_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -325,7 +326,9 @@ send SELECT alias.a FROM t2, ( SELECT * FROM t2 ) AS alias GROUP BY alias.a; connection default; --source include/wait_condition.inc ---echo # NOTE: current code will not show "Using join buffer": +--echo # FIXED by "conservative assumptions about when QEP is available" fix: +--echo # NOTE: current code will not show "Using join buffer": +--error ER_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -374,6 +377,7 @@ send SELECT * FROM v1, t2; connection default; --source include/wait_condition.inc +--error ER_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -403,6 +407,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_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -418,6 +423,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_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -513,6 +519,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_ERROR_WHEN_EXECUTING_COMMAND evalp show explain for $thr2; connection con1; reap; @@ -601,6 +608,32 @@ set debug_dbug=''; DROP TABLE t1; +--echo # +--echo # MDEV-324: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN for a query with TEMPTABLE view +--echo # loses 'DERIVED' line on the way without saying that the plan was already deleted +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM t1; + +EXPLAIN SELECT a + 1 FROM v1; + +set debug_dbug='d,show_explain_probe_join_tab_preread'; +set @show_explain_probe_select_id=1; + +send + SELECT a + 1 FROM v1; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; +set debug_dbug=''; + +DROP VIEW v1; +DROP TABLE t1; + + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_class.cc b/sql/sql_class.cc index f477c0fe0ec..65ead744ddb 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2356,13 +2356,14 @@ int select_result_explain_buffer::send_data(List &items) } +/* Write all strings out to the output, and free them. */ + void select_result_explain_buffer::flush_data() { List_iterator it(data_rows); String *str; while ((str= it++)) { - /* TODO: write out the lines. */ protocol->set_packet(str->ptr(), str->length()); protocol->write(); delete str; @@ -2370,6 +2371,20 @@ void select_result_explain_buffer::flush_data() data_rows.empty(); } + +/* Just free all of the accumulated strings */ + +void select_result_explain_buffer::discard_data() +{ + List_iterator it(data_rows); + String *str; + while ((str= it++)) + { + delete str; + } + data_rows.empty(); +} + ////////////////////////////////////////////////////////////////////////////// @@ -3288,6 +3303,7 @@ void Show_explain_request::get_explain_data(void *arg) // Actually, change the ARENA, because we're going to allocate items! Query_arena backup_arena; THD *target_thd= req->target_thd; + bool printed_anything= FALSE; target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, &backup_arena); @@ -3296,7 +3312,11 @@ void Show_explain_request::get_explain_data(void *arg) target_thd->query_length(), &my_charset_bin); - if (target_thd->lex->unit.print_explain(req->explain_buf, 0 /* explain flags */)) + if (target_thd->lex->unit.print_explain(req->explain_buf, 0 /* explain flags*/, + &printed_anything)) + req->failed_to_produce= TRUE; + + if (!printed_anything) req->failed_to_produce= TRUE; target_thd->restore_active_arena((Query_arena*)req->request_thd, diff --git a/sql/sql_class.h b/sql/sql_class.h index 6be477605f1..0d58bb413f0 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3328,6 +3328,8 @@ public: /* this will be called in the parent thread: */ void flush_data(); + void discard_data(); + List data_rows; }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index df0fcd24249..99320d88702 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4074,7 +4074,8 @@ int print_explain_message_line(select_result_sink *result, int st_select_lex::print_explain(select_result_sink *output, - uint8 explain_flags) + uint8 explain_flags, + bool *printed_anything) { int res; if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) @@ -4083,6 +4084,7 @@ int st_select_lex::print_explain(select_result_sink *output, 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 */ @@ -4112,7 +4114,8 @@ int st_select_lex::print_explain(select_result_sink *output, */ if (!(unit->item && unit->item->eliminated)) { - unit->print_explain(output, explain_flags); + if ((res= unit->print_explain(output, explain_flags, printed_anything))) + goto err; } } } @@ -4120,7 +4123,9 @@ int st_select_lex::print_explain(select_result_sink *output, { const char *msg; if (!join) - DBUG_ASSERT(0); // psergey: TODO: can this happen or not? + 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 @@ -4132,12 +4137,12 @@ int st_select_lex::print_explain(select_result_sink *output, 0, msg); } err: - return 0; + return res; } int st_select_lex_unit::print_explain(select_result_sink *output, - uint8 explain_flags) + uint8 explain_flags, bool *printed_anything) { int res= 0; SELECT_LEX *first= first_select(); @@ -4148,12 +4153,15 @@ int st_select_lex_unit::print_explain(select_result_sink *output, If there is only one child, 'first', and it has join==NULL, emit "not in EXPLAIN state" error. */ - return 1; + const char *msg="Query plan already deleted"; + res= print_explain_message_line(output, first, TRUE /* on_the_fly */, + 0, msg); + return 0; } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - if ((res= sl->print_explain(output, explain_flags))) + if ((res= sl->print_explain(output, explain_flags, printed_anything))) break; } diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 911a6b766f6..eb908089479 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -718,7 +718,8 @@ public: friend int subselect_union_engine::exec(); List *get_unit_column_types(); - int print_explain(select_result_sink *output, uint8 explain_flags); + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -1039,7 +1040,8 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); - int print_explain(select_result_sink *output, uint8 explain_flags); + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); /* 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 aba5c0761c8..5aa69f54fa3 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10365,9 +10365,18 @@ bool JOIN_TAB::preread_init() mysql_handle_single_derived(join->thd->lex, derived, DT_CREATE | DT_FILL)) return TRUE; + preread_init_done= TRUE; if (select && select->quick) select->quick->replace_handler(table->file); + + DBUG_EXECUTE_IF("show_explain_probe_join_tab_preread", + if (dbug_user_var_equals_int(join->thd, + "show_explain_probe_select_id", + join->select_lex->select_number)) + dbug_serve_apcs(join->thd, 1); + ); + return FALSE; } diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 5079e82fa40..9f0adf4a608 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2085,6 +2085,7 @@ void mysqld_show_explain(THD *thd, ulong thread_id) "Target is not running EXPLAINable command"); } bres= TRUE; + explain_buf->discard_data(); } else { From a161c19bf25145f70c77561c38b99148deb84481 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 7 Jun 2012 20:03:36 +0400 Subject: [PATCH 33/67] Fix test results to deal with rounding error in #rows in EXPLAIN output. --- mysql-test/include/index_merge2.inc | 1 + mysql-test/r/index_merge_innodb.result | 2 +- mysql-test/r/index_merge_myisam.result | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mysql-test/include/index_merge2.inc b/mysql-test/include/index_merge2.inc index 1d6b82e1787..99af143a4e0 100644 --- a/mysql-test/include/index_merge2.inc +++ b/mysql-test/include/index_merge2.inc @@ -343,6 +343,7 @@ alter table t1 add index i3(key3); update t1 set key2=key1,key3=key1; # to test the bug, the following must use "sort_union": +--replace_column 9 ROWS explain select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); drop table t1; diff --git a/mysql-test/r/index_merge_innodb.result b/mysql-test/r/index_merge_innodb.result index b00a949fcbc..7a8a8db517e 100644 --- a/mysql-test/r/index_merge_innodb.result +++ b/mysql-test/r/index_merge_innodb.result @@ -313,7 +313,7 @@ alter table t1 add index i3(key3); update t1 set key2=key1,key3=key1; explain select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index_merge i2,i3 i3,i2 4,4 NULL 8 Using sort_union(i3,i2); Using where +1 SIMPLE t1 index_merge i2,i3 i3,i2 4,4 NULL ROWS Using sort_union(i3,i2); Using where select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); key1 key2 key3 31 31 31 diff --git a/mysql-test/r/index_merge_myisam.result b/mysql-test/r/index_merge_myisam.result index b560c1e5176..8bb3f95934d 100644 --- a/mysql-test/r/index_merge_myisam.result +++ b/mysql-test/r/index_merge_myisam.result @@ -1146,7 +1146,7 @@ alter table t1 add index i3(key3); update t1 set key2=key1,key3=key1; explain select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index_merge i2,i3 i3,i2 4,4 NULL 11 Using sort_union(i3,i2); Using where +1 SIMPLE t1 index_merge i2,i3 i3,i2 4,4 NULL ROWS Using sort_union(i3,i2); Using where select * from t1 where (key3 > 30 and key3<35) or (key2 >32 and key2 < 40); key1 key2 key3 31 31 31 From 950dc8022e64339abc93adc6953661cf38afa530 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 7 Jun 2012 21:19:22 +0400 Subject: [PATCH 34/67] MDEV-323: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN loses 'UNION RESULT' line - Make SHOW EXPLAIN code correctly print fake_select_lex: both in the case where it has not yet been executed, and when it has been executed. --- mysql-test/r/show_explain.result | 34 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 27 +++++++++++++++++++++++++ sql/sql_lex.cc | 6 ++---- sql/sql_union.cc | 2 ++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index aa473eafd9b..14569b5c14b 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -625,4 +625,38 @@ a + 1 set debug_dbug=''; DROP VIEW v1; DROP TABLE t1; +# +# MDEV-323: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN loses +# 'UNION RESULT' line on the way without saying that the plan was already deleted +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (4),(6); +EXPLAIN +SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +set debug_dbug='d,show_explain_probe_union_read'; +SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ); +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +Warnings: +Note 1003 SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ) +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +Warnings: +Note 1003 SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ) +a +set debug_dbug=''; +DROP TABLE t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 8f6b6301671..1c890262758 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -634,6 +634,33 @@ DROP VIEW v1; DROP TABLE t1; +--echo # +--echo # MDEV-323: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN loses +--echo # 'UNION RESULT' line on the way without saying that the plan was already deleted +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (4),(6); + +EXPLAIN +SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ); + +set debug_dbug='d,show_explain_probe_union_read'; +send +SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ); + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +DROP TABLE t1; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 99320d88702..fafa100396d 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4165,10 +4165,8 @@ int st_select_lex_unit::print_explain(select_result_sink *output, break; } - /* - Note: it could be that fake_select_lex->join == NULL still at this point - */ - if (fake_select_lex && !fake_select_lex->join) + /* 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); diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 021267b53bd..b0e4239e6cd 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -720,6 +720,8 @@ bool st_select_lex_unit::exec() } } + DBUG_EXECUTE_IF("show_explain_probe_union_read", + dbug_serve_apcs(thd, 1);); /* Send result to 'result' */ saved_error= TRUE; { From cf8461a0f75eebcc15cddd3c48d5e24f872930c2 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 8 Jun 2012 02:19:36 +0400 Subject: [PATCH 35/67] SHOW EXPLAIN: Code cleanup --- sql/filesort.cc | 3 --- sql/item_subselect.cc | 1 - sql/my_apc.h | 3 +-- sql/opt_subselect.cc | 2 +- sql/sql_class.cc | 13 +++++++------ sql/sql_select.cc | 6 +----- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/sql/filesort.cc b/sql/filesort.cc index 5220d80767d..6d1cabb02b7 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -502,7 +502,6 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, my_off_t record; TABLE *sort_form; THD *thd= current_thd; - //volatile killed_state *killed= &thd->killed; handler *file; MY_BITMAP *save_read_set, *save_write_set, *save_vcol_set; uchar *next_sort_key= sort_keys_buf; @@ -1236,9 +1235,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, void *first_cmp_arg; element_count dupl_count= 0; uchar *src; - /* killed_state not_killable; */ uchar *unique_buff= param->unique_buff; - /* volatile killed_state *killed= ¤t_thd->killed; */ const bool killable= !param->not_killable; THD* const thd=current_thd; DBUG_ENTER("merge_buffers"); diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 7b1e4fc8b27..276f35fe301 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -4725,7 +4725,6 @@ int subselect_hash_sj_engine::exec() thd->lex->current_select= materialize_engine->select_lex; /* The subquery should be optimized, and materialized only once. */ DBUG_ASSERT(materialize_join->optimized && !is_materialized); - materialize_join->exec(); if ((res= test(materialize_join->error || thd->is_fatal_error || thd->is_error()))) diff --git a/sql/my_apc.h b/sql/my_apc.h index 8e6980fa855..5de1cf7d8d3 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -53,8 +53,7 @@ public: int timeout_sec, bool *timed_out); #ifndef DBUG_OFF - int n_calls_processed; - //int call_queue_size; + int n_calls_processed; /* Number of calls served by this target */ #endif private: class Call_request; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 34e73f5cac8..dfd0785cc4a 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -1656,7 +1656,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) while ((ifm= li++)) parent_lex->ftfunc_list->push_front(ifm); } - + parent_lex->have_merged_subqueries= TRUE; DBUG_RETURN(FALSE); } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 65ead744ddb..ba9140fc9bc 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -1361,7 +1361,7 @@ void THD::cleanup(void) mysql_mutex_unlock(&LOCK_user_locks); ull= NULL; } - + apc_target.destroy(); cleanup_done=1; DBUG_VOID_RETURN; @@ -1372,8 +1372,6 @@ THD::~THD() { THD_CHECK_SENTRY(this); DBUG_ENTER("~THD()"); - //psergey-todo: assert that the queue is disabled and empty. - /* Ensure that no one is using THD */ mysql_mutex_lock(&LOCK_thd_data); mysys_var=0; // Safety (shouldn't be needed) @@ -2311,8 +2309,8 @@ int select_send::send_data(List &items) DBUG_RETURN(0); } - ////////////////////////////////////////////////////////////////////////////// + int select_result_explain_buffer::send_data(List &items) { List_iterator_fast li(items); @@ -2336,13 +2334,16 @@ int select_result_explain_buffer::send_data(List &items) */ buffer.set(buff, sizeof(buff), &my_charset_bin); } - //TODO: do we need the following: + if (thd->is_error()) { protocol->remove_last_row(); DBUG_RETURN(1); } - /* psergey-TODO: instead of protocol->write(), steal the packet here */ + /* + Instead of calling protocol->write(), steal the packed and put it to our + buffer + */ const char *packet_data; size_t len; protocol->get_packet(&packet_data, &len); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5aa69f54fa3..e1af5605d37 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2228,7 +2228,7 @@ void JOIN::exec_inner() List *columns_list= &fields_list; int tmp_error; DBUG_ENTER("JOIN::exec"); - + thd_proc_info(thd, "executing"); error= 0; if (procedure) @@ -2526,16 +2526,12 @@ void JOIN::exec_inner() /* Free first data from old join */ - fprintf(stderr,"Q: %s\n", thd->query()); - //DBUG_ASSERT(0); /* psergey-todo: this is the place of pre-mature JOIN::free call. */ curr_join->join_free(); - //psergey-todo: SHOW EXPLAIN probe here if (curr_join->make_simple_join(this, curr_tmp_table)) DBUG_VOID_RETURN; - //psergey-todo: SHOW EXPLAIN probe here calc_group_buffer(curr_join, group_list); count_field_types(select_lex, &curr_join->tmp_table_param, curr_join->tmp_all_fields1, From 1ce0c706b39018c09994ef133fdeeb93d750903c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 19 Jun 2012 13:53:16 +0400 Subject: [PATCH 36/67] MDEV-327: SHOW EXPLAIN: Different select_type in plans produced by SHOW EXPLAIN and EXPLAIN - SHOW EXPLAIN actually produced correct plan - Apply fix for lp:1013343 to make EXPLAIN and SHOW EXPLAIN uniform. --- mysql-test/r/show_explain.result | 36 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 35 +++++++++++++++++++++++++++++++ sql/item_subselect.cc | 2 +- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 14569b5c14b..1c18112a7e5 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -659,4 +659,40 @@ Note 1003 SELECT a FROM t1 WHERE a IN ( SELECT 1+SLEEP(0.01) UNION SELECT 2 ) a set debug_dbug=''; DROP TABLE t1; +# +# MDEV-327: SHOW EXPLAIN: Different select_type in plans produced by SHOW EXPLAIN +# and standard EXPLAIN: 'SUBQUERY' vs 'DEPENDENT SUBQUERY' +# +CREATE TABLE t1 (a INT) ENGINE=Aria; +INSERT INTO t1 VALUES +(4),(6),(3),(5),(3),(246),(2),(9),(3),(8), +(1),(8),(8),(5),(7),(5),(1),(6),(2),(9); +CREATE TABLE t2 (b INT) ENGINE=Aria; +INSERT INTO t2 VALUES +(1),(7),(4),(7),(0),(2),(9),(4),(0),(9), +(1),(3),(8),(8),(18),(84),(6),(3),(6),(6); +EXPLAIN +SELECT * FROM t1, ( SELECT * FROM t2 ) AS alias +WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 20 Using where +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 +3 SUBQUERY t1 ALL NULL NULL NULL NULL 20 +3 SUBQUERY t2 ALL NULL NULL NULL NULL 20 Using where +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SELECT * FROM t1, ( SELECT * FROM t2 ) AS alias +WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ); +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 20 Using where +1 PRIMARY t2 ALL NULL NULL NULL NULL 20 +3 SUBQUERY t1 ALL NULL NULL NULL NULL 20 +3 SUBQUERY t2 ALL NULL NULL NULL NULL 20 Using where +Warnings: +Note 1003 SELECT * FROM t1, ( SELECT * FROM t2 ) AS alias +WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ) +a b +set debug_dbug=''; +DROP TABLE t1, t2; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 1c890262758..d7e5a0bc6fd 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -662,6 +662,41 @@ set debug_dbug=''; DROP TABLE t1; +--echo # +--echo # MDEV-327: SHOW EXPLAIN: Different select_type in plans produced by SHOW EXPLAIN +--echo # and standard EXPLAIN: 'SUBQUERY' vs 'DEPENDENT SUBQUERY' +--echo # +CREATE TABLE t1 (a INT) ENGINE=Aria; +INSERT INTO t1 VALUES +(4),(6),(3),(5),(3),(246),(2),(9),(3),(8), +(1),(8),(8),(5),(7),(5),(1),(6),(2),(9); + +CREATE TABLE t2 (b INT) ENGINE=Aria; +INSERT INTO t2 VALUES +(1),(7),(4),(7),(0),(2),(9),(4),(0),(9), +(1),(3),(8),(8),(18),(84),(6),(3),(6),(6); + +EXPLAIN +SELECT * FROM t1, ( SELECT * FROM t2 ) AS alias +WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ); + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +--send +SELECT * FROM t1, ( SELECT * FROM t2 ) AS alias +WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ); + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +DROP TABLE t1, t2; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 5458a2fb968..d94e395d356 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1813,7 +1813,7 @@ bool Item_allany_subselect::is_maxmin_applicable(JOIN *join) WHERE condition. */ return (abort_on_null || (upper_item && upper_item->is_top_level_item())) && - !join->select_lex->master_unit()->uncacheable && !func->eqne_op(); + !(join->select_lex->master_unit()->uncacheable & ~UNCACHEABLE_EXPLAIN) && !func->eqne_op(); } From 6eb2ce58635dded450953bf18123fbd7d9dbfaea Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 19 Jun 2012 18:10:32 +0400 Subject: [PATCH 37/67] SHOW EXPLAIN: better comments --- sql/my_apc.h | 27 ++++----------------------- sql/sql_class.cc | 8 ++++++-- sql/sql_class.h | 29 +++++++++++++++++++++-------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/sql/my_apc.h b/sql/my_apc.h index 5de1cf7d8d3..5879a0070d6 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -75,38 +75,19 @@ private: */ Call_request *apc_calls; - - /* - This mutex is used to - - make queue put/remove operations atomic (one must be in posession of the - mutex when putting/removing something from the queue) - - - make sure that nobody enqueues a request onto an Apc_target which has - disabled==TRUE. The idea is: - = requestor must be in possession of the mutex and check that - disabled==FALSE when he is putting his request into the queue. - = When the owner (ie. service) thread changes the Apc_target from - enabled to disabled, it will acquire the mutex, disable the - Apc_target (preventing any new requests), and then serve all pending - requests. - That way, we will never have the situation where the Apc_target is - disabled, but there are some un-served requests. - */ - //pthread_mutex_t LOCK_apc_queue; - class Call_request { public: apc_func_t func; /* Function to call */ void *func_arg; /* Argument to pass it */ - bool processed; - //pthread_mutex_t LOCK_request; - //pthread_cond_t COND_request; + /* The caller will actually wait for "processed==TRUE" */ + bool processed; /* Condition that will be signalled when the request has been served */ mysql_cond_t COND_request; - + + /* Double linked-list linkage */ Call_request *next; Call_request *prev; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 040712fb42a..d694afebc2c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2311,6 +2311,10 @@ int select_send::send_data(List &items) ////////////////////////////////////////////////////////////////////////////// +/* + Save the data being sent in our internal buffer. +*/ + int select_result_explain_buffer::send_data(List &items) { List_iterator_fast li(items); @@ -2357,7 +2361,7 @@ int select_result_explain_buffer::send_data(List &items) } -/* Write all strings out to the output, and free them. */ +/* Write the saved resultset to the client (via this->protocol) and free it. */ void select_result_explain_buffer::flush_data() { @@ -2373,7 +2377,7 @@ void select_result_explain_buffer::flush_data() } -/* Just free all of the accumulated strings */ +/* Free the accumulated resultset */ void select_result_explain_buffer::discard_data() { diff --git a/sql/sql_class.h b/sql/sql_class.h index 345216dcdea..bfbc9e86611 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1522,16 +1522,29 @@ extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); class select_result_explain_buffer; + +/* + SHOW EXPLAIN request object. + + The thread that runs SHOW EXPLAIN statement creates a Show_explain_request + object R, and then schedules APC call of + Show_explain_request::get_explain_data((void*)&R). + +*/ + class Show_explain_request { public: - THD *target_thd; - THD *request_thd; + THD *target_thd; /* thd that we're running SHOW EXPLAIN for */ + THD *request_thd; /* thd that run SHOW EXPLAIN command */ + /* If true, there was some error when producing EXPLAIN output. */ bool failed_to_produce; - + + /* SHOW EXPLAIN will be stored here */ select_result_explain_buffer *explain_buf; - + + /* Query that we've got SHOW EXPLAIN for */ String query_str; static void get_explain_data(void *arg); @@ -2414,11 +2427,11 @@ public: /* - This is what allows this thread to serve as a target for others to - schedule Async Procedure Calls on. + Allows this thread to serve as a target for others to schedule Async + Procedure Calls on. - It's possible to schedule arbitrary C function call but currently this - facility is used only by SHOW EXPLAIN code (See Show_explain_request) + It's possible to schedule arbitrary C++ function calls. Currently, only + Show_explain_request uses this. */ Apc_target apc_target; From 45503698c13c55a6c21e02f4422bd3a4bcb02fd9 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 21 Jun 2012 22:15:13 +0400 Subject: [PATCH 38/67] Test that SHOW EXPLAIN will print 'Distinct'. --- mysql-test/r/show_explain.result | 33 +++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 34 ++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 1c18112a7e5..07bcb29a7ea 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -695,4 +695,37 @@ WHERE a < ALL ( SELECT b FROM t1, t2 WHERE a = b ) a b set debug_dbug=''; DROP TABLE t1, t2; +# +# Test that SHOW EXPLAIN will print 'Distinct'. +# +CREATE TABLE t1 (a int(10) unsigned not null primary key,b int(10) unsigned); +INSERT INTO t1 VALUES (1,1),(2,1),(3,1),(4,1); +CREATE TABLE t3 (a int(10) unsigned, key(A), b text); +INSERT INTO t3 VALUES (1,'1'),(2,'2'); +create temporary table t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +explain select distinct t1.a from t1,t3 where t1.a=t3.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 4 Using index; Using temporary +1 SIMPLE t3 ref a a 5 test.t1.a 7 Using index; Distinct +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +select distinct t1.a from t1,t3 where t1.a=t3.a; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index PRIMARY PRIMARY 4 NULL 4 Using index; Using temporary +1 SIMPLE t3 ref a a 5 test.t1.a 7 Using index; Distinct +Warnings: +Note 1003 select distinct t1.a from t1,t3 where t1.a=t3.a +a +1 +2 +set debug_dbug=''; +drop table t1,t3,t4; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index d7e5a0bc6fd..ffd2c25dfd9 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -692,11 +692,41 @@ evalp show explain for $thr2; connection con1; reap; - set debug_dbug=''; - DROP TABLE t1, t2; +--echo # +--echo # Test that SHOW EXPLAIN will print 'Distinct'. +--echo # +CREATE TABLE t1 (a int(10) unsigned not null primary key,b int(10) unsigned); +INSERT INTO t1 VALUES (1,1),(2,1),(3,1),(4,1); + +CREATE TABLE t3 (a int(10) unsigned, key(A), b text); +INSERT INTO t3 VALUES (1,'1'),(2,'2'); + +create temporary table t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +insert into t4 select * from t3; +insert into t3 select * from t4; +explain select distinct t1.a from t1,t3 where t1.a=t3.a; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +--send +select distinct t1.a from t1,t3 where t1.a=t3.a; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; +set debug_dbug=''; + +drop table t1,t3,t4; ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. From 66c62de1034e780144e6945b3a2150253e2a652c Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 25 Jun 2012 18:39:26 +0400 Subject: [PATCH 39/67] MWL#182: Explain running statements - Remove out-of-date comments, add dbug assertions. --- sql/filesort.cc | 5 ----- sql/my_apc.cc | 5 ++--- sql/my_apc.h | 2 +- sql/sql_class.cc | 6 ++++-- sql/sql_select.cc | 7 +------ sql/sql_show.cc | 1 - 6 files changed, 8 insertions(+), 18 deletions(-) diff --git a/sql/filesort.cc b/sql/filesort.cc index 6d1cabb02b7..f9f7288c93f 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -1242,11 +1242,6 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file, status_var_increment(thd->status_var.filesort_merge_passes); thd->query_plan_fsort_passes++; - /*if (param->not_killable) - { - killed= ¬_killable; - not_killable= NOT_KILLED; - }*/ error=0; rec_length= param->rec_length; diff --git a/sql/my_apc.cc b/sql/my_apc.cc index d5e3eb080a2..eae2843edb4 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -86,7 +86,7 @@ void Apc_target::disable() void Apc_target::enqueue_request(Call_request *qe) { - //call_queue_size++; + mysql_mutex_assert_owner(LOCK_thd_data_ptr); if (apc_calls) { Call_request *after= apc_calls->prev; @@ -112,12 +112,11 @@ void Apc_target::enqueue_request(Call_request *qe) void Apc_target::dequeue_request(Call_request *qe) { - //call_queue_size--; + mysql_mutex_assert_owner(LOCK_thd_data_ptr); if (apc_calls == qe) { if ((apc_calls= apc_calls->next) == qe) { - //DBUG_ASSERT(!call_queue_size); apc_calls= NULL; } } diff --git a/sql/my_apc.h b/sql/my_apc.h index 5879a0070d6..450e07b92a4 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -28,7 +28,7 @@ class Apc_target { mysql_mutex_t *LOCK_thd_data_ptr; public: - Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {} + Apc_target() : enabled(0), apc_calls(NULL) {} ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);} void init(mysql_mutex_t *target_mutex); diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d694afebc2c..5408b10c6b1 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3333,12 +3333,14 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup) void Show_explain_request::get_explain_data(void *arg) { Show_explain_request *req= (Show_explain_request*)arg; - //TODO: change mem_root to point to request_thd->mem_root. - // Actually, change the ARENA, because we're going to allocate items! Query_arena backup_arena; THD *target_thd= req->target_thd; bool printed_anything= FALSE; + /* + Change the arena because JOIN::print_explain and co. are going to allocate + items. Let them allocate them on our arena. + */ target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, &backup_arena); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 60530670ac9..c0e765b7991 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10321,12 +10321,7 @@ void JOIN_TAB::cleanup() if (cache) { cache->free(); - cache= 0; // psergey: this is why we don't see "Using join cache" in SHOW EXPLAIN - // when it is run for "Using temporary+filesort" queries while they - // are at reading-from-tmp-table phase. - // - // TODO ask igor if this can be just moved to later phase - // (JOIN_CACHE objects themselves are not big, arent they) + cache= 0; } limit= 0; if (table) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 9f0adf4a608..0671ebae199 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2092,7 +2092,6 @@ void mysqld_show_explain(THD *thd, ulong thread_id) push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_YES, explain_req.query_str.c_ptr_safe()); } - //mysql_mutex_unlock(&tmp->LOCK_thd_data); if (!bres) { explain_buf->flush_data(); From c62c0c551684d063e51024579a4956f66048bbf5 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 28 Jun 2012 13:58:37 +0400 Subject: [PATCH 40/67] MWL#182: Explain running statements: address review feedback - Add Monty Program Ab copyright in new files - Change Apc_target::make_apc_call() to accept a C++-style functor (instead of C-style function + parameter) --- sql/my_apc.cc | 25 ++++++++++++++++++------- sql/my_apc.h | 30 ++++++++++++++++++++++-------- sql/sql_class.cc | 23 +++++++++++------------ sql/sql_class.h | 9 +++++---- sql/sql_show.cc | 4 +--- 5 files changed, 57 insertions(+), 34 deletions(-) diff --git a/sql/my_apc.cc b/sql/my_apc.cc index eae2843edb4..212fbd0f6cb 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -1,6 +1,18 @@ /* - TODO: MP AB Copyright -*/ + Copyright (c) 2009, 2011, 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 MY_APC_STANDALONE @@ -139,8 +151,8 @@ void Apc_target::dequeue_request(Call_request *qe) to use thd->enter_cond() calls to be killable) */ -bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, - int timeout_sec, bool *timed_out) +bool Apc_target::make_apc_call(Apc_call *call, int timeout_sec, + bool *timed_out) { bool res= TRUE; *timed_out= FALSE; @@ -149,8 +161,7 @@ bool Apc_target::make_apc_call(apc_func_t func, void *func_arg, { /* Create and post the request */ Call_request apc_request; - apc_request.func= func; - apc_request.func_arg= func_arg; + apc_request.call= call; apc_request.processed= FALSE; mysql_cond_init(0 /* do not track in PS */, &apc_request.COND_request, NULL); enqueue_request(&apc_request); @@ -229,7 +240,7 @@ void Apc_target::process_apc_requests() dequeue_request(request); request->processed= TRUE; - request->func(request->func_arg); + request->call->call_in_target_thread(); request->what="func called by process_apc_requests"; #ifndef DBUG_OFF diff --git a/sql/my_apc.h b/sql/my_apc.h index 450e07b92a4..3d9a2154af2 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -1,6 +1,18 @@ /* - TODO: MP AB Copyright -*/ + Copyright (c) 2009, 2011, 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 */ /* Interface @@ -38,8 +50,12 @@ public: void process_apc_requests(); - typedef void (*apc_func_t)(void *arg); - + class Apc_call + { + public: + virtual void call_in_target_thread()= 0; + virtual ~Apc_call() {} + }; /* Make an APC call: schedule it for execution and wait until the target thread has executed it. This function must not be called from a thread @@ -49,8 +65,7 @@ public: @retval TRUE - Call wasnt made (either the target is in disabled state or timeout occured) */ - bool make_apc_call(apc_func_t func, void *func_arg, - int timeout_sec, bool *timed_out); + bool make_apc_call(Apc_call *call, int timeout_sec, bool *timed_out); #ifndef DBUG_OFF int n_calls_processed; /* Number of calls served by this target */ @@ -78,8 +93,7 @@ private: class Call_request { public: - apc_func_t func; /* Function to call */ - void *func_arg; /* Argument to pass it */ + Apc_call *call; /* Functor to be called */ /* The caller will actually wait for "processed==TRUE" */ bool processed; diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 5408b10c6b1..35cc28bcae7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3330,33 +3330,32 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup) we're producing EXPLAIN for. */ -void Show_explain_request::get_explain_data(void *arg) +void Show_explain_request::call_in_target_thread() { - Show_explain_request *req= (Show_explain_request*)arg; Query_arena backup_arena; - THD *target_thd= req->target_thd; bool printed_anything= FALSE; /* Change the arena because JOIN::print_explain and co. are going to allocate items. Let them allocate them on our arena. */ - target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd, + target_thd->set_n_backup_active_arena((Query_arena*)request_thd, &backup_arena); - req->query_str.copy(target_thd->query(), - target_thd->query_length(), - &my_charset_bin); + query_str.copy(target_thd->query(), + target_thd->query_length(), + &my_charset_bin); - if (target_thd->lex->unit.print_explain(req->explain_buf, 0 /* explain flags*/, + if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, &printed_anything)) - req->failed_to_produce= TRUE; + { + failed_to_produce= TRUE; + } if (!printed_anything) - req->failed_to_produce= TRUE; + failed_to_produce= TRUE; - target_thd->restore_active_arena((Query_arena*)req->request_thd, - &backup_arena); + target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena); } diff --git a/sql/sql_class.h b/sql/sql_class.h index bfbc9e86611..73123151738 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1528,11 +1528,11 @@ class select_result_explain_buffer; The thread that runs SHOW EXPLAIN statement creates a Show_explain_request object R, and then schedules APC call of - Show_explain_request::get_explain_data((void*)&R). + Show_explain_request::call((void*)&R). */ -class Show_explain_request +class Show_explain_request : public Apc_target::Apc_call { public: THD *target_thd; /* thd that we're running SHOW EXPLAIN for */ @@ -1546,8 +1546,9 @@ public: /* Query that we've got SHOW EXPLAIN for */ String query_str; - - static void get_explain_data(void *arg); + + /* Overloaded virtual function */ + void call_in_target_thread(); }; class THD; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 0671ebae199..145f4fbebcc 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2065,9 +2065,7 @@ void mysqld_show_explain(THD *thd, ulong thread_id) explain_req.failed_to_produce= FALSE; /* Ok, we have a lock on target->LOCK_thd_data, can call: */ - bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data, - (void*)&explain_req, - timeout_sec, &timed_out); + bres= tmp->apc_target.make_apc_call(&explain_req, timeout_sec, &timed_out); if (bres || explain_req.failed_to_produce) { From 84fd4e2542fa6483c20c27127294c83211888e9f Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 28 Jun 2012 16:46:24 +0400 Subject: [PATCH 41/67] MWL#182: Explain running statements: address review feedback - Move standalone tests to a unittest. - Added comments. --- CMakeLists.txt | 1 + sql/my_apc.cc | 158 ++-------------------------- sql/my_apc.h | 15 +-- unittest/sql/CMakeLists.txt | 3 + unittest/sql/my_apc-t.cc | 201 ++++++++++++++++++++++++++++++++++++ 5 files changed, 221 insertions(+), 157 deletions(-) create mode 100644 unittest/sql/CMakeLists.txt create mode 100644 unittest/sql/my_apc-t.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b0c4898785..ec8c602761a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -279,6 +279,7 @@ IF(WITH_UNIT_TESTS) ADD_SUBDIRECTORY(unittest/strings) ADD_SUBDIRECTORY(unittest/examples) ADD_SUBDIRECTORY(unittest/mysys) + ADD_SUBDIRECTORY(unittest/sql) ENDIF() IF(NOT WITHOUT_SERVER) diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 212fbd0f6cb..48d539aed78 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -15,22 +15,13 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#ifdef MY_APC_STANDALONE - -#include -#include -#include - -#include "my_apc.h" - -#else +#ifndef MY_APC_STANDALONE #include "sql_priv.h" #include "sql_class.h" #endif - /* Standalone testing: g++ -c -DMY_APC_STANDALONE -g -I.. -I../include -o my_apc.o my_apc.cc @@ -140,12 +131,19 @@ void Apc_target::dequeue_request(Call_request *qe) /* Make an APC (Async Procedure Call) to another thread. + + @detail + Make an APC call: schedule it for execution and wait until the target + thread has executed it. - - The caller is responsible for making sure he's not calling to the same - thread. + - The caller is responsible for making sure he's not posting request + to the thread he's calling this function from. - - The caller should have locked target_thread_mutex. + - The caller must have locked target_mutex. The function will release it. + @retval FALSE - Ok, the call has been made + @retval TRUE - Call wasnt made (either the target is in disabled state or + timeout occured) psergey-todo: Should waits here be KILLable? (it seems one needs to use thd->enter_cond() calls to be killable) @@ -251,137 +249,3 @@ void Apc_target::process_apc_requests() } } -/***************************************************************************** - * Testing - *****************************************************************************/ -#ifdef MY_APC_STANDALONE - -volatile bool started= FALSE; -volatile bool service_should_exit= FALSE; -volatile bool requestors_should_exit=FALSE; - -volatile int apcs_served= 0; -volatile int apcs_missed=0; -volatile int apcs_timed_out=0; - -Apc_target apc_target; -mysql_mutex_t target_mutex; - -int int_rand(int size) -{ - return round (((double)rand() / RAND_MAX) * size); -} - -/* An APC-serving thread */ -void *test_apc_service_thread(void *ptr) -{ - my_thread_init(); - mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST); - apc_target.init(&target_mutex); - apc_target.enable(); - started= TRUE; - fprintf(stderr, "# test_apc_service_thread started\n"); - while (!service_should_exit) - { - //apc_target.disable(); - usleep(10000); - //apc_target.enable(); - for (int i = 0; i < 10 && !service_should_exit; i++) - { - apc_target.process_apc_requests(); - usleep(int_rand(30)); - } - } - apc_target.disable(); - apc_target.destroy(); - my_thread_end(); - pthread_exit(0); -} - -class Apc_order -{ -public: - int value; // The value - int *where_to; // Where to write it - Apc_order(int a, int *b) : value(a), where_to(b) {} -}; - -void test_apc_func(void *arg) -{ - Apc_order *order=(Apc_order*)arg; - usleep(int_rand(1000)); - *(order->where_to) = order->value; - __sync_fetch_and_add(&apcs_served, 1); -} - -void *test_apc_requestor_thread(void *ptr) -{ - my_thread_init(); - fprintf(stderr, "# test_apc_requestor_thread started\n"); - while (!requestors_should_exit) - { - int dst_value= 0; - int src_value= int_rand(4*1000*100); - /* Create APC to do dst_value= src_value */ - Apc_order apc_order(src_value, &dst_value); - bool timed_out; - - bool res= apc_target.make_apc_call(test_apc_func, (void*)&apc_order, 60, &timed_out); - if (res) - { - if (timed_out) - __sync_fetch_and_add(&apcs_timed_out, 1); - else - __sync_fetch_and_add(&apcs_missed, 1); - - if (dst_value != 0) - fprintf(stderr, "APC was done even though return value says it wasnt!\n"); - } - else - { - if (dst_value != src_value) - fprintf(stderr, "APC was not done even though return value says it was!\n"); - } - //usleep(300); - } - fprintf(stderr, "# test_apc_requestor_thread exiting\n"); - my_thread_end(); -} - -const int N_THREADS=23; -int main(int args, char **argv) -{ - pthread_t service_thr; - pthread_t request_thr[N_THREADS]; - int i, j; - my_thread_global_init(); - - pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL); - while (!started) - usleep(1000); - for (i = 0; i < N_THREADS; i++) - pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL); - - for (i = 0; i < 15; i++) - { - usleep(500*1000); - fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed); - } - fprintf(stderr, "# Shutting down requestors\n"); - requestors_should_exit= TRUE; - for (i = 0; i < N_THREADS; i++) - pthread_join(request_thr[i], NULL); - - fprintf(stderr, "# Shutting down service\n"); - service_should_exit= TRUE; - pthread_join(service_thr, NULL); - fprintf(stderr, "# Done.\n"); - my_thread_end(); - my_thread_global_end(); - return 0; -} - -#endif // MY_APC_STANDALONE - - - diff --git a/sql/my_apc.h b/sql/my_apc.h index 3d9a2154af2..99861ca3194 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -49,22 +49,17 @@ public: void disable(); void process_apc_requests(); - + + /* Functor class for calls you can schedule */ class Apc_call { public: + /* This function will be called in the target thread */ virtual void call_in_target_thread()= 0; virtual ~Apc_call() {} }; - /* - Make an APC call: schedule it for execution and wait until the target - thread has executed it. This function must not be called from a thread - that's different from the target thread. - - @retval FALSE - Ok, the call has been made - @retval TRUE - Call wasnt made (either the target is in disabled state or - timeout occured) - */ + + /* Make a call in the target thread (see function definition for details) */ bool make_apc_call(Apc_call *call, int timeout_sec, bool *timed_out); #ifndef DBUG_OFF diff --git a/unittest/sql/CMakeLists.txt b/unittest/sql/CMakeLists.txt new file mode 100644 index 00000000000..36b14e9a08a --- /dev/null +++ b/unittest/sql/CMakeLists.txt @@ -0,0 +1,3 @@ + +MY_ADD_TESTS(my_apc LINK_LIBRARIES mysys EXT cc) + diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc new file mode 100644 index 00000000000..ada09a1675c --- /dev/null +++ b/unittest/sql/my_apc-t.cc @@ -0,0 +1,201 @@ +/* + Copyright (c) 2012, 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 */ + +/* + This file does standalone APC system tests. +*/ +#include +#include +#include +#include + +#include + +#include "../sql/my_apc.h" + +#define MY_APC_STANDALONE 1 +#include "../sql/my_apc.cc" + +volatile bool started= FALSE; +volatile bool service_should_exit= FALSE; +volatile bool requestors_should_exit=FALSE; + +/* Counters for APC calls */ +int apcs_served= 0; +int apcs_missed=0; +int apcs_timed_out=0; +mysql_mutex_t apc_counters_mutex; + +inline void increment_counter(int *var) +{ + mysql_mutex_lock(&apc_counters_mutex); + *var= *var+1; + mysql_mutex_unlock(&apc_counters_mutex); +} + +volatile bool have_errors= false; + +Apc_target apc_target; +mysql_mutex_t target_mutex; + +int int_rand(int size) +{ + return round (((double)rand() / RAND_MAX) * size); +} + +/* + APC target thread (the one that will serve the APC requests). We will have + one target. +*/ +void *test_apc_service_thread(void *ptr) +{ + my_thread_init(); + mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST); + apc_target.init(&target_mutex); + apc_target.enable(); + started= TRUE; + fprintf(stderr, "# test_apc_service_thread started\n"); + while (!service_should_exit) + { + //apc_target.disable(); + usleep(10000); + //apc_target.enable(); + for (int i = 0; i < 10 && !service_should_exit; i++) + { + apc_target.process_apc_requests(); + usleep(int_rand(30)); + } + } + apc_target.disable(); + apc_target.destroy(); + mysql_mutex_destroy(&target_mutex); + my_thread_end(); + pthread_exit(0); +} + + +/* + One APC request (to write 'value' into *where_to) +*/ +class Apc_order : public Apc_target::Apc_call +{ +public: + int value; // The value + int *where_to; // Where to write it + Apc_order(int a, int *b) : value(a), where_to(b) {} + + void call_in_target_thread() + { + usleep(int_rand(1000)); + *where_to = value; + increment_counter(&apcs_served); + } +}; + + +/* + APC requestor thread. It makes APC requests, and checks if they were actually + executed. +*/ +void *test_apc_requestor_thread(void *ptr) +{ + my_thread_init(); + fprintf(stderr, "# test_apc_requestor_thread started\n"); + while (!requestors_should_exit) + { + int dst_value= 0; + int src_value= int_rand(4*1000*100); + /* Create an APC to do "dst_value= src_value" assignment */ + Apc_order apc_order(src_value, &dst_value); + bool timed_out; + + mysql_mutex_lock(&target_mutex); + bool res= apc_target.make_apc_call(&apc_order, 60, &timed_out); + if (res) + { + if (timed_out) + increment_counter(&apcs_timed_out); + else + increment_counter(&apcs_missed); + + if (dst_value != 0) + { + fprintf(stderr, "APC was done even though return value says it wasnt!\n"); + have_errors= true; + } + } + else + { + if (dst_value != src_value) + { + fprintf(stderr, "APC was not done even though return value says it was!\n"); + have_errors= true; + } + } + //usleep(300); + } + fprintf(stderr, "# test_apc_requestor_thread exiting\n"); + my_thread_end(); + return NULL; +} + +/* Number of APC requestor threads */ +const int N_THREADS=23; + + +int main(int args, char **argv) +{ + pthread_t service_thr; + pthread_t request_thr[N_THREADS]; + int i; + + my_thread_global_init(); + + mysql_mutex_init(0, &apc_counters_mutex, MY_MUTEX_INIT_FAST); + + plan(1); + diag("Testing APC delivery and execution"); + + pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL); + while (!started) + usleep(1000); + for (i = 0; i < N_THREADS; i++) + pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL); + + for (i = 0; i < 15; i++) + { + usleep(500*1000); + fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed); + } + fprintf(stderr, "# Shutting down requestors\n"); + requestors_should_exit= TRUE; + for (i = 0; i < N_THREADS; i++) + pthread_join(request_thr[i], NULL); + + fprintf(stderr, "# Shutting down service\n"); + service_should_exit= TRUE; + pthread_join(service_thr, NULL); + + mysql_mutex_destroy(&apc_counters_mutex); + + fprintf(stderr, "# Done.\n"); + my_thread_end(); + my_thread_global_end(); + + ok1(!have_errors); + return exit_status(); +} + From 94bf016321825209353b41c03e0ea8399787303e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 28 Jun 2012 17:34:26 +0400 Subject: [PATCH 42/67] sql_select.cc: work compiler warnings my_apc-t.cc: make it compile on Windows. --- sql/sql_select.cc | 5 ++--- unittest/sql/my_apc-t.cc | 14 +++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c0e765b7991..5cd7430050b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -15804,12 +15804,11 @@ do_select(JOIN *join,List *fields,TABLE *table,Procedure *procedure) { DBUG_ASSERT(join->table_count); - THD *thd= join->thd; DBUG_EXECUTE_IF("show_explain_probe_do_select", - if (dbug_user_var_equals_int(thd, + if (dbug_user_var_equals_int(join->thd, "show_explain_probe_select_id", join->select_lex->select_number)) - dbug_serve_apcs(thd, 1); + dbug_serve_apcs(join->thd, 1); ); if (join->outer_ref_cond && !join->outer_ref_cond->val_int()) diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc index ada09a1675c..741cfbdb124 100644 --- a/unittest/sql/my_apc-t.cc +++ b/unittest/sql/my_apc-t.cc @@ -53,7 +53,7 @@ mysql_mutex_t target_mutex; int int_rand(int size) { - return round (((double)rand() / RAND_MAX) * size); + return (int) (0.5 + ((double)rand() / RAND_MAX) * size); } /* @@ -71,12 +71,12 @@ void *test_apc_service_thread(void *ptr) while (!service_should_exit) { //apc_target.disable(); - usleep(10000); + my_sleep(10000); //apc_target.enable(); for (int i = 0; i < 10 && !service_should_exit; i++) { apc_target.process_apc_requests(); - usleep(int_rand(30)); + my_sleep(int_rand(30)); } } apc_target.disable(); @@ -99,7 +99,7 @@ public: void call_in_target_thread() { - usleep(int_rand(1000)); + my_sleep(int_rand(1000)); *where_to = value; increment_counter(&apcs_served); } @@ -145,7 +145,7 @@ void *test_apc_requestor_thread(void *ptr) have_errors= true; } } - //usleep(300); + //my_sleep(300); } fprintf(stderr, "# test_apc_requestor_thread exiting\n"); my_thread_end(); @@ -171,13 +171,13 @@ int main(int args, char **argv) pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL); while (!started) - usleep(1000); + my_sleep(1000); for (i = 0; i < N_THREADS; i++) pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL); for (i = 0; i < 15; i++) { - usleep(500*1000); + my_sleep(500*1000); fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed); } fprintf(stderr, "# Shutting down requestors\n"); From b9093d370bc8185ed067b41a6d5765a26ef21f89 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 29 Jun 2012 22:17:16 +0400 Subject: [PATCH 43/67] MWL#182: Explain running statements: address review feedback - Fix the year in Monty Program Ab copyrights in the new files. - Fix permissions handling so that SHOW EXPLAIN's handling is the same as SHOW PROCESSLIST's. --- mysql-test/r/show_explain.result | 45 +++++++++++++++++++++ mysql-test/t/show_explain.test | 67 ++++++++++++++++++++++++++++++-- sql/my_apc.cc | 2 +- sql/my_apc.h | 2 +- sql/sql_class.h | 2 +- sql/sql_parse.cc | 5 ++- sql/sql_show.cc | 25 ++++++++++-- 7 files changed, 138 insertions(+), 10 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 07bcb29a7ea..a4097b9d65e 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -728,4 +728,49 @@ a 2 set debug_dbug=''; drop table t1,t3,t4; +# +# ---------- SHOW EXPLAIN and permissions ----------------- +# +grant ALL on test.* to test2@localhost; +# +# First, make sure that user 'test2' cannot do SHOW EXPLAIN on us +# +set debug_dbug='d,show_explain_probe_join_exec_start'; +select * from t0 where a < 3; +show explain for $thr2; +ERROR 42000: Access denied; you need (at least one of) the PROCESSLIST privilege(s) for this operation +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 10 Using where +Warnings: +Note 1003 select * from t0 where a < 3 +a +0 +1 +2 +set debug_dbug=''; +# +# Unfortunately, our test setup doesn't allow to check that test2 +# can do SHOW EXPLAIN on his own queries. This is because SET debug_dbug +# requires SUPER privilege. Giving SUPER to test2 will make the test +# meaningless +# +# +# Now, grant test2 a PROCESSLIST permission, and see that he's able to observe us +# +grant process on *.* to test2@localhost; +set debug_dbug='d,show_explain_probe_join_exec_start'; +select * from t0 where a < 3; +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 10 Using where +Warnings: +Note 1003 select * from t0 where a < 3 +a +0 +1 +2 +set debug_dbug=''; +revoke all privileges on test.* from test2@localhost; +drop user test2@localhost; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index ffd2c25dfd9..5db6b96d93b 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -727,10 +727,71 @@ reap; set debug_dbug=''; drop table t1,t3,t4; + +--echo # +--echo # ---------- SHOW EXPLAIN and permissions ----------------- +--echo # +grant ALL on test.* to test2@localhost; + +connect (con2, localhost, test2,,); +connection con1; + +--echo # +--echo # First, make sure that user 'test2' cannot do SHOW EXPLAIN on us +--echo # +set debug_dbug='d,show_explain_probe_join_exec_start'; +send +select * from t0 where a < 3; + +connection default; +--source include/wait_condition.inc + +connection con2; +--error ER_SPECIFIC_ACCESS_DENIED_ERROR +evalp show explain for $thr2; + +connection default; +evalp show explain for $thr2; + +connection con1; +reap; +set debug_dbug=''; + +--echo # +--echo # Unfortunately, our test setup doesn't allow to check that test2 +--echo # can do SHOW EXPLAIN on his own queries. This is because SET debug_dbug +--echo # requires SUPER privilege. Giving SUPER to test2 will make the test +--echo # meaningless +--echo # + +--echo # +--echo # Now, grant test2 a PROCESSLIST permission, and see that he's able to observe us +--echo # +disconnect con2; +grant process on *.* to test2@localhost; +connect (con2, localhost, test2,,); +connection con1; + +set debug_dbug='d,show_explain_probe_join_exec_start'; +send +select * from t0 where a < 3; + +connection default; +--source include/wait_condition.inc + +connection con2; +evalp show explain for $thr2; + +connection con1; +reap; +set debug_dbug=''; + + + +revoke all privileges on test.* from test2@localhost; +drop user test2@localhost; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. -## TODO: SHOW EXPLAIN while the primary query is running EXPLAIN EXTENDED/PARTITIONS -## - drop table t0; diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 48d539aed78..b5f2300c17f 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, 2011, Monty Program Ab + Copyright (c) 2011 - 2012, 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 diff --git a/sql/my_apc.h b/sql/my_apc.h index 99861ca3194..88df8145186 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -1,5 +1,5 @@ /* - Copyright (c) 2009, 2011, Monty Program Ab + Copyright (c) 2011 - 2012, 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 diff --git a/sql/sql_class.h b/sql/sql_class.h index 73123151738..d1183225a83 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1552,7 +1552,7 @@ public: }; class THD; -void mysqld_show_explain(THD *thd, ulong thread_id); +void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id); #ifndef DBUG_OFF void dbug_serve_apcs(THD *thd, int n_calls); #endif diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 18db712d6cb..9ebb1b3f36e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3130,6 +3130,7 @@ end_with_restore_list: break; case SQLCOM_SHOW_EXPLAIN: { + const char *effective_user; /* Same security as SHOW PROCESSLIST (TODO check this) */ if (!thd->security_ctx->priv_user[0] && check_global_access(thd,PROCESS_ACL)) @@ -3150,8 +3151,10 @@ end_with_restore_list: MYF(0)); goto error; } + effective_user=(thd->security_ctx->master_access & PROCESS_ACL ? NullS : + thd->security_ctx->priv_user); - mysqld_show_explain(thd, (ulong)it->val_int()); + mysqld_show_explain(thd, effective_user, (ulong)it->val_int()); break; } case SQLCOM_SHOW_AUTHORS: diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 145f4fbebcc..d26c8f18340 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2002,8 +2002,11 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) /* SHOW EXPLAIN FOR command handler - @param thd Current thread's thd - @param thread_id Thread whose explain we need + @param thd Current thread's thd + @param calling_user User that invoked SHOW EXPLAIN, or NULL if the user + has SUPER or PROCESS privileges, and so is allowed + to run SHOW EXPLAIN on anybody. + @param thread_id Thread whose explain we need @notes - Attempt to do "SHOW EXPLAIN FOR " will properly produce "target not @@ -2011,7 +2014,7 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) - todo: check how all this can/will work when using thread pools */ -void mysqld_show_explain(THD *thd, ulong thread_id) +void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) { THD *tmp; Protocol *protocol= thd->protocol; @@ -2043,6 +2046,22 @@ void mysqld_show_explain(THD *thd, ulong thread_id) if (tmp) { + Security_context *tmp_sctx= tmp->security_ctx; + /* + If calling_user==NULL, calling thread has SUPER or PROCESS + privilege, and so can do SHOW EXPLAIN on any user. + + if calling_user!=NULL, he's only allowed to view SHOW EXPLAIN on + his own threads. + */ + if (calling_user && (!tmp_sctx->user || strcmp(calling_user, + tmp_sctx->user))) + { + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESSLIST"); + mysql_mutex_unlock(&tmp->LOCK_thd_data); + DBUG_VOID_RETURN; + } + bool bres; /* Ok we've found the thread of interest and it won't go away because From 00ff7345fa10fc0de924b7c2acd3ea9ea4cf50aa Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 30 Jun 2012 06:05:06 +0400 Subject: [PATCH 44/67] - More "local" code in show_explain.test - Better comments - Make unittest compile on Windows --- mysql-test/r/show_explain.result | 2 ++ mysql-test/t/show_explain.test | 2 ++ sql/my_apc.cc | 6 +----- sql/my_apc.h | 6 +++--- unittest/sql/my_apc-t.cc | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index a4097b9d65e..7c22a2c6435 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -735,6 +735,7 @@ grant ALL on test.* to test2@localhost; # # First, make sure that user 'test2' cannot do SHOW EXPLAIN on us # +set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; select * from t0 where a < 3; show explain for $thr2; @@ -759,6 +760,7 @@ set debug_dbug=''; # Now, grant test2 a PROCESSLIST permission, and see that he's able to observe us # grant process on *.* to test2@localhost; +set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; select * from t0 where a < 3; show explain for $thr2; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 5db6b96d93b..5cfc0a5e202 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -739,6 +739,7 @@ connection con1; --echo # --echo # First, make sure that user 'test2' cannot do SHOW EXPLAIN on us --echo # +set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; send select * from t0 where a < 3; @@ -772,6 +773,7 @@ grant process on *.* to test2@localhost; connect (con2, localhost, test2,,); connection con1; +set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; send select * from t0 where a < 3; diff --git a/sql/my_apc.cc b/sql/my_apc.cc index b5f2300c17f..1cc13f41566 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -22,11 +22,7 @@ #endif -/* - Standalone testing: - g++ -c -DMY_APC_STANDALONE -g -I.. -I../include -o my_apc.o my_apc.cc - g++ -L../mysys -L../dbug -L../strings my_apc.o -lmysys -ldbug -lmystrings -lpthread -lrt -*/ +/* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ /* diff --git a/sql/my_apc.h b/sql/my_apc.h index 88df8145186..160c19b6353 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -34,7 +34,9 @@ */ /* - Target for asynchronous procedue calls (APCs). + Target for asynchronous procedure calls (APCs). + - A target is running in some particular thread, + - One can make calls to it from other threads. */ class Apc_target { @@ -113,5 +115,3 @@ private: } }; -/////////////////////////////////////////////////////////////////////// - diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc index 741cfbdb124..fccfb6ecd13 100644 --- a/unittest/sql/my_apc-t.cc +++ b/unittest/sql/my_apc-t.cc @@ -84,6 +84,7 @@ void *test_apc_service_thread(void *ptr) mysql_mutex_destroy(&target_mutex); my_thread_end(); pthread_exit(0); + return NULL; } From b97678f066dd9dfb32409c61028080ac14efb1eb Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 30 Jun 2012 08:29:29 +0400 Subject: [PATCH 45/67] Better comments --- sql/my_apc.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sql/my_apc.h b/sql/my_apc.h index 160c19b6353..93b934c9df1 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -80,7 +80,9 @@ private: /* Circular, double-linked list of all enqueued call requests. We use this structure, because we - - process requests sequentially (i.e. they are removed from the front) + - process requests sequentially: requests are added at the end of the + list and removed from the front. With circular list, we can keep one + pointer. - a thread that has posted a request may time out (or be KILLed) and cancel the request, which means we need a fast request-removal operation. From 3e90dc1f77dc3fa51d542bf82a336753310f7776 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 5 Jul 2012 22:04:13 +0400 Subject: [PATCH 46/67] MWL#182: Explain running statements - Make SHOW EXPLAIN command be KILLable with KILL QUERY. --- mysql-test/r/show_explain.result | 40 +++++++++++++++++++++++++ mysql-test/t/show_explain.test | 51 ++++++++++++++++++++++++++++++-- sql/my_apc.cc | 21 +++++++++---- sql/my_apc.h | 4 ++- sql/sql_show.cc | 8 +++-- unittest/sql/my_apc-t.cc | 27 ++++++++++++++++- 6 files changed, 140 insertions(+), 11 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 7c22a2c6435..c5891f96e82 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -775,4 +775,44 @@ a set debug_dbug=''; revoke all privileges on test.* from test2@localhost; drop user test2@localhost; +# +# Test that it is possible to KILL a SHOW EXPLAIN command that's waiting +# on its target thread +# +create table t1 (pk int primary key, data char(64)) engine=innodb; +insert into t1 select A.a + 10 * B.a + 100 * C.a, 'data1' from t0 A, t0 B, t0 C; +# Lock two threads +set autocommit=0; +select * from t1 where pk between 10 and 20 for update; +pk data +10 data1 +11 data1 +12 data1 +13 data1 +14 data1 +15 data1 +16 data1 +17 data1 +18 data1 +19 data1 +20 data1 +set autocommit=0; +select * from t1 where pk between 10 and 20 for update; +show explain for 3; +kill query $thr_default; +ERROR 70100: Query execution was interrupted +rollback; +pk data +10 data1 +11 data1 +12 data1 +13 data1 +14 data1 +15 data1 +16 data1 +17 data1 +18 data1 +19 data1 +20 data1 +drop table t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 5cfc0a5e202..c8d52ad7bb5 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -2,6 +2,7 @@ # Tests for SHOW EXPLAIN FOR functionality # --source include/have_debug.inc +--source include/have_innodb.inc --disable_warnings drop table if exists t0, t1, t2, t3, t4; @@ -788,11 +789,57 @@ connection con1; reap; set debug_dbug=''; - - revoke all privileges on test.* from test2@localhost; drop user test2@localhost; +disconnect con2; +--echo # +--echo # Test that it is possible to KILL a SHOW EXPLAIN command that's waiting +--echo # on its target thread +--echo # +connect (con2, localhost, root,,); +connect (con3, localhost, root,,); +connection con2; +create table t1 (pk int primary key, data char(64)) engine=innodb; +insert into t1 select A.a + 10 * B.a + 100 * C.a, 'data1' from t0 A, t0 B, t0 C; + +--echo # Lock two threads +set autocommit=0; +select * from t1 where pk between 10 and 20 for update; + +connection con1; +set autocommit=0; +# This will freeze +send +select * from t1 where pk between 10 and 20 for update; + +# run SHOW EXPLAIN on a frozen thread +connection default; +let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2; +let $thr_default=`select connection_id()`; +--source include/wait_condition.inc +send_eval show explain for $thr2; + +# kill the SHOW EXPLAIN command +connection con3; +let $wait_condition= select State='show_explain' from information_schema.processlist where id=$thr_default; +--source include/wait_condition.inc +evalp kill query $thr_default; + +connection default; +--error ER_QUERY_INTERRUPTED +reap; + +connection con2; +rollback; + +connection con1; +reap; + +drop table t1; +disconnect con3; +disconnect con2; + ## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a ## thread and served together. diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 1cc13f41566..dd15daf48a2 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -145,8 +145,8 @@ void Apc_target::dequeue_request(Call_request *qe) to use thd->enter_cond() calls to be killable) */ -bool Apc_target::make_apc_call(Apc_call *call, int timeout_sec, - bool *timed_out) +bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, + int timeout_sec, bool *timed_out) { bool res= TRUE; *timed_out= FALSE; @@ -166,6 +166,9 @@ bool Apc_target::make_apc_call(Apc_call *call, int timeout_sec, set_timespec(abstime, timeout); int wait_res= 0; + const char *old_msg; + old_msg= caller_thd->enter_cond(&apc_request.COND_request, + LOCK_thd_data_ptr, "show_explain"); /* todo: how about processing other errors here? */ while (!apc_request.processed && (wait_res != ETIMEDOUT)) { @@ -173,13 +176,18 @@ bool Apc_target::make_apc_call(Apc_call *call, int timeout_sec, wait_res= mysql_cond_timedwait(&apc_request.COND_request, LOCK_thd_data_ptr, &abstime); // &apc_request.LOCK_request, &abstime); + if (caller_thd->killed) + { + break; + } } if (!apc_request.processed) { /* - The wait has timed out. Remove the request from the queue (ok to do - because we own LOCK_thd_data_ptr. + The wait has timed out, or this thread was KILLed. + Remove the request from the queue (ok to do because we own + LOCK_thd_data_ptr) */ apc_request.processed= TRUE; dequeue_request(&apc_request); @@ -191,7 +199,10 @@ bool Apc_target::make_apc_call(Apc_call *call, int timeout_sec, /* Request was successfully executed and dequeued by the target thread */ res= FALSE; } - mysql_mutex_unlock(LOCK_thd_data_ptr); + /* + exit_cond() will call mysql_mutex_unlock(LOCK_thd_data_ptr) for us: + */ + caller_thd->exit_cond(old_msg); /* Destroy all APC request data */ mysql_cond_destroy(&apc_request.COND_request); diff --git a/sql/my_apc.h b/sql/my_apc.h index 93b934c9df1..84819b9beea 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -33,6 +33,8 @@ requestor. */ +class THD; + /* Target for asynchronous procedure calls (APCs). - A target is running in some particular thread, @@ -62,7 +64,7 @@ public: }; /* Make a call in the target thread (see function definition for details) */ - bool make_apc_call(Apc_call *call, int timeout_sec, bool *timed_out); + bool make_apc_call(THD *caller_thd, Apc_call *call, int timeout_sec, bool *timed_out); #ifndef DBUG_OFF int n_calls_processed; /* Number of calls served by this target */ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index d26c8f18340..6c407f0cec3 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2084,11 +2084,15 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) explain_req.failed_to_produce= FALSE; /* Ok, we have a lock on target->LOCK_thd_data, can call: */ - bres= tmp->apc_target.make_apc_call(&explain_req, timeout_sec, &timed_out); + bres= tmp->apc_target.make_apc_call(thd, &explain_req, timeout_sec, &timed_out); if (bres || explain_req.failed_to_produce) { - /* TODO not enabled or time out */ + if (thd->killed) + { + thd->send_kill_message(); + } + else if (timed_out) { my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc index fccfb6ecd13..3b837b4700e 100644 --- a/unittest/sql/my_apc-t.cc +++ b/unittest/sql/my_apc-t.cc @@ -24,6 +24,29 @@ #include +/* + A fake THD with enter_cond/exit_cond and some other members. +*/ +class THD +{ + mysql_mutex_t* thd_mutex; +public: + bool killed; + + THD() : killed(FALSE) {} + inline const char* enter_cond(mysql_cond_t *cond, mysql_mutex_t* mutex, + const char* msg) + { + mysql_mutex_assert_owner(mutex); + thd_mutex= mutex; + return NULL; + } + inline void exit_cond(const char* old_msg) + { + mysql_mutex_unlock(thd_mutex); + } +}; + #include "../sql/my_apc.h" #define MY_APC_STANDALONE 1 @@ -115,6 +138,8 @@ void *test_apc_requestor_thread(void *ptr) { my_thread_init(); fprintf(stderr, "# test_apc_requestor_thread started\n"); + THD my_thd; + while (!requestors_should_exit) { int dst_value= 0; @@ -124,7 +149,7 @@ void *test_apc_requestor_thread(void *ptr) bool timed_out; mysql_mutex_lock(&target_mutex); - bool res= apc_target.make_apc_call(&apc_order, 60, &timed_out); + bool res= apc_target.make_apc_call(&my_thd, &apc_order, 60, &timed_out); if (res) { if (timed_out) From 89820b4202f36353146ea4491569a06ccb0550fb Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 6 Jul 2012 00:28:30 +0400 Subject: [PATCH 47/67] Better comments. --- sql/my_apc.cc | 3 --- sql/sql_lex.cc | 4 +++- sql/sql_select.cc | 15 ++++++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sql/my_apc.cc b/sql/my_apc.cc index dd15daf48a2..4a523fcd03e 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -140,9 +140,6 @@ void Apc_target::dequeue_request(Call_request *qe) @retval FALSE - Ok, the call has been made @retval TRUE - Call wasnt made (either the target is in disabled state or timeout occured) - - psergey-todo: Should waits here be KILLable? (it seems one needs - to use thd->enter_cond() calls to be killable) */ bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 349642f72b2..f918439d45a 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3832,7 +3832,9 @@ void SELECT_LEX::update_used_tables() /** Set the EXPLAIN type for this subquery. - psergey-todo: comments about + + @param on_the_fly TRUE<=> We're running a SHOW EXPLAIN command, so we must + not change any variables */ void st_select_lex::set_explain_type(bool on_the_fly) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 5cd7430050b..486a55cf50e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -273,13 +273,17 @@ JOIN_TAB *first_depth_first_tab(JOIN* join); JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab); #ifndef DBUG_OFF -// psergey: + +/* + SHOW EXPLAIN testing: wait for, and serve n_calls APC requests. +*/ void dbug_serve_apcs(THD *thd, int n_calls) { - // TODO how do we signal that we're SHOW-EXPLAIN-READY? const char *save_proc_info= thd->proc_info; + /* This is so that mysqltest knows we're ready to serve requests: */ thd_proc_info(thd, "show_explain_trap"); + /* Busy-wait for n_calls APC requests to arrive and be processed */ int n_apcs= thd->apc_target.n_calls_processed + n_calls; while (thd->apc_target.n_calls_processed < n_apcs) { @@ -10637,9 +10641,6 @@ void JOIN::cleanup(bool full) DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); - /* - psergey: let's try without this first: - */ have_query_plan= QEP_DELETED; if (table) @@ -21330,7 +21331,7 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, Item *item_null= new Item_null(); List item_list; if (on_the_fly) - select_lex->set_explain_type(on_the_fly); //psergey + select_lex->set_explain_type(on_the_fly); /* here we assume that the query will return at least two rows, so we show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong @@ -22043,7 +22044,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) { - sl->set_explain_type(FALSE); //psergey-todo: maybe remove this from here? + sl->set_explain_type(FALSE); sl->options|= SELECT_DESCRIBE; } From ae3bc191613fdec05d4d84a74648d1e84edc8ce4 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 6 Jul 2012 00:34:32 +0400 Subject: [PATCH 48/67] Remove out-of-date comments. --- sql/sql_show.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 6c407f0cec3..2319a13de8c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2011,7 +2011,6 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) @notes - Attempt to do "SHOW EXPLAIN FOR " will properly produce "target not running EXPLAINable command". - - todo: check how all this can/will work when using thread pools */ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) @@ -2065,9 +2064,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) bool bres; /* Ok we've found the thread of interest and it won't go away because - we're holding its LOCK_thd data. - Post it an EXPLAIN request. - todo: where to get timeout from? + we're holding its LOCK_thd data. Post it a SHOW EXPLAIN request. */ bool timed_out; int timeout_sec= 30; From a931467e17c4826ce4fa473de0479953d9bbcc59 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 7 Jul 2012 08:47:41 +0400 Subject: [PATCH 49/67] Enable PERFORMANCE_SCHEMA tracking for SHOW EXPLAIN's conditions. --- mysql-test/r/show_explain_ps.result | 27 ++++++++++++++++ mysql-test/t/show_explain_ps.test | 48 +++++++++++++++++++++++++++++ sql/my_apc.cc | 25 +++++++++++++-- sql/my_apc.h | 4 +++ sql/mysqld.cc | 1 + 5 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 mysql-test/r/show_explain_ps.result create mode 100644 mysql-test/t/show_explain_ps.test diff --git a/mysql-test/r/show_explain_ps.result b/mysql-test/r/show_explain_ps.result new file mode 100644 index 00000000000..625b9cfddae --- /dev/null +++ b/mysql-test/r/show_explain_ps.result @@ -0,0 +1,27 @@ +drop table if exists t0, t1; +select * from performance_schema.setup_instruments where name like '%show_explain%'; +NAME ENABLED TIMED +wait/synch/cond/sql/show_explain YES YES +# We've got no instances +select * from performance_schema.cond_instances where name like '%show_explain%'; +NAME OBJECT_INSTANCE_BEGIN +# Check out if our cond is hit. +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +select count(*) from t0 where a < 100000; +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 10 Using where +Warnings: +Note 1003 select count(*) from t0 where a < 100000 +count(*) +10 +set debug_dbug=''; +select event_name +from performance_schema.events_waits_history_long +where event_name='wait/synch/cond/sql/show_explain'; +event_name +wait/synch/cond/sql/show_explain +drop table t0; diff --git a/mysql-test/t/show_explain_ps.test b/mysql-test/t/show_explain_ps.test new file mode 100644 index 00000000000..67634ac57e2 --- /dev/null +++ b/mysql-test/t/show_explain_ps.test @@ -0,0 +1,48 @@ +# +# Test how SHOW EXPLAIN is represented in performance schema +# +--source include/have_perfschema.inc + +--disable_warnings +drop table if exists t0, t1; +--enable_warnings + +select * from performance_schema.setup_instruments where name like '%show_explain%'; + +--echo # We've got no instances +select * from performance_schema.cond_instances where name like '%show_explain%'; + +--echo # Check out if our cond is hit. + +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +let $thr1=`select connection_id()`; +connect (con1, localhost, root,,); +connection con1; +let $thr2=`select connection_id()`; +connection default; + +let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; + +# +# Test SHOW EXPLAIN for simple queries +# +connection con1; +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +send select count(*) from t0 where a < 100000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con1; +reap; + +set debug_dbug=''; + +select event_name +from performance_schema.events_waits_history_long +where event_name='wait/synch/cond/sql/show_explain'; + +drop table t0; diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 4a523fcd03e..c7ba25ad3ba 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -124,6 +124,26 @@ void Apc_target::dequeue_request(Call_request *qe) qe->next->prev= qe->prev; } +#ifdef HAVE_PSI_INTERFACE + +/* One key for all conds */ +PSI_cond_key key_show_explain_request_COND; + +static PSI_cond_info show_explain_psi_conds[]= +{ + { &key_show_explain_request_COND, "show_explain", 0 /* not using PSI_FLAG_GLOBAL*/ } +}; + +void init_show_explain_psi_keys(void) +{ + if (PSI_server == NULL) + return; + + PSI_server->register_cond("sql", show_explain_psi_conds, + array_elements(show_explain_psi_conds)); +} +#endif + /* Make an APC (Async Procedure Call) to another thread. @@ -154,7 +174,8 @@ bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, Call_request apc_request; apc_request.call= call; apc_request.processed= FALSE; - mysql_cond_init(0 /* do not track in PS */, &apc_request.COND_request, NULL); + mysql_cond_init(key_show_explain_request_COND, &apc_request.COND_request, + NULL); enqueue_request(&apc_request); apc_request.what="enqueued by make_apc_call"; @@ -174,9 +195,7 @@ bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, LOCK_thd_data_ptr, &abstime); // &apc_request.LOCK_request, &abstime); if (caller_thd->killed) - { break; - } } if (!apc_request.processed) diff --git a/sql/my_apc.h b/sql/my_apc.h index 84819b9beea..1c4cc25376b 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -119,3 +119,7 @@ private: } }; +#ifdef HAVE_PSI_INTERFACE +void init_show_explain_psi_keys(void); +#endif + diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 67446ef7239..54b5b95afaf 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3909,6 +3909,7 @@ static int init_thread_environment() sp_cache_init(); #ifdef HAVE_EVENT_SCHEDULER Events::init_mutexes(); + init_show_explain_psi_keys(); #endif /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); From 725d76e1e844b587eeeab23fb0caa670735e47b6 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 10 Jul 2012 21:23:00 +0400 Subject: [PATCH 50/67] MWL#182: Explain running statements: address review feedback - switch SHOW EXPLAIN to using an INFORMATION_SCHEMA table. --- mysql-test/r/show_explain.result | 7 ++- mysql-test/t/show_explain.test | 9 ++++ sql/handler.h | 1 + sql/my_apc.cc | 28 +++++++++++ sql/protocol.h | 14 ------ sql/sql_class.cc | 82 ++------------------------------ sql/sql_class.h | 23 ++++----- sql/sql_lex.h | 3 +- sql/sql_parse.cc | 55 ++++++++++----------- sql/sql_show.cc | 66 ++++++++++++------------- sql/sql_yacc.yy | 5 +- 11 files changed, 118 insertions(+), 175 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index c5891f96e82..60341a5d68b 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -798,7 +798,7 @@ pk data 20 data1 set autocommit=0; select * from t1 where pk between 10 and 20 for update; -show explain for 3; +# do: send_eval show explain for 3; kill query $thr_default; ERROR 70100: Query execution was interrupted rollback; @@ -816,3 +816,8 @@ pk data 20 data1 drop table t1; drop table t0; +# +# Check that the I_S table is invisible +# +select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%'; +table_name diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index c8d52ad7bb5..2e47a2e9615 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -818,7 +818,10 @@ connection default; let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2; let $thr_default=`select connection_id()`; --source include/wait_condition.inc +--echo # do: send_eval show explain for $thr2; +--disable_query_log send_eval show explain for $thr2; +--enable_query_log # kill the SHOW EXPLAIN command connection con3; @@ -844,3 +847,9 @@ disconnect con2; ## thread and served together. drop table t0; + +--echo # +--echo # Check that the I_S table is invisible +--echo # +select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%'; + diff --git a/sql/handler.h b/sql/handler.h index ee1731af563..148801f8fe7 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -600,6 +600,7 @@ enum enum_schema_tables SCH_COLUMN_PRIVILEGES, SCH_ENGINES, SCH_EVENTS, + SCH_EXPLAIN, SCH_FILES, SCH_GLOBAL_STATUS, SCH_GLOBAL_VARIABLES, diff --git a/sql/my_apc.cc b/sql/my_apc.cc index c7ba25ad3ba..8551c0187a4 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -24,7 +24,35 @@ /* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ +#ifndef MY_APC_STANDALONE +ST_FIELD_INFO show_explain_fields_info[]= +{ + /* field_name, length, type, value, field_flags, old_name*/ + {"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id", + SKIP_OPEN_TABLE}, + {"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type", + SKIP_OPEN_TABLE}, + {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, + "table", SKIP_OPEN_TABLE}, + {"type", 10, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, + {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, + {"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, + "key", SKIP_OPEN_TABLE}, + {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE}, + {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE}, + {"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows", + SKIP_OPEN_TABLE}, + {"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra", + SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + + +#endif /* Initialize the target. diff --git a/sql/protocol.h b/sql/protocol.h index 3627e625c07..1c0a28560bd 100644 --- a/sql/protocol.h +++ b/sql/protocol.h @@ -78,20 +78,6 @@ public: virtual bool send_result_set_metadata(List *list, uint flags); bool send_result_set_row(List *row_items); - void get_packet(const char **start, size_t *length) - { - *start= packet->ptr(); - *length= packet->length(); - } - void set_packet(const char *start, size_t len) - { - packet->length(0); - packet->append(start, len); -#ifndef DBUG_OFF - field_pos= field_count - 1; -#endif - } - bool store(I_List *str_list); bool store(const char *from, CHARSET_INFO *cs); String *storage_packet() { return packet; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 35cc28bcae7..d6d14c45b47 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2309,90 +2309,16 @@ int select_send::send_data(List &items) DBUG_RETURN(0); } -////////////////////////////////////////////////////////////////////////////// - -/* - Save the data being sent in our internal buffer. -*/ int select_result_explain_buffer::send_data(List &items) { - List_iterator_fast li(items); - char buff[MAX_FIELD_WIDTH]; - String buffer(buff, sizeof(buff), &my_charset_bin); - DBUG_ENTER("select_send::send_data"); - - protocol->prepare_for_resend(); - Item *item; - while ((item=li++)) - { - if (item->send(protocol, &buffer)) - { - protocol->free(); // Free used buffer - my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0)); - break; - } - /* - Reset buffer to its original state, as it may have been altered in - Item::send(). - */ - buffer.set(buff, sizeof(buff), &my_charset_bin); - } - - if (thd->is_error()) - { - protocol->remove_last_row(); - DBUG_RETURN(1); - } - /* - Instead of calling protocol->write(), steal the packed and put it to our - buffer - */ - const char *packet_data; - size_t len; - protocol->get_packet(&packet_data, &len); - - String *s= new (thd->mem_root) String; - s->append(packet_data, len); - data_rows.push_back(s); - protocol->remove_last_row(); // <-- this does nothing. Do we need it? - // prepare_for_resend() will wipe out the packet - DBUG_RETURN(0); + fill_record(thd, dst_table->field, items, TRUE, FALSE); + if ((dst_table->file->ha_write_tmp_row(dst_table->record[0]))) + return 1; + return 0; } -/* Write the saved resultset to the client (via this->protocol) and free it. */ - -void select_result_explain_buffer::flush_data() -{ - List_iterator it(data_rows); - String *str; - while ((str= it++)) - { - protocol->set_packet(str->ptr(), str->length()); - protocol->write(); - delete str; - } - data_rows.empty(); -} - - -/* Free the accumulated resultset */ - -void select_result_explain_buffer::discard_data() -{ - List_iterator it(data_rows); - String *str; - while ((str= it++)) - { - delete str; - } - data_rows.empty(); -} - -////////////////////////////////////////////////////////////////////////////// - - bool select_send::send_eof() { /* diff --git a/sql/sql_class.h b/sql/sql_class.h index d1183225a83..d34e285ead9 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3319,32 +3319,27 @@ public: /* - A select result sink that collects the sent data and then can flush it to - network when requested. - - This class is targeted at collecting EXPLAIN output: - - Unoptimized data storage (can't handle big datasets) + This is a select_result_sink which simply writes all data into a (temporary) + table. Creation/deletion of the table is outside of the scope of the class + + It is aimed at capturing SHOW EXPLAIN output, so: - Unlike select_result class, we don't assume that the sent data is an output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the unit) + - We don't try to convert the target table to MyISAM */ class select_result_explain_buffer : public select_result_sink { public: + select_result_explain_buffer(THD *thd_arg, TABLE *table_arg) : + thd(thd_arg), dst_table(table_arg) {}; + THD *thd; - Protocol *protocol; - select_result_explain_buffer(){}; + TABLE *dst_table; /* table to write into */ /* The following is called in the child thread: */ int send_data(List &items); - - /* this will be called in the parent thread: */ - void flush_data(); - - void discard_data(); - - List data_rows; }; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 1967a15ef5a..d8316dcc03c 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2357,7 +2357,8 @@ struct LEX: public Query_tables_list char *backup_dir; /* For RESTORE/BACKUP */ char* to_log; /* For PURGE MASTER LOGS TO */ char* x509_subject,*x509_issuer,*ssl_cipher; - String *wild; + String *wild; /* Wildcard in SHOW {something} LIKE 'wild'*/ + Item *show_explain_for_thread; /* id in SHOW EXPLAIN FOR id */ sql_exchange *exchange; select_result *result; Item *default_value, *on_update_value; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 9ebb1b3f36e..3a6bb4909f2 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2144,6 +2144,32 @@ mysql_execute_command(THD *thd) execute_show_status(thd, all_tables); break; } + case SQLCOM_SHOW_EXPLAIN: + { + if (!thd->security_ctx->priv_user[0] && + check_global_access(thd,PROCESS_ACL)) + break; + + /* + The select should use only one table, it's the SHOW EXPLAIN pseudo-table + */ + if (lex->sroutines.records || lex->query_tables->next_global) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " + "function calls as part of this statement"); + break; + } + + Item **it= &(lex->show_explain_for_thread); + if ((!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || + (*it)->check_cols(1)) + { + my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; + } + /* no break; fall through */ + } case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TRIGGERS: @@ -3128,35 +3154,6 @@ end_with_restore_list: thd->security_ctx->priv_user), lex->verbose); break; - case SQLCOM_SHOW_EXPLAIN: - { - const char *effective_user; - /* Same security as SHOW PROCESSLIST (TODO check this) */ - if (!thd->security_ctx->priv_user[0] && - check_global_access(thd,PROCESS_ACL)) - break; - - Item *it= (Item *)lex->value_list.head(); - - if (lex->table_or_sp_used()) - { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " - "function calls as part of this statement"); - break; - } - - if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1)) - { - my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), - MYF(0)); - goto error; - } - effective_user=(thd->security_ctx->master_access & PROCESS_ACL ? NullS : - thd->security_ctx->priv_user); - - mysqld_show_explain(thd, effective_user, (ulong)it->val_int()); - break; - } case SQLCOM_SHOW_AUTHORS: res= mysqld_show_authors(thd); break; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 2319a13de8c..dc5f32b5728 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1999,32 +1999,24 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) } +static +const char *target_not_explainable_cmd="Target is not running EXPLAINable command"; + /* - SHOW EXPLAIN FOR command handler - - @param thd Current thread's thd - @param calling_user User that invoked SHOW EXPLAIN, or NULL if the user - has SUPER or PROCESS privileges, and so is allowed - to run SHOW EXPLAIN on anybody. - @param thread_id Thread whose explain we need - - @notes - - Attempt to do "SHOW EXPLAIN FOR " will properly produce "target not - running EXPLAINable command". + Store the SHOW EXPLAIN output in the temporary table. */ -void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) +int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) { + const char *calling_user; THD *tmp; - Protocol *protocol= thd->protocol; - List field_list; - DBUG_ENTER("mysqld_show_explain"); - - thd->make_explain_field_list(field_list); - if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS | - Protocol::SEND_EOF)) - DBUG_VOID_RETURN; - + my_thread_id thread_id; + DBUG_ENTER("fill_show_explain"); + + DBUG_ASSERT(cond==NULL); + thread_id= thd->lex->show_explain_for_thread->val_int(); + calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS : + thd->security_ctx->priv_user; /* Find the thread we need EXPLAIN for. Thread search code was copied from kill_one_thread() @@ -2042,7 +2034,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) } } mysql_mutex_unlock(&LOCK_thread_count); - + if (tmp) { Security_context *tmp_sctx= tmp->security_ctx; @@ -2058,7 +2050,15 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) { my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESSLIST"); mysql_mutex_unlock(&tmp->LOCK_thd_data); - DBUG_VOID_RETURN; + DBUG_RETURN(1); + } + + if (tmp == thd) + { + mysql_mutex_unlock(&tmp->LOCK_thd_data); + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SHOW EXPLAIN", + target_not_explainable_cmd); + DBUG_RETURN(1); } bool bres; @@ -2071,9 +2071,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) Show_explain_request explain_req; select_result_explain_buffer *explain_buf; - explain_buf= new select_result_explain_buffer; - explain_buf->thd=thd; - explain_buf->protocol= thd->protocol; + explain_buf= new select_result_explain_buffer(thd, table->table); explain_req.explain_buf= explain_buf; explain_req.target_thd= tmp; @@ -2099,29 +2097,22 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) else { my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), - "SHOW EXPLAIN", - "Target is not running EXPLAINable command"); + "SHOW EXPLAIN", target_not_explainable_cmd); } bres= TRUE; - explain_buf->discard_data(); } else { push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, ER_YES, explain_req.query_str.c_ptr_safe()); } - if (!bres) - { - explain_buf->flush_data(); - my_eof(thd); - } + DBUG_RETURN(bres); } else { my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id); + DBUG_RETURN(1); } - - DBUG_VOID_RETURN; } @@ -8436,6 +8427,7 @@ ST_FIELD_INFO keycache_fields_info[]= }; +extern ST_FIELD_INFO show_explain_fields_info[]; /* Description of ST_FIELD_INFO in table.h @@ -8467,6 +8459,8 @@ ST_SCHEMA_TABLE schema_tables[]= {"EVENTS", events_fields_info, create_schema_table, 0, make_old_format, 0, -1, -1, 0, 0}, #endif + {"EXPLAIN", show_explain_fields_info, create_schema_table, fill_show_explain, + make_old_format, 0, -1, -1, TRUE /*hidden*/ , 0}, {"FILES", files_fields_info, create_schema_table, hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, {"GLOBAL_STATUS", variables_fields_info, create_schema_table, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index d60fcbff35e..aed8edab027 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11617,8 +11617,9 @@ show_param: | describe_command FOR_SYM expr { Lex->sql_command= SQLCOM_SHOW_EXPLAIN; - Lex->value_list.empty(); - Lex->value_list.push_front($3); + if (prepare_schema_table(YYTHD, Lex, 0, SCH_EXPLAIN)) + MYSQL_YYABORT; + Lex->show_explain_for_thread= $3; } ; From f913ba7a605b965359b437db36b0baa5498ec2c0 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 11 Jul 2012 13:39:56 +0400 Subject: [PATCH 51/67] MWL#182: Explain running statements: address review feedback - Make THD::check_killed() an inline function which makes calls to non-inline functions only whern there are APC requests to be served. --- sql/my_apc.h | 9 +++++++++ sql/sql_class.cc | 15 --------------- sql/sql_class.h | 9 ++++++++- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/sql/my_apc.h b/sql/my_apc.h index 1c4cc25376b..5473600bae6 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -53,6 +53,15 @@ public: void disable(); void process_apc_requests(); + /* + A lightweight function, intended to be used in frequent checks like this: + + if (apc_target.have_requests()) apc_target.process_apc_requests() + */ + inline bool have_apc_requests() + { + return test(apc_calls); + } /* Functor class for calls you can schedule */ class Apc_call diff --git a/sql/sql_class.cc b/sql/sql_class.cc index d6d14c45b47..414b1ba3f7f 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2166,21 +2166,6 @@ void THD::rollback_item_tree_changes() } -/* - Check if the thread has been killed, and also process "APC requests" - - @retval true The thread is killed, execution should be interrupted - @retval false Not killed, continue execution -*/ - -bool THD::check_killed() -{ - if (killed) - return TRUE; - apc_target.process_apc_requests(); - return FALSE; -} - /***************************************************************************** ** Functions to provide a interface to select results *****************************************************************************/ diff --git a/sql/sql_class.h b/sql/sql_class.h index d34e285ead9..50e445fe9c4 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2222,7 +2222,14 @@ public: */ killed_state volatile killed; - bool check_killed(); + inline bool check_killed() + { + if (killed) + return TRUE; + if (apc_target.have_apc_requests()) + apc_target.process_apc_requests(); + return FALSE; + } /* scramble - random string sent to client on handshake */ char scramble[SCRAMBLE_LENGTH+1]; From de65de3baaa3c04711b84523a3b297a3a36c9b44 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 11 Jul 2012 14:27:59 +0400 Subject: [PATCH 52/67] Make test results stable --- mysql-test/r/show_explain.result | 2 +- mysql-test/t/show_explain.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 60341a5d68b..e5659bdf4ac 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -798,7 +798,7 @@ pk data 20 data1 set autocommit=0; select * from t1 where pk between 10 and 20 for update; -# do: send_eval show explain for 3; +# do: send_eval show explain for thr2; kill query $thr_default; ERROR 70100: Query execution was interrupted rollback; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 2e47a2e9615..6d4ddd79104 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -818,7 +818,7 @@ connection default; let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2; let $thr_default=`select connection_id()`; --source include/wait_condition.inc ---echo # do: send_eval show explain for $thr2; +--echo # do: send_eval show explain for thr2; --disable_query_log send_eval show explain for $thr2; --enable_query_log From a49b4c970fe582919d89109e0cc3ebe2558269cd Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 11 Jul 2012 16:33:10 +0400 Subject: [PATCH 53/67] t/show_explain_ps.test needs debug system to work. --- mysql-test/t/show_explain_ps.test | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/t/show_explain_ps.test b/mysql-test/t/show_explain_ps.test index 67634ac57e2..d891e118b24 100644 --- a/mysql-test/t/show_explain_ps.test +++ b/mysql-test/t/show_explain_ps.test @@ -1,6 +1,7 @@ # # Test how SHOW EXPLAIN is represented in performance schema # +--source include/have_debug.inc --source include/have_perfschema.inc --disable_warnings From 2368f8895d10a3d883a6a0ffe33b2b222caf7f1a Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 17 Jul 2012 21:52:08 +0400 Subject: [PATCH 54/67] MWL#182: Explain running statements - Address feedback from the second code review. --- mysql-test/r/show_explain.result | 48 +++++++++------- mysql-test/t/show_explain.test | 58 +++++++++++++------ sql/my_apc.cc | 32 ----------- sql/my_apc.h | 6 +- sql/mysqld.cc | 2 +- sql/share/errmsg-utf8.txt | 8 ++- sql/sql_class.cc | 51 ++--------------- sql/sql_class.h | 36 +----------- sql/sql_parse.cc | 6 +- sql/sql_show.cc | 95 +++++++++++++++++++++++++------- sql/sql_show.h | 25 +++++++++ unittest/sql/my_apc-t.cc | 18 +++--- 12 files changed, 201 insertions(+), 184 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index e5659bdf4ac..2851e775280 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -10,11 +10,11 @@ alter table t1 add key(a), add key(b); show explain for 2*1000*1000*1000; ERROR HY000: Unknown thread id: 2000000000 show explain for (select max(a) from t0); -ERROR 42000: This version of MariaDB doesn't yet support 'Usage of subqueries or stored function calls as part of this statement' +ERROR HY000: You may only use constant expressions in this statement show explain for $thr2; -ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command show explain for $thr1; -ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; select count(*) from t1 where a < 100000; @@ -153,7 +153,7 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command a (select max(a) from t0 b where b.a+a.a<10) 0 9 # Try to do SHOW EXPLAIN for a query that runs a SET command: @@ -163,7 +163,7 @@ set @show_explain_probe_select_id=2; 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command # # Attempt SHOW EXPLAIN for an UPDATE # @@ -172,9 +172,9 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command show explain for $thr2; -ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command drop table t2; # # Attempt SHOW EXPLAIN for a DELETE @@ -184,9 +184,9 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command show explain for $thr2; -ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command drop table t2; # # Multiple SHOW EXPLAIN calls for one select @@ -308,7 +308,7 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command a 1 2 @@ -361,7 +361,7 @@ set @show_explain_probe_select_id=2; set debug_dbug='d,show_explain_probe_join_exec_end'; SELECT * FROM v1, t2; show explain for $thr2; -ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command a b 8 4 8 5 @@ -393,7 +393,7 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command a set debug_dbug=''; # @@ -405,7 +405,7 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command a a set debug_dbug=''; drop table t3; @@ -510,7 +510,7 @@ 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: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command +ERROR HY000: Target is not running an EXPLAINable command pk a1 set debug_dbug=''; DROP TABLE t2; @@ -732,6 +732,7 @@ drop table t1,t3,t4; # ---------- SHOW EXPLAIN and permissions ----------------- # grant ALL on test.* to test2@localhost; +grant super on *.* to test2@localhost; # # First, make sure that user 'test2' cannot do SHOW EXPLAIN on us # @@ -739,7 +740,7 @@ set @show_explain_probe_select_id=1; set debug_dbug='d,show_explain_probe_join_exec_start'; select * from t0 where a < 3; show explain for $thr2; -ERROR 42000: Access denied; you need (at least one of) the PROCESSLIST privilege(s) for this operation +ERROR 42000: Access denied; you need (at least one of) the PROCESS privilege(s) for this operation 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 10 Using where @@ -751,11 +752,20 @@ a 2 set debug_dbug=''; # -# Unfortunately, our test setup doesn't allow to check that test2 -# can do SHOW EXPLAIN on his own queries. This is because SET debug_dbug -# requires SUPER privilege. Giving SUPER to test2 will make the test -# meaningless +# Check that user test2 can do SHOW EXPLAIN on its own queries # +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +select * from t0 where a < 3; +show explain for $thr_con2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 select * from t0 where a < 3 +a +0 +1 +2 # # Now, grant test2 a PROCESSLIST permission, and see that he's able to observe us # diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 6d4ddd79104..2cb3ab6924a 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -45,7 +45,7 @@ alter table t1 add key(a), add key(b); --error ER_NO_SUCH_THREAD show explain for 2*1000*1000*1000; ---error ER_NOT_SUPPORTED_YET +--error ER_SET_CONSTANTS_ONLY show explain for (select max(a) from t0); # @@ -58,11 +58,11 @@ let $thr2=`select connection_id()`; connection default; # SHOW EXPLAIN FOR ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; # SHOW EXPLAIN FOR ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr1; let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; @@ -192,7 +192,7 @@ 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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -212,7 +212,7 @@ set debug_dbug='d,show_explain_probe_join_exec_start'; send set @foo= (select max(a) from t0 where sin(a) >0); connection default; --source include/wait_condition.inc ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -226,9 +226,9 @@ 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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -243,9 +243,9 @@ 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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -329,7 +329,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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -378,7 +378,7 @@ send SELECT * FROM v1, t2; connection default; --source include/wait_condition.inc ---error ER_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -408,7 +408,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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -424,7 +424,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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -520,7 +520,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_ERROR_WHEN_EXECUTING_COMMAND +--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; @@ -734,6 +734,8 @@ drop table t1,t3,t4; --echo # grant ALL on test.* to test2@localhost; +# Give the user SUPER privilege so it can set debug_dbug variable. +grant super on *.* to test2@localhost; connect (con2, localhost, test2,,); connection con1; @@ -760,12 +762,32 @@ reap; set debug_dbug=''; --echo # ---echo # Unfortunately, our test setup doesn't allow to check that test2 ---echo # can do SHOW EXPLAIN on his own queries. This is because SET debug_dbug ---echo # requires SUPER privilege. Giving SUPER to test2 will make the test ---echo # meaningless +--echo # Check that user test2 can do SHOW EXPLAIN on its own queries --echo # +connect (con3, localhost, test2,,); +connection con2; +let $thr_con2=`select connection_id()`; +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +send +select * from t0 where a < 3; + +connection con1; +let $save_wait_condition= $wait_condition; +let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr_con2; +--source include/wait_condition.inc + +connection con3; +evalp show explain for $thr_con2; + +connection con2; +reap; + +connection con1; + +disconnect con3; +let $wait_condition= $save_wait_condition; --echo # --echo # Now, grant test2 a PROCESSLIST permission, and see that he's able to observe us --echo # diff --git a/sql/my_apc.cc b/sql/my_apc.cc index 8551c0187a4..4d0042510ae 100644 --- a/sql/my_apc.cc +++ b/sql/my_apc.cc @@ -24,35 +24,6 @@ /* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ -#ifndef MY_APC_STANDALONE - -ST_FIELD_INFO show_explain_fields_info[]= -{ - /* field_name, length, type, value, field_flags, old_name*/ - {"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id", - SKIP_OPEN_TABLE}, - {"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type", - SKIP_OPEN_TABLE}, - {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, - "table", SKIP_OPEN_TABLE}, - {"type", 10, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, - {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, - MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, - {"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, - "key", SKIP_OPEN_TABLE}, - {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, - MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE}, - {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/, - MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE}, - {"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows", - SKIP_OPEN_TABLE}, - {"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra", - SKIP_OPEN_TABLE}, - {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} -}; - - -#endif /* Initialize the target. @@ -266,9 +237,6 @@ bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call, void Apc_target::process_apc_requests() { - if (!get_first_in_queue()) - return; - while (1) { Call_request *request; diff --git a/sql/my_apc.h b/sql/my_apc.h index 5473600bae6..7f19809c082 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -1,3 +1,5 @@ +#ifndef INCLUDES_MY_APC_H +#define INCLUDES_MY_APC_H /* Copyright (c) 2011 - 2012, Monty Program Ab @@ -93,7 +95,7 @@ private: We use this structure, because we - process requests sequentially: requests are added at the end of the list and removed from the front. With circular list, we can keep one - pointer. + pointer, and access both front an back of the list with it. - a thread that has posted a request may time out (or be KILLed) and cancel the request, which means we need a fast request-removal operation. @@ -132,3 +134,5 @@ private: void init_show_explain_psi_keys(void); #endif +#endif //INCLUDES_MY_APC_H + diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 54b5b95afaf..7123bf03cdf 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3909,8 +3909,8 @@ static int init_thread_environment() sp_cache_init(); #ifdef HAVE_EVENT_SCHEDULER Events::init_mutexes(); - init_show_explain_psi_keys(); #endif + init_show_explain_psi_keys(); /* Parameter for threads created for connections */ (void) pthread_attr_init(&connection_attrib); (void) pthread_attr_setdetachstate(&connection_attrib, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 140220dfa9c..6e58a70412d 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -4345,13 +4345,13 @@ ER_TOO_MANY_USER_CONNECTIONS 42000 ER_SET_CONSTANTS_ONLY dan "Du mÃ¥ kun bruge konstantudtryk med SET" nla "U mag alleen constante expressies gebruiken bij SET" - eng "You may only use constant expressions with SET" + eng "You may only use constant expressions in this statement" est "Ainult konstantsed suurused on lubatud SET klauslis" fre "Seules les expressions constantes sont autorisées avec SET" - ger "Bei SET dürfen nur konstante Ausdrücke verwendet werden" + ger "Bei diesem Befehl dürfen nur konstante Ausdrücke verwendet werden" ita "Si possono usare solo espressioni costanti con SET" por "Você pode usar apenas expressões constantes com SET" - rus "Ð’Ñ‹ можете иÑпользовать в SET только конÑтантные выражениÑ" + rus "С Ñтой командой вы можете иÑпользовать только конÑтантные выражениÑ" serbian "Možete upotrebiti samo konstantan iskaz sa komandom 'SET'" spa "Tu solo debes usar expresiones constantes con SET" swe "Man kan endast använda konstantuttryck med SET" @@ -6582,3 +6582,5 @@ ER_STORED_FUNCTION_PREVENTS_SWITCH_SKIP_REPLICATION eng "Cannot modify @@session.skip_replication inside a stored function or trigger" ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT eng "Query execution was interrupted. The query examined at least %llu rows, which exceeds LIMIT ROWS EXAMINED (%llu). The query result may be incomplete." +ER_TARGET_NOT_EXPLAINABLE + eng "Target is not running an EXPLAINable command" diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 414b1ba3f7f..d6e09b6c59a 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2295,15 +2295,6 @@ int select_send::send_data(List &items) } -int select_result_explain_buffer::send_data(List &items) -{ - fill_record(thd, dst_table->field, items, TRUE, FALSE); - if ((dst_table->file->ha_write_tmp_row(dst_table->record[0]))) - return 1; - return 0; -} - - bool select_send::send_eof() { /* @@ -3233,43 +3224,6 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup) DBUG_VOID_RETURN; } - -/* - Produce EXPLAIN data. - - This function is APC-scheduled to be run in the context of the thread that - we're producing EXPLAIN for. -*/ - -void Show_explain_request::call_in_target_thread() -{ - Query_arena backup_arena; - bool printed_anything= FALSE; - - /* - Change the arena because JOIN::print_explain and co. are going to allocate - items. Let them allocate them on our arena. - */ - target_thd->set_n_backup_active_arena((Query_arena*)request_thd, - &backup_arena); - - query_str.copy(target_thd->query(), - target_thd->query_length(), - &my_charset_bin); - - if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, - &printed_anything)) - { - failed_to_produce= TRUE; - } - - if (!printed_anything) - failed_to_produce= TRUE; - - target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena); -} - - Statement::~Statement() { } @@ -3832,6 +3786,7 @@ void THD::restore_backup_open_tables_state(Open_tables_backup *backup) @retval 1 the user thread has been killed This is used to signal a storage engine if it should be killed. + See also THD::check_killed(). */ extern "C" int thd_killed(const MYSQL_THD thd) @@ -3839,6 +3794,10 @@ extern "C" int thd_killed(const MYSQL_THD thd) if (!thd) thd= current_thd; + Apc_target *apc_target= (Apc_target*)&thd->apc_target; + if (apc_target->have_apc_requests()) + apc_target->process_apc_requests(); + if (!(thd->killed & KILL_HARD_BIT)) return 0; return thd->killed; diff --git a/sql/sql_class.h b/sql/sql_class.h index 50e445fe9c4..86797a8608b 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1520,39 +1520,7 @@ private: extern "C" void my_message_sql(uint error, const char *str, myf MyFlags); -class select_result_explain_buffer; - - -/* - SHOW EXPLAIN request object. - - The thread that runs SHOW EXPLAIN statement creates a Show_explain_request - object R, and then schedules APC call of - Show_explain_request::call((void*)&R). - -*/ - -class Show_explain_request : public Apc_target::Apc_call -{ -public: - THD *target_thd; /* thd that we're running SHOW EXPLAIN for */ - THD *request_thd; /* thd that run SHOW EXPLAIN command */ - - /* If true, there was some error when producing EXPLAIN output. */ - bool failed_to_produce; - - /* SHOW EXPLAIN will be stored here */ - select_result_explain_buffer *explain_buf; - - /* Query that we've got SHOW EXPLAIN for */ - String query_str; - - /* Overloaded virtual function */ - void call_in_target_thread(); -}; - class THD; -void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id); #ifndef DBUG_OFF void dbug_serve_apcs(THD *thd, int n_calls); #endif @@ -2222,6 +2190,7 @@ public: */ killed_state volatile killed; + /* See also thd_killed() */ inline bool check_killed() { if (killed) @@ -2438,7 +2407,8 @@ public: Allows this thread to serve as a target for others to schedule Async Procedure Calls on. - It's possible to schedule arbitrary C++ function calls. Currently, only + It's possible to schedule any code to be executed this way, by + inheriting from the Apc_call object. Currently, only Show_explain_request uses this. */ Apc_target apc_target; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3a6bb4909f2..2bdc7952d73 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2155,9 +2155,9 @@ mysql_execute_command(THD *thd) */ if (lex->sroutines.records || lex->query_tables->next_global) { - my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored " - "function calls as part of this statement"); - break; + my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), + MYF(0)); + goto error; } Item **it= &(lex->show_explain_for_thread); diff --git a/sql/sql_show.cc b/sql/sql_show.cc index dc5f32b5728..94463b8293c 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -1999,8 +1999,50 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) } -static -const char *target_not_explainable_cmd="Target is not running EXPLAINable command"; +/* + Produce EXPLAIN data. + + This function is APC-scheduled to be run in the context of the thread that + we're producing EXPLAIN for. +*/ + +void Show_explain_request::call_in_target_thread() +{ + Query_arena backup_arena; + bool printed_anything= FALSE; + + /* + Change the arena because JOIN::print_explain and co. are going to allocate + items. Let them allocate them on our arena. + */ + target_thd->set_n_backup_active_arena((Query_arena*)request_thd, + &backup_arena); + + query_str.copy(target_thd->query(), + target_thd->query_length(), + &my_charset_bin); + + if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, + &printed_anything)) + { + failed_to_produce= TRUE; + } + + if (!printed_anything) + failed_to_produce= TRUE; + + target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena); +} + + +int select_result_explain_buffer::send_data(List &items) +{ + fill_record(thd, dst_table->field, items, TRUE, FALSE); + if ((dst_table->file->ha_write_tmp_row(dst_table->record[0]))) + return 1; + return 0; +} + /* Store the SHOW EXPLAIN output in the temporary table. @@ -2048,7 +2090,7 @@ int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) if (calling_user && (!tmp_sctx->user || strcmp(calling_user, tmp_sctx->user))) { - my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESSLIST"); + my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS"); mysql_mutex_unlock(&tmp->LOCK_thd_data); DBUG_RETURN(1); } @@ -2056,8 +2098,7 @@ int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) if (tmp == thd) { mysql_mutex_unlock(&tmp->LOCK_thd_data); - my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SHOW EXPLAIN", - target_not_explainable_cmd); + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); DBUG_RETURN(1); } @@ -2084,21 +2125,12 @@ int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) if (bres || explain_req.failed_to_produce) { if (thd->killed) - { thd->send_kill_message(); - } - else - if (timed_out) - { - my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), - "SHOW EXPLAIN", - "Timeout"); - } + else if (timed_out) + my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); else - { - my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), - "SHOW EXPLAIN", target_not_explainable_cmd); - } + my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0)); + bres= TRUE; } else @@ -8427,7 +8459,32 @@ ST_FIELD_INFO keycache_fields_info[]= }; -extern ST_FIELD_INFO show_explain_fields_info[]; +ST_FIELD_INFO show_explain_fields_info[]= +{ + /* field_name, length, type, value, field_flags, old_name*/ + {"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id", + SKIP_OPEN_TABLE}, + {"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type", + SKIP_OPEN_TABLE}, + {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, + "table", SKIP_OPEN_TABLE}, + {"type", 10, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, + {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, + {"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, + "key", SKIP_OPEN_TABLE}, + {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE}, + {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE}, + {"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows", + SKIP_OPEN_TABLE}, + {"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra", + SKIP_OPEN_TABLE}, + {0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE} +}; + + /* Description of ST_FIELD_INFO in table.h diff --git a/sql/sql_show.h b/sql/sql_show.h index 8ad9327c08c..6ccb6e03872 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -19,6 +19,7 @@ #include "sql_list.h" /* List */ #include "handler.h" /* enum_schema_tables */ #include "table.h" /* enum_schema_table_state */ +#include "my_apc.h" /* Forward declarations */ class JOIN; @@ -132,4 +133,28 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); /* These functions were under INNODB_COMPATIBILITY_HOOKS */ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); +class select_result_explain_buffer; +/* + SHOW EXPLAIN request object. +*/ + +class Show_explain_request : public Apc_target::Apc_call +{ +public: + THD *target_thd; /* thd that we're running SHOW EXPLAIN for */ + THD *request_thd; /* thd that run SHOW EXPLAIN command */ + + /* If true, there was some error when producing EXPLAIN output. */ + bool failed_to_produce; + + /* SHOW EXPLAIN will be stored here */ + select_result_explain_buffer *explain_buf; + + /* Query that we've got SHOW EXPLAIN for */ + String query_str; + + /* Overloaded virtual function */ + void call_in_target_thread(); +}; + #endif /* SQL_SHOW_H */ diff --git a/unittest/sql/my_apc-t.cc b/unittest/sql/my_apc-t.cc index 3b837b4700e..8b599198302 100644 --- a/unittest/sql/my_apc-t.cc +++ b/unittest/sql/my_apc-t.cc @@ -90,7 +90,7 @@ void *test_apc_service_thread(void *ptr) apc_target.init(&target_mutex); apc_target.enable(); started= TRUE; - fprintf(stderr, "# test_apc_service_thread started\n"); + diag("test_apc_service_thread started"); while (!service_should_exit) { //apc_target.disable(); @@ -137,7 +137,7 @@ public: void *test_apc_requestor_thread(void *ptr) { my_thread_init(); - fprintf(stderr, "# test_apc_requestor_thread started\n"); + diag("test_apc_requestor_thread started"); THD my_thd; while (!requestors_should_exit) @@ -159,7 +159,7 @@ void *test_apc_requestor_thread(void *ptr) if (dst_value != 0) { - fprintf(stderr, "APC was done even though return value says it wasnt!\n"); + diag("APC was done even though return value says it wasnt!"); have_errors= true; } } @@ -167,13 +167,13 @@ void *test_apc_requestor_thread(void *ptr) { if (dst_value != src_value) { - fprintf(stderr, "APC was not done even though return value says it was!\n"); + diag("APC was not done even though return value says it was!"); have_errors= true; } } //my_sleep(300); } - fprintf(stderr, "# test_apc_requestor_thread exiting\n"); + diag("test_apc_requestor_thread exiting"); my_thread_end(); return NULL; } @@ -204,20 +204,20 @@ int main(int args, char **argv) for (i = 0; i < 15; i++) { my_sleep(500*1000); - fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed); + diag("%d APCs served %d missed", apcs_served, apcs_missed); } - fprintf(stderr, "# Shutting down requestors\n"); + diag("Shutting down requestors"); requestors_should_exit= TRUE; for (i = 0; i < N_THREADS; i++) pthread_join(request_thr[i], NULL); - fprintf(stderr, "# Shutting down service\n"); + diag("Shutting down service"); service_should_exit= TRUE; pthread_join(service_thr, NULL); mysql_mutex_destroy(&apc_counters_mutex); - fprintf(stderr, "# Done.\n"); + diag("Done"); my_thread_end(); my_thread_global_end(); From 879069cadaff8ee7f89f6a566ce37dc39d356e13 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 17 Jul 2012 23:27:03 +0400 Subject: [PATCH 55/67] MWL#182: Explain running statements Address feedback from the second code review: - Make SHOW EXPLAIN code convert the query string before pushing it as a warning text. --- sql/sql_show.cc | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 94463b8293c..03bed2689df 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2020,7 +2020,7 @@ void Show_explain_request::call_in_target_thread() query_str.copy(target_thd->query(), target_thd->query_length(), - &my_charset_bin); + target_thd->query_charset()); if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, &printed_anything)) @@ -2135,8 +2135,35 @@ int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) } else { + /* + Push the query string as a warning. The query may be in a different + charset than the charset that's used for error messages, so, convert it + if needed. + */ + CHARSET_INFO *fromcs= explain_req.query_str.charset(); + CHARSET_INFO *tocs= error_message_charset_info; + char *warning_text; + if (!my_charset_same(fromcs, tocs)) + { + uint conv_length= 1 + tocs->mbmaxlen * explain_req.query_str.length() / + fromcs->mbminlen; + uint dummy_errors; + char *to, *p; + if (!(to= (char*)thd->alloc(conv_length + 1))) + DBUG_RETURN(1); + p= to; + p+= copy_and_convert(to, conv_length, tocs, + explain_req.query_str.c_ptr(), + explain_req.query_str.length(), fromcs, + &dummy_errors); + *p= 0; + warning_text= to; + } + else + warning_text= explain_req.query_str.c_ptr_safe(); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_YES, explain_req.query_str.c_ptr_safe()); + ER_YES, warning_text); } DBUG_RETURN(bres); } From 678b4f5dea6edb146fc1877b384d602f5afcac03 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 18 Jul 2012 20:51:41 +0400 Subject: [PATCH 56/67] Fix the tests: show_explain_ps cannot work on embedded server. --- mysql-test/t/show_explain_ps.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mysql-test/t/show_explain_ps.test b/mysql-test/t/show_explain_ps.test index d891e118b24..4ad1e4304de 100644 --- a/mysql-test/t/show_explain_ps.test +++ b/mysql-test/t/show_explain_ps.test @@ -3,6 +3,8 @@ # --source include/have_debug.inc --source include/have_perfschema.inc +# Like all other perfschema tests, we don't work on embedded server: +--source include/not_embedded.inc --disable_warnings drop table if exists t0, t1; From ab70b76d9c4eaafde3a4c639bd280d2ccb1d2999 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 19 Jul 2012 15:52:19 +0400 Subject: [PATCH 57/67] BUG#992942 & MDEV-325: Pre-liminary commit for testing --- mysql-test/r/show_explain.result | 60 +++++++++++++- mysql-test/t/show_explain.test | 56 +++++++++++-- sql/sql_select.cc | 134 +++++++++++++++++++++++++++++-- sql/sql_select.h | 10 +++ sql/sql_show.cc | 2 +- 5 files changed, 250 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 2851e775280..d4161a6ac06 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -825,9 +825,67 @@ pk data 19 data1 20 data1 drop table t1; -drop table t0; # # Check that the I_S table is invisible # select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%'; table_name +# +# MDEV-325: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN is different from standard EXPLAIN: type ALL vs 'index_merge'.. +# +CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(8,0),(128,5050),(5372,8),(234,7596),(2,0),(2907,8930),(1,0), +(0,5224),(8,7638),(960,5),(9872,1534),(0,2295),(3408,9809), +(7,0),(1168,0),(2089,5570),(0,205),(88,1018),(0,26528), +(0,0),(4,5567),(1444,145),(6,0),(1,7535),(7793,534),(70,9), +(178,1),(44,5),(189,0),(3,0); +EXPLAIN +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge a,b a,b 5,5 NULL 8 Using sort_union(a,b); Using where; Using filesort +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge a,b a,b 5,5 NULL 8 Using sort_union(a,b); Using where; Using filesort +Warnings: +Note 1003 SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b +a+SLEEP(0.01) +0 +5372 +70 +0 +0 +0 +0 +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index_merge a,b a,b 5,5 NULL 8 Using sort_union(a,b); Using where; Using filesort +Warnings: +Note 1003 SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b +a+SLEEP(0.01) +0 +5372 +70 +0 +0 +0 +0 +set debug_dbug=''; +drop table t1; +drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 2cb3ab6924a..61d1a9398c4 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -837,6 +837,7 @@ select * from t1 where pk between 10 and 20 for update; # run SHOW EXPLAIN on a frozen thread connection default; +let $save_wait_condition= $wait_condition; let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2; let $thr_default=`select connection_id()`; --source include/wait_condition.inc @@ -864,14 +865,59 @@ reap; drop table t1; disconnect con3; disconnect con2; - -## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a -## thread and served together. - -drop table t0; +let $wait_condition= $save_wait_condition; --echo # --echo # Check that the I_S table is invisible --echo # select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%'; +--echo # +--echo # MDEV-325: SHOW EXPLAIN: Plan produced by SHOW EXPLAIN is different from standard EXPLAIN: type ALL vs 'index_merge'.. +--echo # +CREATE TABLE t1 (a INT, b INT, KEY(a), KEY(b)) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(8,0),(128,5050),(5372,8),(234,7596),(2,0),(2907,8930),(1,0), +(0,5224),(8,7638),(960,5),(9872,1534),(0,2295),(3408,9809), +(7,0),(1168,0),(2089,5570),(0,205),(88,1018),(0,26528), +(0,0),(4,5567),(1444,145),(6,0),(1,7535),(7793,534),(70,9), +(178,1),(44,5),(189,0),(3,0); + +EXPLAIN +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +--send +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +--send +SELECT a+SLEEP(0.01) FROM t1 +WHERE a IN ( 255, 0 ) OR b BETWEEN 6 AND 129 +ORDER BY b; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +drop table t1; +drop table t0; + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 486a55cf50e..599590cc313 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10686,6 +10686,9 @@ void JOIN::cleanup(bool full) */ if (full) { + if (pre_sort_join_tab) + clean_pre_sort_join_tab(); + if (tmp_join) tmp_table_param.copy_field= 0; group_fields.delete_elements(); @@ -18908,6 +18911,8 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, TABLE *table; SQL_SELECT *select; JOIN_TAB *tab; + int err= 0; + bool quick_created= FALSE; DBUG_ENTER("create_sort_index"); if (join->table_count == join->const_tables) @@ -18915,6 +18920,43 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, tab= join->join_tab + join->const_tables; table= tab->table; select= tab->select; + + JOIN_TAB *save_pre_sort_join_tab= NULL; + if (join->pre_sort_join_tab) + { + /* + we've already been in this function, and stashed away the original access + method in join->pre_sort_join_tab, restore it now. + */ + + /* First, restore state of the handler */ + if (join->pre_sort_index != MAX_KEY) + { + if (table->file->ha_index_or_rnd_end()) + goto err; + if (join->pre_sort_idx_pushed_cond) + { + table->file->idx_cond_push(join->pre_sort_index, + join->pre_sort_idx_pushed_cond); + } + } + else + { + if (table->file->ha_index_or_rnd_end() || + table->file->ha_rnd_init(TRUE)) + goto err; + } + + /* Second, restore access method parameters */ + tab->records= join->pre_sort_join_tab->records; + tab->select= join->pre_sort_join_tab->select; + tab->select_cond= join->pre_sort_join_tab->select_cond; + tab->type= join->pre_sort_join_tab->type; + tab->read_first_record= join->pre_sort_join_tab->read_first_record; + + save_pre_sort_join_tab= join->pre_sort_join_tab; + join->pre_sort_join_tab= NULL; + } /* Currently ORDER BY ... LIMIT is not supported in subqueries. */ DBUG_ASSERT(join->group_list || !join->is_in_subquery()); @@ -18970,6 +19012,7 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, get_quick_select_for_ref(thd, table, &tab->ref, tab->found_records)))) goto err; + quick_created= TRUE; } } @@ -18985,7 +19028,37 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, table->sort.found_records=filesort(thd, table,join->sortorder, length, select, filesort_limit, 0, &examined_rows); + + if (quick_created) + { + /* This will delete the quick select. */ + select->cleanup(); + } + + if (!join->pre_sort_join_tab) + { + if (save_pre_sort_join_tab) + join->pre_sort_join_tab= save_pre_sort_join_tab; + else if (!(join->pre_sort_join_tab= (JOIN_TAB*)thd->alloc(sizeof(JOIN_TAB)))) + goto err; + } + + *(join->pre_sort_join_tab)= *tab; + + if (table->file->inited == handler::INDEX) + { + // Save index #, save index condition + join->pre_sort_index= table->file->active_index; + join->pre_sort_idx_pushed_cond= table->file->pushed_idx_cond; + // no need to save key_read? + err= table->file->ha_index_end(); + } + else + join->pre_sort_index= MAX_KEY; + + /*TODO: here, close the index scan, cancel index-only read. */ tab->records= table->sort.found_records; // For SQL_CALC_ROWS +#if 0 if (select) { /* @@ -19002,23 +19075,65 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, tablesort_result_cache= table->sort.io_cache; table->sort.io_cache= NULL; - select->cleanup(); // filesort did select + // select->cleanup(); // filesort did select table->quick_keys.clear_all(); // as far as we cleanup select->quick table->intersect_keys.clear_all(); table->sort.io_cache= tablesort_result_cache; } +#endif + tab->select=NULL; tab->set_select_cond(NULL, __LINE__); - tab->last_inner= 0; - tab->first_unmatched= 0; +// tab->last_inner= 0; +// tab->first_unmatched= 0; tab->type=JT_ALL; // Read with normal read_record tab->read_first_record= join_init_read_record; + tab->table->file->ha_index_or_rnd_end(); + + if (err) + goto err; + tab->join->examined_rows+=examined_rows; - table->disable_keyread(); // Restore if we used indexes DBUG_RETURN(table->sort.found_records == HA_POS_ERROR); err: DBUG_RETURN(-1); } +void JOIN::clean_pre_sort_join_tab() +{ + TABLE *table= pre_sort_join_tab->table; + /* + Note: we can come here for fake_select_lex object. That object will have + the table already deleted by st_select_lex_unit::cleanup(). + We rely on that fake_select_lex didn't have quick select. + */ +#if 0 + if (pre_sort_join_tab->select && pre_sort_join_tab->select->quick) + { + /* + We need to preserve tablesort's output resultset here, because + QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT (called by + SQL_SELECT::cleanup()) may free it assuming it's the result of the quick + select operation that we no longer need. Note that all the other parts of + this data structure are cleaned up when + QUICK_INDEX_MERGE_SELECT::get_next encounters end of data, so the next + SQL_SELECT::cleanup() call changes sort.io_cache alone. + */ + IO_CACHE *tablesort_result_cache; + + tablesort_result_cache= table->sort.io_cache; + table->sort.io_cache= NULL; + pre_sort_join_tab->select->cleanup(); + table->quick_keys.clear_all(); // as far as we cleanup select->quick + table->intersect_keys.clear_all(); + table->sort.io_cache= tablesort_result_cache; + } +#endif + //table->disable_keyread(); // Restore if we used indexes + if (pre_sort_join_tab->select && pre_sort_join_tab->select->quick) + { + pre_sort_join_tab->select->cleanup(); + } +} /***************************************************************************** Remove duplicates from tmp table This should be recoded to add a unique index to the table and remove @@ -21502,6 +21617,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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) @@ -21510,6 +21626,12 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, continue; } + if (tab == first_top_tab && 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)); @@ -21656,7 +21778,6 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, keylen_str_buf); tmp3.append(keylen_str_buf, length, cs); /*<<<<<<< TREE - } if ((is_hj || tab->type==JT_RANGE || tab->type == JT_INDEX_MERGE) && tab->select && tab->select->quick) =======*/ @@ -21977,6 +22098,9 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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)) diff --git a/sql/sql_select.h b/sql/sql_select.h index b78ac10d35f..d0677f069e7 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -903,6 +903,14 @@ protected: public: JOIN_TAB *join_tab, **best_ref; + + /* + Saved join_tab for pre_sorting. create_sort_index() will save here.. + */ + JOIN_TAB *pre_sort_join_tab; + uint pre_sort_index; + Item *pre_sort_idx_pushed_cond; + void clean_pre_sort_join_tab(); /* For "Using temporary+Using filesort" queries, JOIN::join_tab can point to @@ -1293,6 +1301,8 @@ public: outer_ref_cond= pseudo_bits_cond= NULL; in_to_exists_where= NULL; in_to_exists_having= NULL; + + pre_sort_join_tab= NULL; } int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 03bed2689df..bc8e01491e6 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -8495,7 +8495,7 @@ ST_FIELD_INFO show_explain_fields_info[]= SKIP_OPEN_TABLE}, {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, "table", SKIP_OPEN_TABLE}, - {"type", 10, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, + {"type", 11, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, {"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, From 8950ed8fdd7e19c8f49a5f0e6ea0c46a92d59e06 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 20 Jul 2012 01:52:03 +0400 Subject: [PATCH 58/67] MDEV-298: SHOW EXPLAIN: Plan returned by SHOW EXPLAIN only contains 'Using temporary' ... - Correct the way SHOW EXPLAIN code calculates 'need_order' parameter for JOIN::print_explain(). The calculation is still an approximation (see MDEV entry for details) (even EXPLAIN itself is wrong in certain cases), but now it's a better approximation than before. --- mysql-test/r/show_explain.result | 38 ++++++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 28 +++++++++++++++++++++++ sql/sql_lex.cc | 3 ++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index d4161a6ac06..c6a740353df 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -888,4 +888,42 @@ a+SLEEP(0.01) 0 set debug_dbug=''; drop table t1; +# +# MDEV-298: SHOW EXPLAIN: Plan returned by SHOW EXPLAIN only contains +# 'Using temporary' while the standard EXPLAIN says 'Using temporary; Using filesort' +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9), +(10),(11),(12),(13),(14),(15),(16); +INSERT INTO t1 SELECT t11.a FROM t1 t11, t1 t12, t1 t13; +EXPLAIN SELECT a FROM t1 GROUP BY a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4112 Using temporary; Using filesort +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SELECT a FROM t1 GROUP BY a; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 4112 Using temporary; Using filesort +Warnings: +Note 1003 SELECT a FROM t1 GROUP BY a +a +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +set debug_dbug=''; +drop table t1; drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 61d1a9398c4..43c2f617214 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -919,5 +919,33 @@ reap; set debug_dbug=''; drop table t1; + +--echo # +--echo # MDEV-298: SHOW EXPLAIN: Plan returned by SHOW EXPLAIN only contains +--echo # 'Using temporary' while the standard EXPLAIN says 'Using temporary; Using filesort' +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9), + (10),(11),(12),(13),(14),(15),(16); +INSERT INTO t1 SELECT t11.a FROM t1 t11, t1 t12, t1 t13; + +EXPLAIN SELECT a FROM t1 GROUP BY a; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +--send +SELECT a FROM t1 GROUP BY a; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +drop table t1; + drop table t0; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index f918439d45a..5ff7705706e 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4100,7 +4100,8 @@ int st_select_lex::print_explain(select_result_sink *output, { res= join->print_explain(output, explain_flags, TRUE, join->need_tmp, // need_tmp_table - (join->order != 0 && !join->skip_sort_order), // bool need_order + !join->skip_sort_order && !join->no_order && + (join->order || join->group_list), // bool need_order join->select_distinct, // bool distinct NULL); //const char *message } From 3956950b9f74e51ac1a16aef985a5806a662816e Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 24 Jul 2012 14:02:53 +0400 Subject: [PATCH 59/67] MDEV-408: SHOW EXPLAIN: Some values are chopped off in SHOW EXPLAIN output - Fix I_S table definition for EXPLAIN output. --- mysql-test/r/show_explain.result | 25 +++++++++++++++++++++++++ mysql-test/t/show_explain.test | 29 ++++++++++++++++++++++++++++- sql/sql_show.cc | 6 +++--- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index c6a740353df..0d0790c473c 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -926,4 +926,29 @@ a 16 set debug_dbug=''; drop table t1; +# +# MDEV-408: SHOW EXPLAIN: Some values are chopped off in SHOW EXPLAIN output +# +CREATE TABLE t1 (a INT, b VARCHAR(35)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (3989,'Abilene'),(3873,'Akron'); +CREATE TABLE t2 (c INT, d VARCHAR(52) PRIMARY KEY, KEY(c)) ENGINE=InnoDB; +INSERT INTO t2 VALUES (86,'English'),(87,'Russian'); +explain SELECT SUM(a + SLEEP(0.1)) FROM t1 WHERE a IN ( SELECT c FROM t2 WHERE d < b ) OR b < 's'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2 index_subquery PRIMARY,c c 5 func 1 Using index; Using where +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +SELECT SUM(a + SLEEP(0.1)) FROM t1 WHERE a IN ( SELECT c FROM t2 WHERE d < b ) OR b < 's'; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2 index_subquery PRIMARY,c c 5 func 1 Using index; Using where +Warnings: +Note 1003 SELECT SUM(a + SLEEP(0.1)) FROM t1 WHERE a IN ( SELECT c FROM t2 WHERE d < b ) OR b < 's' +SUM(a + SLEEP(0.1)) +7862 +set debug_dbug=''; +drop table t1, t2; +# End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 43c2f617214..4b2cf187089 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -947,5 +947,32 @@ set debug_dbug=''; drop table t1; -drop table t0; +--echo # +--echo # MDEV-408: SHOW EXPLAIN: Some values are chopped off in SHOW EXPLAIN output +--echo # +CREATE TABLE t1 (a INT, b VARCHAR(35)) ENGINE=InnoDB; +INSERT INTO t1 VALUES (3989,'Abilene'),(3873,'Akron'); +CREATE TABLE t2 (c INT, d VARCHAR(52) PRIMARY KEY, KEY(c)) ENGINE=InnoDB; +INSERT INTO t2 VALUES (86,'English'),(87,'Russian'); + +explain SELECT SUM(a + SLEEP(0.1)) FROM t1 WHERE a IN ( SELECT c FROM t2 WHERE d < b ) OR b < 's'; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +--send +SELECT SUM(a + SLEEP(0.1)) FROM t1 WHERE a IN ( SELECT c FROM t2 WHERE d < b ) OR b < 's'; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +drop table t1, t2; + +--echo # End +drop table t0; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index bc8e01491e6..c3a4e924de5 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -8495,11 +8495,11 @@ ST_FIELD_INFO show_explain_fields_info[]= SKIP_OPEN_TABLE}, {"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL, "table", SKIP_OPEN_TABLE}, - {"type", 11, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, + {"type", 15, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE}, {"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE}, - {"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, - "key", SKIP_OPEN_TABLE}, + {"key", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, + MY_I_S_MAYBE_NULL, "key", SKIP_OPEN_TABLE}, {"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE}, {"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/, From 0b79fe2b30e7a82a823e7c134eb69cfc0c078f95 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 25 Jul 2012 13:00:39 +0400 Subject: [PATCH 60/67] MDEV-412: SHOW EXPLAIN: Server crashes in JOIN::print_explain on a query with inner join and ORDER BY the same column twice - JOIN::print_explain should print pre_sort_join_tab instead of "first non-constant table". The code didn't take the "non-constant" part into account. --- mysql-test/r/show_explain.result | 48 +++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 52 ++++++++++++++++++++++++++++++++ sql/sql_select.cc | 2 +- 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 0d0790c473c..6ff9a0d0f4e 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -950,5 +950,53 @@ SUM(a + SLEEP(0.1)) 7862 set debug_dbug=''; drop table t1, t2; +# +# MDEV-412: SHOW EXPLAIN: Server crashes in JOIN::print_explain on a query with inner join and ORDER BY the same column twice +# +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(3), KEY(b)) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(3795,'USA'),(3913,'USA'),(3846,'ITA'),(4021,'USA'),(4005,'RUS'),(4038,'USA'), +(3825,'USA'),(3840,'USA'),(3987,'USA'),(3807,'USA'),(3896,'USA'),(4052,'USA'), +(3973,'USA'),(3982,'ITA'),(3965,'USA'),(3852,'RUS'),(4006,'USA'),(3800,'USA'), +(4020,'USA'),(4040,'USA'),(3916,'USA'),(3817,'USA'),(3885,'USA'),(3802,'USA'), +(4009,'ITA'),(3895,'USA'),(3963,'RUS'),(4045,'USA'),(3988,'USA'),(3815,'USA'), +(4063,'USA'),(3978,'USA'),(4019,'USA'),(3954,'USA'),(3950,'USA'),(3974,'ITA'), +(4054,'USA'),(4061,'RUS'),(3976,'USA'),(3966,'USA'),(3957,'USA'),(3981,'USA'), +(3923,'USA'),(3876,'USA'),(3819,'USA'),(3877,'USA'),(3829,'ITA'),(3964,'USA'), +(4053,'RUS'),(3917,'USA'),(3874,'USA'),(4023,'USA'),(4001,'USA'),(3872,'USA'), +(3890,'USA'),(3962,'USA'),(3886,'USA'),(4026,'ITA'),(3869,'USA'),(3937,'RUS'), +(3975,'USA'),(3944,'USA'),(3908,'USA'),(3867,'USA'),(3947,'USA'),(3838,'USA'), +(3796,'USA'),(3893,'USA'),(3920,'ITA'),(3994,'USA'),(3875,'RUS'),(4011,'USA'), +(4013,'USA'),(3810,'USA'),(3834,'USA'),(3968,'USA'),(3931,'USA'),(3839,'USA'), +(4042,'USA'),(4039,'ITA'),(3811,'USA'),(3837,'RUS'),(4041,'USA'),(3884,'USA'), +(3894,'USA'),(3879,'USA'),(3942,'USA'),(3959,'USA'),(3814,'USA'),(4044,'USA'), +(3971,'ITA'),(3823,'USA'),(3793,'RUS'),(3855,'USA'),(3905,'USA'),(3865,'USA'), +(4046,'USA'),(3990,'USA'),(4022,'USA'),(3833,'USA'),(3918,'USA'),(4064,'ITA'), +(3821,'USA'),(3836,'RUS'),(3921,'USA'),(3914,'USA'),(3888,'USA'); +CREATE TABLE t2 (c VARCHAR(3) PRIMARY KEY) ENGINE=MyISAM; +INSERT INTO t2 VALUES ('USA'); +CREATE TABLE t3 (d VARCHAR(3), e VARCHAR(52), PRIMARY KEY (d,e)) ENGINE=MyISAM; +INSERT INTO t3 VALUES +('JPN','Japanese'),('KOR','Korean'),('POL','Polish'),('PRT','Portuguese'), +('ESP','Spanish'),('FRA','French'),('VNM','Vietnamese'); +explain +SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY field1, field2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 system NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t1 index b b 6 NULL 107 Using where; Using index +1 SIMPLE t3 ref PRIMARY PRIMARY 5 test.t1.b 1 Using index +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY field1, field2; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 system NULL NULL NULL NULL 1 Using filesort +1 SIMPLE t1 index b b 6 NULL 107 Using where; Using index +1 SIMPLE t3 ref PRIMARY PRIMARY 5 test.t1.b 1 Using index +Warnings: +Note 1003 SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY field1, field2 +field1 field2 +set debug_dbug=''; +DROP TABLE t1,t2,t3; # End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 4b2cf187089..24b01402287 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -974,5 +974,57 @@ set debug_dbug=''; drop table t1, t2; +--echo # +--echo # MDEV-412: SHOW EXPLAIN: Server crashes in JOIN::print_explain on a query with inner join and ORDER BY the same column twice +--echo # +CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(3), KEY(b)) ENGINE=MyISAM; +INSERT INTO t1 VALUES +(3795,'USA'),(3913,'USA'),(3846,'ITA'),(4021,'USA'),(4005,'RUS'),(4038,'USA'), +(3825,'USA'),(3840,'USA'),(3987,'USA'),(3807,'USA'),(3896,'USA'),(4052,'USA'), +(3973,'USA'),(3982,'ITA'),(3965,'USA'),(3852,'RUS'),(4006,'USA'),(3800,'USA'), +(4020,'USA'),(4040,'USA'),(3916,'USA'),(3817,'USA'),(3885,'USA'),(3802,'USA'), +(4009,'ITA'),(3895,'USA'),(3963,'RUS'),(4045,'USA'),(3988,'USA'),(3815,'USA'), +(4063,'USA'),(3978,'USA'),(4019,'USA'),(3954,'USA'),(3950,'USA'),(3974,'ITA'), +(4054,'USA'),(4061,'RUS'),(3976,'USA'),(3966,'USA'),(3957,'USA'),(3981,'USA'), +(3923,'USA'),(3876,'USA'),(3819,'USA'),(3877,'USA'),(3829,'ITA'),(3964,'USA'), +(4053,'RUS'),(3917,'USA'),(3874,'USA'),(4023,'USA'),(4001,'USA'),(3872,'USA'), +(3890,'USA'),(3962,'USA'),(3886,'USA'),(4026,'ITA'),(3869,'USA'),(3937,'RUS'), +(3975,'USA'),(3944,'USA'),(3908,'USA'),(3867,'USA'),(3947,'USA'),(3838,'USA'), +(3796,'USA'),(3893,'USA'),(3920,'ITA'),(3994,'USA'),(3875,'RUS'),(4011,'USA'), +(4013,'USA'),(3810,'USA'),(3834,'USA'),(3968,'USA'),(3931,'USA'),(3839,'USA'), +(4042,'USA'),(4039,'ITA'),(3811,'USA'),(3837,'RUS'),(4041,'USA'),(3884,'USA'), +(3894,'USA'),(3879,'USA'),(3942,'USA'),(3959,'USA'),(3814,'USA'),(4044,'USA'), +(3971,'ITA'),(3823,'USA'),(3793,'RUS'),(3855,'USA'),(3905,'USA'),(3865,'USA'), +(4046,'USA'),(3990,'USA'),(4022,'USA'),(3833,'USA'),(3918,'USA'),(4064,'ITA'), +(3821,'USA'),(3836,'RUS'),(3921,'USA'),(3914,'USA'),(3888,'USA'); + +CREATE TABLE t2 (c VARCHAR(3) PRIMARY KEY) ENGINE=MyISAM; +INSERT INTO t2 VALUES ('USA'); + +CREATE TABLE t3 (d VARCHAR(3), e VARCHAR(52), PRIMARY KEY (d,e)) ENGINE=MyISAM; +INSERT INTO t3 VALUES +('JPN','Japanese'),('KOR','Korean'),('POL','Polish'),('PRT','Portuguese'), +('ESP','Spanish'),('FRA','French'),('VNM','Vietnamese'); + +explain +SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY field1, field2; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; + +send +SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY field1, field2; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +DROP TABLE t1,t2,t3; + --echo # End drop table t0; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 599590cc313..11a2e7b6639 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -21626,7 +21626,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, continue; } - if (tab == first_top_tab && pre_sort_join_tab) + if (tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) { saved_join_tab= tab; tab= pre_sort_join_tab; From aaa188dad935865e52663c34afac1af983362526 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Fri, 27 Jul 2012 16:17:52 +0400 Subject: [PATCH 61/67] Post-merge fixes. --- mysql-test/r/subselect.result | 2 +- mysql-test/r/subselect_innodb.result | 4 ++-- mysql-test/r/subselect_mat_cost_bugs.result | 2 +- mysql-test/r/subselect_no_mat.result | 2 +- mysql-test/r/subselect_no_opts.result | 2 +- mysql-test/r/subselect_no_scache.result | 2 +- mysql-test/r/subselect_no_semijoin.result | 2 +- sql/sql_select.cc | 19 +++++++++++-------- 8 files changed, 19 insertions(+), 16 deletions(-) diff --git a/mysql-test/r/subselect.result b/mysql-test/r/subselect.result index 3ea6c0be398..8c71c09c1ac 100644 --- a/mysql-test/r/subselect.result +++ b/mysql-test/r/subselect.result @@ -4196,7 +4196,7 @@ INSERT INTO t1 VALUES (1,1),(2,1); EXPLAIN SELECT 1 FROM t1 WHERE a = (SELECT COUNT(*) FROM t1 GROUP BY b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ref a a 5 const 1 Using where; Using index -2 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +2 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort DROP TABLE t1; CREATE TABLE t1 (id int NOT NULL, st CHAR(2), INDEX idx(id)); INSERT INTO t1 VALUES diff --git a/mysql-test/r/subselect_innodb.result b/mysql-test/r/subselect_innodb.result index a0f05a26a46..a4670872fad 100644 --- a/mysql-test/r/subselect_innodb.result +++ b/mysql-test/r/subselect_innodb.result @@ -333,7 +333,7 @@ WHERE (SELECT DISTINCT b FROM t3) > 0); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 const PRIMARY PRIMARY 4 const 1 Using where; Using index 2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE -3 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 0 Using temporary +3 SUBQUERY t3 ALL NULL NULL NULL NULL 1 Using temporary SELECT * FROM t1 WHERE t1.a = ( @@ -386,7 +386,7 @@ select 1 from t1 where 1 like (select 1 from t1 where 1 <=> (select 1 from t1 gr id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 1 2 SUBQUERY t1 ALL NULL NULL NULL NULL 1 -3 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +3 SUBQUERY t1 ALL NULL NULL NULL NULL 1 Using temporary; Using filesort select 1 from t1 where 1 like (select 1 from t1 where 1 <=> (select 1 from t1 group by a1)); 1 1 diff --git a/mysql-test/r/subselect_mat_cost_bugs.result b/mysql-test/r/subselect_mat_cost_bugs.result index 1d03f2e9fe6..8c95c8637aa 100644 --- a/mysql-test/r/subselect_mat_cost_bugs.result +++ b/mysql-test/r/subselect_mat_cost_bugs.result @@ -148,7 +148,7 @@ FROM t2 GROUP BY f1 id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables 2 SUBQUERY t1 system NULL NULL NULL NULL 1 -3 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +3 SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort drop table t1, t2, t3; # # LP BUG#715034 Item_sum_distinct::clear(): Assertion `tree != 0' failed diff --git a/mysql-test/r/subselect_no_mat.result b/mysql-test/r/subselect_no_mat.result index 8ce097787f1..48ab96dcaaa 100644 --- a/mysql-test/r/subselect_no_mat.result +++ b/mysql-test/r/subselect_no_mat.result @@ -4200,7 +4200,7 @@ INSERT INTO t1 VALUES (1,1),(2,1); EXPLAIN SELECT 1 FROM t1 WHERE a = (SELECT COUNT(*) FROM t1 GROUP BY b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ref a a 5 const 1 Using where; Using index -2 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +2 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort DROP TABLE t1; CREATE TABLE t1 (id int NOT NULL, st CHAR(2), INDEX idx(id)); INSERT INTO t1 VALUES diff --git a/mysql-test/r/subselect_no_opts.result b/mysql-test/r/subselect_no_opts.result index 466d8c8fd06..9ab4e23b091 100644 --- a/mysql-test/r/subselect_no_opts.result +++ b/mysql-test/r/subselect_no_opts.result @@ -4196,7 +4196,7 @@ INSERT INTO t1 VALUES (1,1),(2,1); EXPLAIN SELECT 1 FROM t1 WHERE a = (SELECT COUNT(*) FROM t1 GROUP BY b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ref a a 5 const 1 Using where; Using index -2 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +2 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort DROP TABLE t1; CREATE TABLE t1 (id int NOT NULL, st CHAR(2), INDEX idx(id)); INSERT INTO t1 VALUES diff --git a/mysql-test/r/subselect_no_scache.result b/mysql-test/r/subselect_no_scache.result index 1fde0d08d7e..d47aa956ead 100644 --- a/mysql-test/r/subselect_no_scache.result +++ b/mysql-test/r/subselect_no_scache.result @@ -4202,7 +4202,7 @@ INSERT INTO t1 VALUES (1,1),(2,1); EXPLAIN SELECT 1 FROM t1 WHERE a = (SELECT COUNT(*) FROM t1 GROUP BY b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ref a a 5 const 1 Using where; Using index -2 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +2 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort DROP TABLE t1; CREATE TABLE t1 (id int NOT NULL, st CHAR(2), INDEX idx(id)); INSERT INTO t1 VALUES diff --git a/mysql-test/r/subselect_no_semijoin.result b/mysql-test/r/subselect_no_semijoin.result index ba50b8522eb..96e6c2182fa 100644 --- a/mysql-test/r/subselect_no_semijoin.result +++ b/mysql-test/r/subselect_no_semijoin.result @@ -4196,7 +4196,7 @@ INSERT INTO t1 VALUES (1,1),(2,1); EXPLAIN SELECT 1 FROM t1 WHERE a = (SELECT COUNT(*) FROM t1 GROUP BY b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ref a a 5 const 1 Using where; Using index -2 SUBQUERY internal_tmp_table ALL group_key NULL NULL NULL 1 Using temporary; Using filesort +2 SUBQUERY t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort DROP TABLE t1; CREATE TABLE t1 (id int NOT NULL, st CHAR(2), INDEX idx(id)); INSERT INTO t1 VALUES diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 53bb42ca2e9..02be522290d 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -271,8 +271,11 @@ Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field, bool *inherited_fl); JOIN_TAB *first_depth_first_tab(JOIN* join); JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab); -JOIN_TAB *first_breadth_first_tab(JOIN *join); -JOIN_TAB *next_breadth_first_tab(JOIN *join, JOIN_TAB *tab); + +enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS}; +JOIN_TAB *first_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind); +JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, + JOIN_TAB *tab); #ifndef DBUG_OFF @@ -6730,12 +6733,12 @@ double JOIN::get_examined_rows() { ha_rows examined_rows; double prev_fanout= 1; - JOIN_TAB *tab= first_breadth_first_tab(this); + JOIN_TAB *tab= first_breadth_first_tab(this, WALK_OPTIMIZATION_TABS); JOIN_TAB *prev_tab= tab; examined_rows= tab->get_examined_rows(); - while ((tab= next_breadth_first_tab(this, tab))) + while ((tab= next_breadth_first_tab(this, WALK_OPTIMIZATION_TABS, tab))) { prev_fanout *= prev_tab->records_read; examined_rows+= tab->get_examined_rows() * prev_fanout; @@ -7345,7 +7348,6 @@ prev_record_reads(POSITION *positions, uint idx, table_map found_ref) return found; } -enum enum_exec_or_opt {WALK_OPTIMIZATION_TABS , WALK_EXECUTION_TABS}; /* Enumerate join tabs in breadth-first fashion, including const tables. @@ -10434,7 +10436,7 @@ ha_rows JOIN_TAB::get_examined_rows() { ha_rows examined_rows; - if (select && select->quick) + if (select && select->quick && use_quick != 2) examined_rows= select->quick->records; else if (type == JT_NEXT || type == JT_ALL || type == JT_HASH || type ==JT_HASH_NEXT) @@ -19182,7 +19184,7 @@ err: void JOIN::clean_pre_sort_join_tab() { - TABLE *table= pre_sort_join_tab->table; + //TABLE *table= pre_sort_join_tab->table; /* Note: we can come here for fake_select_lex object. That object will have the table already deleted by st_select_lex_unit::cleanup(). @@ -21710,7 +21712,8 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, continue; } - if (tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) + 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; From fd3e9c53c1b3c50ae85d020551915d5a31cd3044 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 1 Aug 2012 15:51:52 +0400 Subject: [PATCH 62/67] MDEV-423: SHOW EXPLAIN: 'Using where' for a subquery is shown in EXPLAIN, but not in SHOW EXPLAIN - Take into account that the optimizer may run the subquery, delete it, and then make a JOIN::optimize call again. --- mysql-test/r/show_explain.result | 34 +++++++++++++++++++++++++++++ mysql-test/t/show_explain.test | 37 ++++++++++++++++++++++++++++++++ sql/sql_select.cc | 10 +++++++-- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 6ff9a0d0f4e..2f7de62e7fe 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -998,5 +998,39 @@ Note 1003 SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY f field1 field2 set debug_dbug=''; DROP TABLE t1,t2,t3; +# +# MDEV-423: SHOW EXPLAIN: 'Using where' for a subquery is shown in EXPLAIN, but not in SHOW EXPLAIN output +# +CREATE TABLE t1 (a INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0),(9),(3),(4),(2),(5),(7),(0),(9),(3),(4),(2),(5); +CREATE TABLE t2 (b INT, c INT) ENGINE=MyISAM; +INSERT INTO t2 VALUES +(0,4),(8,6),(1,3),(8,5),(9,3),(24,246),(6,2),(1,9),(6,3),(2,8), +(4,1),(8,8),(4,8),(4,5),(7,7),(4,5),(1,1),(9,6),(4,2),(8,9); +create table t3 like t2; +insert into t3 select * from t2; +explain +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; +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 t3 ALL NULL NULL NULL NULL 20 Using where +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +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; +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 +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 +max(a+b+c) +279 +set debug_dbug=''; +DROP TABLE t1,t2,t3; # End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 24b01402287..485bda84cf5 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -1026,5 +1026,42 @@ set debug_dbug=''; DROP TABLE t1,t2,t3; + +--echo # +--echo # MDEV-423: SHOW EXPLAIN: 'Using where' for a subquery is shown in EXPLAIN, but not in SHOW EXPLAIN output +--echo # +CREATE TABLE t1 (a INT) ENGINE=MyISAM; +INSERT INTO t1 VALUES (7),(0),(9),(3),(4),(2),(5),(7),(0),(9),(3),(4),(2),(5); + +CREATE TABLE t2 (b INT, c INT) ENGINE=MyISAM; +INSERT INTO t2 VALUES +(0,4),(8,6),(1,3),(8,5),(9,3),(24,246),(6,2),(1,9),(6,3),(2,8), +(4,1),(8,8),(4,8),(4,5),(7,7),(4,5),(1,1),(9,6),(4,2),(8,9); + +create table t3 like t2; +insert into t3 select * from t2; + +explain +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; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; + +send +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; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; + +connection con1; +reap; + +set debug_dbug=''; + +DROP TABLE t1,t2,t3; + --echo # End drop table t0; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 02be522290d..89765ab44db 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -987,7 +987,13 @@ err: int JOIN::optimize() { int res= optimize_inner(); - if (!res) + /* + 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. + */ + if (!res && have_query_plan != QEP_DELETED) have_query_plan= QEP_AVAILABLE; return res; } @@ -21630,7 +21636,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, 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); + DBUG_ASSERT(on_the_fly? have_query_plan == QEP_AVAILABLE: TRUE); /* Don't log this into the slow query log */ if (!on_the_fly) From 59e64b6c9bdcfd576db5118e5e33df2c813233dd Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 2 Aug 2012 17:06:05 +0400 Subject: [PATCH 63/67] MDEV-416: Server crashes in SQL_SELECT::cleanup on EXPLAIN with SUM ( DISTINCT ) - When JOIN::cleanup(full==TRUE) is called, the select can be in two states: = Right after the create_sort_index() call, when join->join_tab[0] is used to read data produced by filesort(). = After create_sort_index(), and after JOIN::reinit() calls, when join->join_tab[0] has been reset to read the original data. - We didn't handle the second case correctly, which resulted in an attempt to free the same SQL_SELECT two times. The fix is to make sure we don't double-free. --- mysql-test/r/show_explain.result | 13 +++++++++++++ mysql-test/t/show_explain.test | 14 ++++++++++++++ sql/sql_select.cc | 16 +++++++++++++--- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 6ff9a0d0f4e..7f942c2e227 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -998,5 +998,18 @@ Note 1003 SELECT b AS field1, b AS field2 FROM t1, t2, t3 WHERE d = b ORDER BY f field1 field2 set debug_dbug=''; DROP TABLE t1,t2,t3; +# +# MDEV-416: Server crashes in SQL_SELECT::cleanup on EXPLAIN with SUM ( DISTINCT ) in a non-correlated subquery (5.5-show-explain tree) +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (8),(9); +EXPLAIN SELECT * FROM t1 +WHERE ( 8, 89 ) IN ( SELECT b, SUM( DISTINCT b ) FROM t2 GROUP BY b ); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +2 SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using filesort +DROP TABLE t1,t2; # End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 24b01402287..edc9907356d 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -1026,5 +1026,19 @@ set debug_dbug=''; DROP TABLE t1,t2,t3; +--echo # +--echo # MDEV-416: Server crashes in SQL_SELECT::cleanup on EXPLAIN with SUM ( DISTINCT ) in a non-correlated subquery (5.5-show-explain tree) +--echo # +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1),(2); + +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (8),(9); + +EXPLAIN SELECT * FROM t1 +WHERE ( 8, 89 ) IN ( SELECT b, SUM( DISTINCT b ) FROM t2 GROUP BY b ); + +DROP TABLE t1,t2; + --echo # End drop table t0; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 02be522290d..815ff7229a2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10731,9 +10731,22 @@ void JOIN::cleanup(bool full) if (full) { + JOIN_TAB *sort_tab= first_linear_tab(this, WITHOUT_CONST_TABLES); + if (pre_sort_join_tab) + { + if (sort_tab && sort_tab->select == pre_sort_join_tab->select) + { + pre_sort_join_tab->select= NULL; + } + else + clean_pre_sort_join_tab(); + } + for (tab= first_linear_tab(this, WITH_CONST_TABLES); tab; tab= next_linear_tab(this, tab, WITH_BUSH_ROOTS)) + { tab->cleanup(); + } } else { @@ -10755,9 +10768,6 @@ void JOIN::cleanup(bool full) */ if (full) { - if (pre_sort_join_tab) - clean_pre_sort_join_tab(); - if (tmp_join) tmp_table_param.copy_field= 0; group_fields.delete_elements(); From 0e5193435ae01ae9323448aa32570a50b23c8658 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Wed, 8 Aug 2012 21:24:00 +0400 Subject: [PATCH 64/67] MWL#182: Explain running statements: Address feedback: - Use LEX::value_list instead of LEX::show_explain_for_thread - Factor out common code into find_thread_by_id(ulong id) --- sql/sql_lex.h | 1 - sql/sql_parse.cc | 46 +++++++++++++++++++++++++++++++--------------- sql/sql_show.cc | 21 ++------------------- sql/sql_show.h | 1 + sql/sql_yacc.yy | 5 +++-- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index eac41a9f0c0..54f8740e210 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2360,7 +2360,6 @@ struct LEX: public Query_tables_list char* to_log; /* For PURGE MASTER LOGS TO */ char* x509_subject,*x509_issuer,*ssl_cipher; String *wild; /* Wildcard in SHOW {something} LIKE 'wild'*/ - Item *show_explain_for_thread; /* id in SHOW EXPLAIN FOR id */ sql_exchange *exchange; select_result *result; Item *default_value, *on_update_value; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 73dbe328761..16f195c44cd 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2165,7 +2165,7 @@ mysql_execute_command(THD *thd) goto error; } - Item **it= &(lex->show_explain_for_thread); + Item **it= lex->value_list.head_ref(); if ((!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || (*it)->check_cols(1)) { @@ -6585,6 +6585,35 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List *using_fields, } +/** + Find a thread by id and return it, locking it LOCK_thd_data + + @param id Identifier of the thread we're looking for + + @return NULL - not found + pointer - thread found, and its LOCK_thd_data is locked. +*/ + +THD *find_thread_by_id(ulong id) +{ + THD *tmp; + mysql_mutex_lock(&LOCK_thread_count); // For unlink from list + I_List_iterator it(threads); + while ((tmp=it++)) + { + if (tmp->command == COM_DAEMON) + continue; + if (tmp->thread_id == id) + { + mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete + break; + } + } + mysql_mutex_unlock(&LOCK_thread_count); + return tmp; +} + + /** kill on thread. @@ -6603,20 +6632,7 @@ uint kill_one_thread(THD *thd, ulong id, killed_state kill_signal) DBUG_ENTER("kill_one_thread"); DBUG_PRINT("enter", ("id: %lu signal: %u", id, (uint) kill_signal)); - mysql_mutex_lock(&LOCK_thread_count); // For unlink from list - I_List_iterator it(threads); - while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; - if (tmp->thread_id == id) - { - mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete - break; - } - } - mysql_mutex_unlock(&LOCK_thread_count); - if (tmp) + if ((tmp= find_thread_by_id(id))) { /* If we're SUPER, we can KILL anything, including system-threads. diff --git a/sql/sql_show.cc b/sql/sql_show.cc index b08d49b3f28..167e099baf9 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2056,28 +2056,11 @@ int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond) DBUG_ENTER("fill_show_explain"); DBUG_ASSERT(cond==NULL); - thread_id= thd->lex->show_explain_for_thread->val_int(); + thread_id= thd->lex->value_list.head()->val_int(); calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS : thd->security_ctx->priv_user; - /* - Find the thread we need EXPLAIN for. Thread search code was copied from - kill_one_thread() - */ - mysql_mutex_lock(&LOCK_thread_count); // For unlink from list - I_List_iterator it(threads); - while ((tmp=it++)) - { - if (tmp->command == COM_DAEMON) - continue; - if (tmp->thread_id == thread_id) - { - mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete - break; - } - } - mysql_mutex_unlock(&LOCK_thread_count); - if (tmp) + if ((tmp= find_thread_by_id(thread_id))) { Security_context *tmp_sctx= tmp->security_ctx; /* diff --git a/sql/sql_show.h b/sql/sql_show.h index 6ccb6e03872..12da9713e21 100644 --- a/sql/sql_show.h +++ b/sql/sql_show.h @@ -132,6 +132,7 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table); /* These functions were under INNODB_COMPATIBILITY_HOOKS */ int get_quote_char_for_identifier(THD *thd, const char *name, uint length); +THD *find_thread_by_id(ulong id); class select_result_explain_buffer; /* diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 50d87497d8c..0d9cb9622f1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11618,10 +11618,11 @@ show_param: } | describe_command FOR_SYM expr { + THD *thd= YYTHD; Lex->sql_command= SQLCOM_SHOW_EXPLAIN; - if (prepare_schema_table(YYTHD, Lex, 0, SCH_EXPLAIN)) + if (prepare_schema_table(thd, Lex, 0, SCH_EXPLAIN)) MYSQL_YYABORT; - Lex->show_explain_for_thread= $3; + add_value_to_list(thd, $3); } ; From a9dab6da62e90fa8238a4ff89d7292d7bafa8935 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Thu, 9 Aug 2012 20:38:09 +0400 Subject: [PATCH 65/67] MWL#182: Explain running statements: address review feedback - Add a testcase showing that queries specified in a charset that's different from the charset used for warnings, are converted. --- mysql-test/r/show_explain.result | 24 ++++++++++++++++++++++++ mysql-test/t/show_explain.test | 28 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index c751e4f913f..b5eda0b30c8 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -1045,5 +1045,29 @@ id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE 2 SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using filesort DROP TABLE t1,t2; +# +# Check if queries in non-default charsets work. +# +set names cp1251; +select charset('ãû'); +charset('ãû') +cp1251 +select hex('ãû'); +hex('ãû') +E3FB +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; +select * from t0 where length('ãû') = a; +set names utf8; +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 10 Using where +Warnings: +Note 1003 select * from t0 where length('гы') = a +set names default; +a +2 +set debug_dbug=''; +set names default; # End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 4bfee830986..8bfeae7e7e5 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -1077,5 +1077,33 @@ WHERE ( 8, 89 ) IN ( SELECT b, SUM( DISTINCT b ) FROM t2 GROUP BY b ); DROP TABLE t1,t2; +--echo # +--echo # Check if queries in non-default charsets work. +--echo # + +set names cp1251; +# The below are two Russian letters with codes E3FB in cp1251 encoding. +select charset('ãû'); +select hex('ãû'); + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_join_exec_start'; + +send +select * from t0 where length('ãû') = a; + +connection default; +set names utf8; +--source include/wait_condition.inc +evalp show explain for $thr2; +set names default; + +connection con1; +# The constant should be two letters, the last looking like 'bl' +reap; + +set debug_dbug=''; +set names default; + --echo # End drop table t0; From b988331b71efbb75b58d10ee3c10b08d0f670394 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 6 Oct 2012 11:09:18 +0400 Subject: [PATCH 66/67] MDEV-462: SHOW EXPLAIN: Assertion `table_list->table' fails in find_field_in_table_ref ... - Only allow basic constants as SHOW EXPLAIN arguments. --- mysql-test/r/show_explain.result | 7 ++++++- mysql-test/t/show_explain.test | 8 +++++++- sql/sql_parse.cc | 3 ++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index b5eda0b30c8..bf96ad88f23 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -7,7 +7,7 @@ insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C; alter table t1 add b int, add c int, add filler char(32); update t1 set b=a, c=a, filler='fooo'; alter table t1 add key(a), add key(b); -show explain for 2*1000*1000*1000; +show explain for 2000000000; ERROR HY000: Unknown thread id: 2000000000 show explain for (select max(a) from t0); ERROR HY000: You may only use constant expressions in this statement @@ -1069,5 +1069,10 @@ a 2 set debug_dbug=''; set names default; +# +# MDEV-462: SHOW EXPLAIN: Assertion `table_list->table' fails in find_field_in_table_ref if FOR contains a non-numeric value +# +show explain for foo; +ERROR HY000: You may only use constant expressions in this statement # End drop table t0; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 8bfeae7e7e5..c2429b82d0c 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -43,7 +43,7 @@ alter table t1 add key(a), add key(b); # Try killing a non-existent thread # --error ER_NO_SUCH_THREAD -show explain for 2*1000*1000*1000; +show explain for 2000000000; --error ER_SET_CONSTANTS_ONLY show explain for (select max(a) from t0); @@ -1105,5 +1105,11 @@ reap; set debug_dbug=''; set names default; +--echo # +--echo # MDEV-462: SHOW EXPLAIN: Assertion `table_list->table' fails in find_field_in_table_ref if FOR contains a non-numeric value +--echo # +--error ER_SET_CONSTANTS_ONLY +show explain for foo; + --echo # End drop table t0; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 3c697cb43af..217b60faa57 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -2159,7 +2159,8 @@ mysql_execute_command(THD *thd) } Item **it= lex->value_list.head_ref(); - if ((!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || + if (!(*it)->basic_const_item() || + (!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) || (*it)->check_cols(1)) { my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY), From 850fb901770d8a40a2a8d0cee7737089153e5cc4 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Sat, 6 Oct 2012 11:10:59 +0400 Subject: [PATCH 67/67] Remove unneeded comments --- sql/sql_select.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7a23a71e040..c46d7cdb30c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -19038,14 +19038,6 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, /* Currently ORDER BY ... LIMIT is not supported in subqueries. */ DBUG_ASSERT(join->group_list || !join->is_in_subquery()); - /* - If we have a select->quick object that is created outside of - create_sort_index() and this is part of a subquery that - potentially can be executed multiple times then we should not - delete the quick object on exit from this function. - */ - //bool keep_quick= select && select->quick && join->join_tab_save; - /* When there is SQL_BIG_RESULT do not sort using index for GROUP BY, and thus force sorting on disk unless a group min-max optimization @@ -19097,7 +19089,6 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, get_quick_select_for_ref(thd, table, &tab->ref, tab->found_records)))) goto err; - //DBUG_ASSERT(!keep_quick);//psergey-post-merge-check quick_created= TRUE; } } @@ -19145,6 +19136,7 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, /*TODO: here, close the index scan, cancel index-only read. */ tab->records= table->sort.found_records; // For SQL_CALC_ROWS #if 0 + /* MariaDB doesn't need the following: */ if (select) { /*