Commit ca8aa390 authored by Sergey Petrunya's avatar Sergey Petrunya

MWL#182: Explain running statements

- Code cleanup
parent 8c4fc9ba
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;
......@@ -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
--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;
# TODO: hit JOIN::optimize for non-select commands: UPDATE/DELETE, SET.
--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;
--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: 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.
......
......@@ -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);
......
......@@ -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 @@ class Apc_target
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 @@ class Apc_target
#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 @@ class Apc_target
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;
}
};
......
......@@ -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);
......
......@@ -1467,6 +1467,8 @@ class Show_explain_request
THD *target_thd;
THD *request_thd;
bool failed_to_produce;
select_result_explain_buffer *explain_buf;
static void get_explain_data(void *arg);
......
......@@ -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())
{
......
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment