mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
MDEV-39492 PQ framework for manager and worker threads
Introduces parallel_worker_threads variable to control the number of worker threads created by a parallel execution query. 2 new files, sql_parallel_workers.h sql_parallel_workers.cc which contain structures for the creation, management and deletion of parallel worker threads (pwt_ in the name). Main management class created in the stack in JOIN::exec, implemented for the top level select. Threads are registed in server_threads, so are visible in information_schema.processlist and the show processlist command. We check that a kill query on a parallel worker is passed onto it's manager and the query is properly aborted, and that a kill connection is handled properly in parallel_worker.test.
This commit is contained in:
@@ -624,6 +624,20 @@ typedef uint64 my_thread_id;
|
||||
*/
|
||||
#define MY_THREAD_ID_MAX UINT32_MAX
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MAX_THREAD_NAME 256
|
||||
#elif defined(__linux__)
|
||||
#define MAX_THREAD_NAME 16
|
||||
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#define MAX_THREAD_NAME 19
|
||||
#include <pthread_np.h>
|
||||
#elif defined(__apple_build_version__)
|
||||
#include <sys/proc_info.h>
|
||||
#define MAX_THREAD_NAME MAXTHREADNAMESIZE
|
||||
#else
|
||||
#define MAX_THREAD_NAME 16
|
||||
#endif
|
||||
|
||||
extern void my_threadattr_global_init(void);
|
||||
extern my_bool my_thread_global_init(void);
|
||||
extern void my_thread_set_name(const char *);
|
||||
|
||||
@@ -72,6 +72,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
||||
../sql/mf_iocache.cc ../sql/my_decimal.cc
|
||||
../sql/net_serv.cc ../sql/opt_range.cc
|
||||
../sql/opt_group_by_cardinality.cc
|
||||
../sql/sql_parallel_workers.cc
|
||||
../sql/opt_rewrite_date_cmp.cc
|
||||
../sql/opt_rewrite_remove_casefold.cc
|
||||
../sql/opt_sargable_left.cc
|
||||
|
||||
@@ -1008,6 +1008,9 @@ The following specify which files/extra groups are read (specified before remain
|
||||
Cost of checking the row against the WHERE clause.
|
||||
Increasing this will have the optimizer to prefer plans
|
||||
with less row combinations
|
||||
--parallel-worker-threads=#
|
||||
Number of worker threads available for parallel query
|
||||
execution. 0 means parallel execution is disabled
|
||||
--path=name Comma-separated list of schema names that defines the
|
||||
search order for stored routines
|
||||
--performance-schema
|
||||
@@ -1995,6 +1998,7 @@ optimizer-trace
|
||||
optimizer-trace-max-mem-size 1048576
|
||||
optimizer-use-condition-selectivity 4
|
||||
optimizer-where-cost 0.032
|
||||
parallel-worker-threads 0
|
||||
path CURRENT_SCHEMA
|
||||
performance-schema FALSE
|
||||
performance-schema-accounts-size -1
|
||||
|
||||
63
mysql-test/main/parallel_query.result
Normal file
63
mysql-test/main/parallel_query.result
Normal file
@@ -0,0 +1,63 @@
|
||||
SET DEBUG_SYNC='RESET';
|
||||
#
|
||||
# MDEV-39492 Parallel Query: Study how to create worker threads
|
||||
#
|
||||
create table t1 (a int) engine=innodb;
|
||||
insert into t1 select seq from seq_1_to_2;
|
||||
set session parallel_worker_threads=3;
|
||||
# we should currently see 3 warnings;
|
||||
select SQL_BUFFER_RESULT a from t1;
|
||||
a
|
||||
1
|
||||
2
|
||||
set session parallel_worker_threads=10;
|
||||
select SQL_BUFFER_RESULT a from t1;
|
||||
a
|
||||
1
|
||||
2
|
||||
set session parallel_worker_threads=0;
|
||||
connect killee, localhost, root, , ;
|
||||
# check that kill query on a parallel worker is passed to the manager
|
||||
connection killee;
|
||||
set session parallel_worker_threads=3;
|
||||
SET @save_dbug= @@global.debug_dbug;
|
||||
SET GLOBAL debug_dbug = "+d,pwt_worker_pause_before_signal";
|
||||
select SQL_BUFFER_RESULT a from t1;;
|
||||
# now use the default connection to view/kill the thread group executing
|
||||
# the parallel work
|
||||
connection default;
|
||||
SET DEBUG_SYNC='now WAIT_FOR pwt_worker_paused';
|
||||
# wait for all 3 workers to hit our debug sync point
|
||||
# then have a look at what is in our process list without thread ID
|
||||
select USER, DB, STATE, SUBSTRING(INFO, 1, 32)
|
||||
from information_schema.processlist
|
||||
where (INFO regexp '.*parallel worker.*' or
|
||||
STATE regexp '.*parallel worker.*') and
|
||||
NOT INFO regexp '.*information_schema\.processlist.*';
|
||||
USER DB STATE SUBSTRING(INFO, 1, 32)
|
||||
root test debug sync point: now Parallel Worker 3 For Thread ID
|
||||
root test debug sync point: now Parallel Worker 2 For Thread ID
|
||||
root test debug sync point: now Parallel Worker 1 For Thread ID
|
||||
root test Reading data from parallel workers select SQL_BUFFER_RESULT a from
|
||||
kill query ID;
|
||||
# signal our workers to continue execution
|
||||
SET DEBUG_SYNC = "now SIGNAL pwt_worker_continue";
|
||||
# then wait for the manager thread to clean up and go back to sleep
|
||||
# review error message on --reap
|
||||
connection killee;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
SET DEBUG_SYNC = "RESET";
|
||||
# save as above, but kill a worker with a simple kill and see the
|
||||
# connection drop
|
||||
select SQL_BUFFER_RESULT a from t1;;
|
||||
connection default;
|
||||
SET DEBUG_SYNC='now WAIT_FOR pwt_worker_paused';
|
||||
kill ID;
|
||||
SET DEBUG_SYNC = "now SIGNAL pwt_worker_continue";
|
||||
connection killee;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
# killee connection now dead, confirmed below
|
||||
connection default;
|
||||
drop table t1;
|
||||
SET GLOBAL debug_dbug = @save_dbug;
|
||||
SET DEBUG_SYNC = "RESET";
|
||||
110
mysql-test/main/parallel_query.test
Normal file
110
mysql-test/main/parallel_query.test
Normal file
@@ -0,0 +1,110 @@
|
||||
#
|
||||
# Test KILL and KILL QUERY statements.
|
||||
#
|
||||
|
||||
-- source include/count_sessions.inc
|
||||
-- source include/not_embedded.inc
|
||||
-- source include/have_innodb.inc
|
||||
-- source include/have_sequence.inc
|
||||
--source include/have_debug.inc
|
||||
--source include/have_debug_sync.inc
|
||||
SET DEBUG_SYNC='RESET';
|
||||
|
||||
--disable_service_connection
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39492 Parallel Query: Study how to create worker threads
|
||||
--echo #
|
||||
|
||||
create table t1 (a int) engine=innodb;
|
||||
insert into t1 select seq from seq_1_to_2;
|
||||
|
||||
set session parallel_worker_threads=3;
|
||||
--echo # we should currently see 3 warnings;
|
||||
select SQL_BUFFER_RESULT a from t1;
|
||||
|
||||
set session parallel_worker_threads=10;
|
||||
select SQL_BUFFER_RESULT a from t1;
|
||||
|
||||
set session parallel_worker_threads=0;
|
||||
connect (killee, localhost, root, , );
|
||||
|
||||
--echo # check that kill query on a parallel worker is passed to the manager
|
||||
|
||||
--connection killee
|
||||
let $id= `select connection_id()`;
|
||||
set session parallel_worker_threads=3;
|
||||
|
||||
# worker THDs don't inherit session vars, we are using a debug_sync_set_action
|
||||
# to pause execution in our worker threads just after they have done something
|
||||
|
||||
SET @save_dbug= @@global.debug_dbug;
|
||||
SET GLOBAL debug_dbug = "+d,pwt_worker_pause_before_signal";
|
||||
|
||||
--send select SQL_BUFFER_RESULT a from t1;
|
||||
--echo # now use the default connection to view/kill the thread group executing
|
||||
--echo # the parallel work
|
||||
--connection default
|
||||
SET DEBUG_SYNC='now WAIT_FOR pwt_worker_paused';
|
||||
let $name= "Parallel Worker 1 For Thread ID $id";
|
||||
let $killID= `SELECT @kid:=ID from information_schema.processlist
|
||||
where info like $name limit 1`;
|
||||
--echo # wait for all 3 workers to hit our debug sync point
|
||||
let $wait_condition=
|
||||
select count(*) = 3 from information_schema.processlist
|
||||
where state like 'debug sync%' and info like '%Thread ID $id';
|
||||
--source include/wait_condition.inc
|
||||
--echo # then have a look at what is in our process list without thread ID
|
||||
#select * from information_schema.processlist;
|
||||
select USER, DB, STATE, SUBSTRING(INFO, 1, 32)
|
||||
from information_schema.processlist
|
||||
where (INFO regexp '.*parallel worker.*' or
|
||||
STATE regexp '.*parallel worker.*') and
|
||||
NOT INFO regexp '.*information_schema\.processlist.*';
|
||||
--replace_result $killID ID
|
||||
eval kill query $killID;
|
||||
--echo # signal our workers to continue execution
|
||||
SET DEBUG_SYNC = "now SIGNAL pwt_worker_continue";
|
||||
--echo # then wait for the manager thread to clean up and go back to sleep
|
||||
let $wait_condition=
|
||||
select count(*) = 1 from information_schema.processlist
|
||||
where command = 'Sleep' and id = $id;
|
||||
--source include/wait_condition.inc
|
||||
#select * from information_schema.processlist;
|
||||
--echo # review error message on --reap
|
||||
--connection killee
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
|
||||
SET DEBUG_SYNC = "RESET";
|
||||
|
||||
--echo # save as above, but kill a worker with a simple kill and see the
|
||||
--echo # connection drop
|
||||
|
||||
--send select SQL_BUFFER_RESULT a from t1;
|
||||
connection default;
|
||||
SET DEBUG_SYNC='now WAIT_FOR pwt_worker_paused';
|
||||
let $name= "Parallel Worker 1 For Thread ID $id";
|
||||
let $killID= `SELECT @kid:=ID from information_schema.processlist
|
||||
where info like $name limit 1`;
|
||||
--replace_result $killID ID
|
||||
eval kill $killID;
|
||||
SET DEBUG_SYNC = "now SIGNAL pwt_worker_continue";
|
||||
let $wait_condition=
|
||||
select count(*) = 0 from information_schema.processlist
|
||||
where command = 'Sleep' and id = $id;
|
||||
--source include/wait_condition.inc
|
||||
connection killee;
|
||||
--disable_warnings
|
||||
--error ER_QUERY_INTERRUPTED
|
||||
reap;
|
||||
|
||||
--echo # killee connection now dead, confirmed below
|
||||
|
||||
connection default;
|
||||
|
||||
drop table t1;
|
||||
SET GLOBAL debug_dbug = @save_dbug;
|
||||
SET DEBUG_SYNC = "RESET";
|
||||
|
||||
--source include/wait_until_count_sessions.inc
|
||||
30
mysql-test/main/parallel_query_oom.result
Normal file
30
mysql-test/main/parallel_query_oom.result
Normal file
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# MDEV-39492 Parallel Query: OOM in worker error_to_queue surfaces a
|
||||
# single ER_OUTOFMEMORY warning so worker diagnostics aren't silently
|
||||
# lost.
|
||||
#
|
||||
set @save_dbug=@@global.debug_dbug;
|
||||
set global debug_dbug="+d,pwt_error_to_queue_oom";
|
||||
set session parallel_worker_threads=4;
|
||||
# The prototype worker emits either a warning or my_error() depending on the
|
||||
# parity of its thread id. Either path runs through error_to_queue; the
|
||||
# DBUG injection forces both into the OOM branch, so neither the original
|
||||
# warning nor the original error reaches the user. We expect just the
|
||||
# manager-surfaced ER_OUTOFMEMORY warnings.
|
||||
#
|
||||
# Four workers rather than one: fewer than PARALLEL_QUERY_MIN_WORKERS are
|
||||
# declined and run serially, and then no worker reaches the injection at
|
||||
# all. Asking for one used to work and quietly stopped testing anything.
|
||||
create table t1 (a int) engine=innodb;
|
||||
insert into t1 select seq from seq_1_to_2000;
|
||||
select * from t1 where a > 1999;
|
||||
a
|
||||
2000
|
||||
Warnings:
|
||||
Warning 1037 Parallel worker diagnostics were dropped due to memory allocation failure
|
||||
show warnings;
|
||||
Level Code Message
|
||||
Warning 1037 Parallel worker diagnostics were dropped due to memory allocation failure
|
||||
drop table t1;
|
||||
set global debug_dbug=@save_dbug;
|
||||
set session parallel_worker_threads=default;
|
||||
45
mysql-test/main/parallel_query_oom.test
Normal file
45
mysql-test/main/parallel_query_oom.test
Normal file
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# Test OOM handling in the parallel worker error/warning queue.
|
||||
#
|
||||
|
||||
-- source include/count_sessions.inc
|
||||
-- source include/have_innodb.inc
|
||||
-- source include/not_embedded.inc
|
||||
-- source include/have_sequence.inc
|
||||
-- source include/have_debug.inc
|
||||
|
||||
--disable_service_connection
|
||||
|
||||
--echo #
|
||||
--echo # MDEV-39492 Parallel Query: OOM in worker error_to_queue surfaces a
|
||||
--echo # single ER_OUTOFMEMORY warning so worker diagnostics aren't silently
|
||||
--echo # lost.
|
||||
--echo #
|
||||
|
||||
set @save_dbug=@@global.debug_dbug;
|
||||
set global debug_dbug="+d,pwt_error_to_queue_oom";
|
||||
set session parallel_worker_threads=4;
|
||||
|
||||
--echo # The prototype worker emits either a warning or my_error() depending on the
|
||||
--echo # parity of its thread id. Either path runs through error_to_queue; the
|
||||
--echo # DBUG injection forces both into the OOM branch, so neither the original
|
||||
--echo # warning nor the original error reaches the user. We expect just the
|
||||
--echo # manager-surfaced ER_OUTOFMEMORY warnings.
|
||||
--echo #
|
||||
--echo # Four workers rather than one: fewer than PARALLEL_QUERY_MIN_WORKERS are
|
||||
--echo # declined and run serially, and then no worker reaches the injection at
|
||||
--echo # all. Asking for one used to work and quietly stopped testing anything.
|
||||
create table t1 (a int) engine=innodb;
|
||||
# The table needs more leaf pages than one: the engine divides a table by
|
||||
# its B-tree and declines a table it cannot divide, so a two-row table runs
|
||||
# serially and no worker reaches the injected failure. The WHERE keeps the
|
||||
# output small; the whole table is still scanned.
|
||||
insert into t1 select seq from seq_1_to_2000;
|
||||
select * from t1 where a > 1999;
|
||||
show warnings;
|
||||
drop table t1;
|
||||
|
||||
set global debug_dbug=@save_dbug;
|
||||
set session parallel_worker_threads=default;
|
||||
|
||||
--source include/wait_until_count_sessions.inc
|
||||
@@ -3012,6 +3012,16 @@ NUMERIC_BLOCK_SIZE NULL
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME PARALLEL_WORKER_THREADS
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE BIGINT UNSIGNED
|
||||
VARIABLE_COMMENT Number of worker threads available for parallel query execution. 0 means parallel execution is disabled
|
||||
NUMERIC_MIN_VALUE 0
|
||||
NUMERIC_MAX_VALUE 100
|
||||
NUMERIC_BLOCK_SIZE 1
|
||||
ENUM_VALUE_LIST NULL
|
||||
READ_ONLY NO
|
||||
COMMAND_LINE_ARGUMENT REQUIRED
|
||||
VARIABLE_NAME PATH
|
||||
VARIABLE_SCOPE SESSION
|
||||
VARIABLE_TYPE VARCHAR
|
||||
|
||||
@@ -20,12 +20,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MAX_THREAD_NAME 256
|
||||
typedef HRESULT (*func_SetThreadDescription)(HANDLE,PCWSTR);
|
||||
#elif defined(__linux__)
|
||||
#define MAX_THREAD_NAME 16
|
||||
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PSI_THREAD_INTERFACE) && !defined DBUG_OFF
|
||||
|
||||
@@ -118,6 +118,7 @@ SET (SQL_SOURCE
|
||||
../sql-common/client_plugin.c
|
||||
opt_range.cc vector_mhnsw.cc
|
||||
opt_group_by_cardinality.cc
|
||||
sql_parallel_workers.cc
|
||||
opt_rewrite_date_cmp.cc
|
||||
opt_rewrite_remove_casefold.cc
|
||||
opt_sargable_left.cc
|
||||
|
||||
@@ -3750,6 +3750,7 @@ public:
|
||||
DBUG_RETURN(rnd_end());
|
||||
}
|
||||
int ha_rnd_init_with_error(bool scan) __attribute__ ((warn_unused_result));
|
||||
|
||||
int ha_reset();
|
||||
/* this is necessary in many places, e.g. in HANDLER command */
|
||||
int ha_index_or_rnd_end()
|
||||
|
||||
@@ -9978,6 +9978,7 @@ PSI_stage_info stage_starting= { 0, "starting", 0};
|
||||
PSI_stage_info stage_waiting_for_flush= { 0, "Waiting for non trans tables to be flushed", 0};
|
||||
PSI_stage_info stage_waiting_for_ddl= { 0, "Waiting for DDLs", 0};
|
||||
PSI_stage_info stage_waiting_for_reset_master= { 0, "Waiting for a running RESET MASTER to complete", 0};
|
||||
PSI_stage_info stage_reading_data_from_parallel_worker= { 0, "Reading data from parallel workers", 0};
|
||||
|
||||
#ifdef WITH_WSREP
|
||||
// Additional Galera thread states
|
||||
@@ -10070,6 +10071,11 @@ PSI_memory_key key_memory_user_var_entry_value;
|
||||
PSI_memory_key key_memory_String_value;
|
||||
PSI_memory_key key_memory_WSREP;
|
||||
PSI_memory_key key_memory_trace_ddl_info;
|
||||
PSI_memory_key key_memory_pwt_queued_event;
|
||||
PSI_memory_key key_memory_pwt_error_message;
|
||||
PSI_memory_key key_memory_pwt_workers;
|
||||
PSI_memory_key key_memory_pwt_db;
|
||||
PSI_memory_key key_memory_pwt_batch_rows;
|
||||
|
||||
#ifdef HAVE_PSI_INTERFACE
|
||||
|
||||
@@ -10211,7 +10217,8 @@ PSI_stage_info *all_server_stages[]=
|
||||
& stage_reading_semi_sync_ack,
|
||||
& stage_waiting_for_deadlock_kill,
|
||||
& stage_starting,
|
||||
& stage_waiting_for_reset_master
|
||||
& stage_waiting_for_reset_master,
|
||||
& stage_reading_data_from_parallel_worker
|
||||
#ifdef WITH_WSREP
|
||||
,
|
||||
& stage_waiting_isolation,
|
||||
@@ -10323,6 +10330,9 @@ static PSI_memory_info all_server_memory[]=
|
||||
{ &key_memory_trace_ddl_info, "TRACE_DDL_INFO", 0}
|
||||
};
|
||||
|
||||
|
||||
extern void pwt_init_psi_keys(void);
|
||||
|
||||
/**
|
||||
Initialise all the performance schema instrumentation points
|
||||
used by the server.
|
||||
@@ -10409,6 +10419,7 @@ void init_server_psi_keys(void)
|
||||
stmt_info_rpl.m_flags= PSI_FLAG_MUTABLE;
|
||||
mysql_statement_register(category, &stmt_info_rpl, 1);
|
||||
#endif
|
||||
pwt_init_psi_keys();
|
||||
}
|
||||
|
||||
#endif /* HAVE_PSI_INTERFACE */
|
||||
|
||||
@@ -500,6 +500,11 @@ extern PSI_memory_key key_memory_Table_trigger_dispatcher;
|
||||
extern PSI_memory_key key_memory_native_functions;
|
||||
extern PSI_memory_key key_memory_WSREP;
|
||||
extern PSI_memory_key key_memory_trace_ddl_info;
|
||||
extern PSI_memory_key key_memory_pwt_queued_event;
|
||||
extern PSI_memory_key key_memory_pwt_error_message;
|
||||
extern PSI_memory_key key_memory_pwt_workers;
|
||||
extern PSI_memory_key key_memory_pwt_db;
|
||||
extern PSI_memory_key key_memory_pwt_batch_rows;
|
||||
|
||||
/*
|
||||
MAINTAINER: Please keep this list in order, to limit merge collisions.
|
||||
@@ -647,6 +652,7 @@ extern PSI_stage_info stage_slave_background_wait_request;
|
||||
extern PSI_stage_info stage_waiting_for_deadlock_kill;
|
||||
extern PSI_stage_info stage_starting;
|
||||
extern PSI_stage_info stage_waiting_for_reset_master;
|
||||
extern PSI_stage_info stage_reading_data_from_parallel_worker;
|
||||
#ifdef WITH_WSREP
|
||||
// Additional Galera thread states
|
||||
extern PSI_stage_info stage_waiting_isolation;
|
||||
|
||||
@@ -1403,6 +1403,8 @@ public:
|
||||
void save_last_pos() override
|
||||
{ file->position(record); }
|
||||
int get_type() override { return QS_TYPE_RANGE; }
|
||||
/* Number of disjoint key intervals this scan covers. */
|
||||
size_t num_ranges() const { return ranges.elements; }
|
||||
void add_keys_and_lengths(String *key_names, String *used_lengths) override;
|
||||
Explain_quick_select *get_explain(MEM_ROOT *alloc) override;
|
||||
#ifndef DBUG_OFF
|
||||
|
||||
@@ -452,6 +452,8 @@ constexpr privilege_t PRIV_SET_SYSTEM_GLOBAL_VAR_MAX_CONNECT_ERRORS=
|
||||
// Was SUPER_ACL prior to 10.5.2
|
||||
constexpr privilege_t PRIV_SET_SYSTEM_GLOBAL_VAR_MAX_PASSWORD_ERRORS=
|
||||
CONNECTION_ADMIN_ACL;
|
||||
constexpr privilege_t PRIV_SET_SYSTEM_GLOBAL_VAR_PARALLEL_WORKER_THREADS=
|
||||
CONNECTION_ADMIN_ACL;
|
||||
// Was SUPER_ACL prior to 10.5.2
|
||||
constexpr privilege_t PRIV_SET_SYSTEM_GLOBAL_VAR_PROXY_PROTOCOL_NETWORKS=
|
||||
CONNECTION_ADMIN_ACL;
|
||||
|
||||
@@ -83,6 +83,7 @@ struct READ_RECORD
|
||||
*/
|
||||
Copy_field *copy_field;
|
||||
Copy_field *copy_field_end;
|
||||
|
||||
public:
|
||||
READ_RECORD() : table(NULL), cache(NULL) {}
|
||||
~READ_RECORD() { end_read_record(this); }
|
||||
|
||||
@@ -722,7 +722,7 @@ const char *thd_where(THD *thd)
|
||||
THD::THD(my_thread_id id, bool is_wsrep_applier)
|
||||
:Statement(&main_lex, &main_mem_root, STMT_CONVENTIONAL_EXECUTION,
|
||||
/* statement id */ 0),
|
||||
rli_fake(0), rgi_fake(0), rgi_slave(NULL),
|
||||
rli_fake(0), rgi_fake(0), rgi_slave(NULL), parallel_worker(NULL),
|
||||
protocol_text(this), protocol_binary(this), initial_status_var(0),
|
||||
m_current_stage_key(0), m_psi(0), start_time(0), start_time_sec_part(0),
|
||||
in_sub_stmt(0), log_all_errors(0),
|
||||
@@ -5490,8 +5490,7 @@ void destroy_thd(MYSQL_THD thd)
|
||||
|
||||
/**
|
||||
Create a THD that only has auxiliary functions
|
||||
It will never be added to the global connection list
|
||||
server_threads. It does not represent any client connection.
|
||||
It does not represent any client connection.
|
||||
|
||||
It should never be counted, because it will stall the
|
||||
shutdown. It is solely for engine's internal use,
|
||||
|
||||
@@ -873,6 +873,7 @@ typedef struct system_variables
|
||||
ulong server_id;
|
||||
ulong session_track_transaction_info;
|
||||
ulong threadpool_priority;
|
||||
ulong parallel_worker_threads;
|
||||
ulong vers_alter_history;
|
||||
|
||||
/* deadlock detection */
|
||||
@@ -3320,7 +3321,7 @@ enum class THD_WHERE
|
||||
|
||||
|
||||
const char *thd_where(THD *thd);
|
||||
|
||||
class pwt_worker;
|
||||
|
||||
/**
|
||||
@class THD
|
||||
@@ -3366,6 +3367,8 @@ public:
|
||||
/* Slave applier execution context */
|
||||
rpl_group_info* rgi_slave;
|
||||
|
||||
pwt_worker *parallel_worker;
|
||||
|
||||
union {
|
||||
rpl_io_thread_info *rpl_io_info;
|
||||
rpl_sql_thread_info *rpl_sql_info;
|
||||
|
||||
@@ -1564,7 +1564,12 @@ int Explain_table_access::print_explain(select_result_sink *output,
|
||||
/* `type` column */
|
||||
StringBuffer<64> join_type_buf;
|
||||
if (rowid_filter == NULL)
|
||||
push_str(thd, &item_list, join_type_str[type]);
|
||||
{
|
||||
if (type == JT_ALL && use_parallel_scan)
|
||||
push_str(thd, &item_list, "PARALLEL");
|
||||
else
|
||||
push_str(thd, &item_list, join_type_str[type]);
|
||||
}
|
||||
else
|
||||
{
|
||||
join_type_buf.append(join_type_str[type], strlen(join_type_str[type]));
|
||||
@@ -2052,7 +2057,10 @@ void Explain_table_access::print_explain_json(Explain_query *query,
|
||||
}
|
||||
}
|
||||
|
||||
writer->add_member("access_type").add_str(join_type_str[type]);
|
||||
if (type == JT_ALL && use_parallel_scan)
|
||||
writer->add_member("access_type").add_str("PARALLEL");
|
||||
else
|
||||
writer->add_member("access_type").add_str(join_type_str[type]);
|
||||
|
||||
add_json_keyset(writer, "possible_keys", &possible_keys);
|
||||
|
||||
|
||||
@@ -812,6 +812,7 @@ public:
|
||||
enum join_type type;
|
||||
|
||||
bool used_partitions_set;
|
||||
bool use_parallel_scan;
|
||||
|
||||
/* Empty means "NULL" will be printed */
|
||||
String_list possible_keys;
|
||||
|
||||
1136
sql/sql_parallel_workers.cc
Normal file
1136
sql/sql_parallel_workers.cc
Normal file
File diff suppressed because it is too large
Load Diff
243
sql/sql_parallel_workers.h
Normal file
243
sql/sql_parallel_workers.h
Normal file
@@ -0,0 +1,243 @@
|
||||
#ifndef SQL_PARALLEL_WORKERS_H
|
||||
#define SQL_PARALLEL_WORKERS_H
|
||||
|
||||
#include "mariadb.h"
|
||||
#include "sql_class.h"
|
||||
#include "mysqld.h"
|
||||
#include "sql_error.h"
|
||||
|
||||
extern MYSQL_THD create_background_thd();
|
||||
extern void destroy_background_thd(MYSQL_THD thd);
|
||||
extern void *thd_attach_thd(MYSQL_THD thd);
|
||||
extern void thd_detach_thd(void *save);
|
||||
|
||||
// PWT Parallel Worker Thread
|
||||
|
||||
|
||||
/*
|
||||
Message Types
|
||||
*/
|
||||
class pwt_error_message
|
||||
{
|
||||
public:
|
||||
uint worker_errno;
|
||||
uint code;
|
||||
Sql_condition::enum_warning_level level;
|
||||
char *message;
|
||||
};
|
||||
|
||||
/*
|
||||
Event type. Inherits ilink so it can live in an I_List<pwt_queued_event>.
|
||||
*/
|
||||
class pwt_queued_event : public ilink
|
||||
{
|
||||
public:
|
||||
pwt_error_message *error;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Number of rows a worker packs into its batch buffer before handing it to the
|
||||
manager. The worker hands rows to the manager a batch at a time rather than
|
||||
one at a time so the channel mutex is touched once per PWT_CHUNK_ROWS rows
|
||||
instead of once per row. Each worker reuses a single raw row buffer (see
|
||||
pwt_worker::batch_rows): it fills the buffer with up to PWT_CHUNK_ROWS raw
|
||||
record images, hands it to the manager and blocks until the manager has
|
||||
drained it, then refills it for the next batch.
|
||||
*/
|
||||
#define PWT_CHUNK_ROWS 2048
|
||||
|
||||
class pwt_manager;
|
||||
typedef struct st_join_table JOIN_TAB;
|
||||
class JOIN;
|
||||
|
||||
#define WORKER_NAME "Parallel Worker"
|
||||
#define WORKER_ID_LENGTH 3
|
||||
#define WORKER_NAME_LENGTH 15
|
||||
#define CONNECTION_NAME_THREAD "For Thread ID"
|
||||
#define CONNECTION_NAME_THREAD_LENGTH 13
|
||||
#define THREAD_ID_LENGTH 20 // ull can occupy 20 chars
|
||||
|
||||
class pwt_worker_batch
|
||||
{
|
||||
public:
|
||||
/*
|
||||
This worker's single reused row buffer, PWT_CHUNK_ROWS * reclength bytes,
|
||||
allocated up front in init_parallel_workers. The worker fills it with raw
|
||||
record[0] images memcpy'd straight from scan_table (the worker and the
|
||||
manager share that table's TABLE_SHARE, so a byte-for-byte record copy
|
||||
reconstructs the row on the manager side -- which is why BLOB/TEXT tables,
|
||||
whose payload lives off the record buffer, are excluded from parallel
|
||||
scan). It hands the buffer to the manager, blocks until the manager has
|
||||
drained it, then refills from the top. The worker and the manager never
|
||||
touch it at the same time, so it needs no per-row locking.
|
||||
*/
|
||||
uchar *rows;
|
||||
uint count; // rows the worker placed in batch_rows
|
||||
/*
|
||||
Hand-off flag for batch_rows, guarded by pwt_manager::LOCK_data.
|
||||
The worker sets it true once the buffer is filled and ready for the manager;
|
||||
the manager clears it once the buffer is drained, releasing the worker to
|
||||
refill. See pwt_manager::handoff_batch / the consumer read function.
|
||||
*/
|
||||
bool full;
|
||||
};
|
||||
|
||||
|
||||
class pwt_worker_execution
|
||||
{
|
||||
public:
|
||||
/*
|
||||
Per-worker copy of the manager's first non-const source table.
|
||||
Opened in init_parallel_workers, closed in the worker thread before
|
||||
its THD is destroyed.
|
||||
*/
|
||||
TABLE *scan_table;
|
||||
JOIN *worker_join; // The JOIN for the worker's JOIN_TABs
|
||||
JOIN_TAB *join_tabs; // n_tables x non-const JOIN_TABs in order
|
||||
Table_access_tracker *tab_stats;
|
||||
ha_handler_stats *tab_hstats;
|
||||
STATUS_VAR stats;
|
||||
Parallel_worker_ctx *handler_ctx;
|
||||
};
|
||||
|
||||
struct pwt_worker_info
|
||||
{
|
||||
char conn_name[MAX_THREAD_NAME+1];
|
||||
/*
|
||||
This is displayed in information_schema.processlist.info
|
||||
Currently "Parallel Worker {1..N} For Thread M"
|
||||
*/
|
||||
char process_list[WORKER_NAME_LENGTH+
|
||||
1+WORKER_ID_LENGTH+1+
|
||||
CONNECTION_NAME_THREAD_LENGTH+
|
||||
1+THREAD_ID_LENGTH+1];
|
||||
};
|
||||
|
||||
struct pwt_worker_state
|
||||
{
|
||||
bool joined;
|
||||
bool finished;
|
||||
killed_state killed;
|
||||
};
|
||||
|
||||
/*
|
||||
Parallel Worker Thread specific attributes
|
||||
*/
|
||||
class pwt_worker
|
||||
{
|
||||
public:
|
||||
THD *thd;
|
||||
pwt_manager *manager;
|
||||
pthread_t pthread;
|
||||
mysql_mutex_t LOCK_worker;
|
||||
mysql_cond_t COND_worker;
|
||||
|
||||
pwt_worker_info info;
|
||||
pwt_worker_state state;
|
||||
pwt_worker_batch batch;
|
||||
pwt_worker_execution exec;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Streaming channel. Each worker (producer) fills its single reused row
|
||||
buffer (batch_rows) and hands it to the manager (single consumer) by
|
||||
setting its batch_full flag; the manager drains the buffer from its first
|
||||
join_tab read function and runs the rest of the join as the batches arrive,
|
||||
instead of waiting for every worker to finish first.
|
||||
|
||||
LOCK_data guards cur_worker, the workers' batch_full flags, active_workers
|
||||
and the flags below. COND_data_avail wakes the consumer when a worker
|
||||
fills its buffer or finishes; COND_data_space wakes a worker when the
|
||||
manager has drained its buffer so it may refill. Because each worker owns
|
||||
one buffer and blocks until it is drained, at most one batch per worker is
|
||||
ever outstanding -- the single buffer is the natural backpressure bound.
|
||||
EOF for the consumer is the state (no worker has batch_full set &&
|
||||
active_workers == 0).
|
||||
*/
|
||||
|
||||
class pwt_manager_drain
|
||||
{
|
||||
public:
|
||||
mysql_mutex_t LOCK_data;
|
||||
mysql_cond_t COND_data_avail;
|
||||
mysql_cond_t COND_data_space;
|
||||
pwt_worker *cur_worker; // worker whose buffer the consumer drains
|
||||
uint cur_cursor; // consumer's row index within cur_worker
|
||||
uint reclength; // shared record image size (bytes)
|
||||
uint active_workers; // producers still running
|
||||
bool fatal_error; // a producer hit a real engine error
|
||||
bool stop; // consumer wants producers to stop
|
||||
|
||||
pwt_manager_drain():
|
||||
cur_worker(nullptr),
|
||||
active_workers(0),
|
||||
fatal_error(false),
|
||||
stop(false)
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Class to create, manage and eventually destroy a "team" of worker threads.
|
||||
*/
|
||||
class pwt_manager : public Sql_alloc
|
||||
{
|
||||
public:
|
||||
pwt_worker *workers;
|
||||
uint nworkers;
|
||||
I_List<pwt_queued_event> parallel_messages;
|
||||
mysql_mutex_t LOCK_pwt_thread;
|
||||
THD *thd;
|
||||
JOIN *join;
|
||||
JOIN_TAB *scan_tab;
|
||||
pwt_manager_drain drain;
|
||||
/*
|
||||
Set under LOCK_pwt_thread when a worker fails to allocate a queued event.
|
||||
The manager surfaces a single ER_OUTOFMEMORY warning so the user sees
|
||||
that worker diagnostics were dropped instead of silently disappearing.
|
||||
*/
|
||||
bool messages_dropped;
|
||||
|
||||
/*
|
||||
Set once the workers have been stopped and pthread_join'd (quiesce_workers).
|
||||
Workers read this join's source table and write their row buffers, so they
|
||||
must be reaped before JOIN::join_free()->cleanup() frees the source table;
|
||||
quiesce_workers is called from join_free, and again (idempotently) from
|
||||
finalize.
|
||||
*/
|
||||
bool reaped;
|
||||
/*
|
||||
Set (under LOCK_data) to a worker's killed_state when that worker exits
|
||||
because it was killed -- e.g. a user KILL [QUERY] aimed at a parallel
|
||||
worker. The consumer propagates it to the manager's own THD so the join
|
||||
aborts with the right error (ER_QUERY_INTERRUPTED) before any result is
|
||||
sent, rather than completing and trying to raise the error too late.
|
||||
*/
|
||||
killed_state kill_signal;
|
||||
|
||||
pwt_manager():
|
||||
workers(nullptr),
|
||||
nworkers(0),
|
||||
messages_dropped(false),
|
||||
reaped(false),
|
||||
kill_signal(NOT_KILLED)
|
||||
{}
|
||||
~pwt_manager()
|
||||
{
|
||||
finalize_parallel_workers(current_thd, join);
|
||||
}
|
||||
int init_parallel_workers(THD *thd, JOIN *join, JOIN_TAB *scan_tab);
|
||||
void quiesce_workers();
|
||||
void finalize_parallel_workers(THD *thd, JOIN *join);
|
||||
bool handoff_batch(pwt_worker *worker);
|
||||
void free_queue();
|
||||
};
|
||||
|
||||
extern bool table_can_be_parallel_scanned(TABLE *table);
|
||||
extern bool scale_cost_for_parallel_scan(THD *thd, TABLE *table,
|
||||
ALL_READ_COST *cost);
|
||||
|
||||
extern int parallel_init_read_record(JOIN_TAB *tab);
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@
|
||||
*/
|
||||
|
||||
#include "mariadb.h"
|
||||
#include "mysqld_error.h"
|
||||
#include "sql_priv.h"
|
||||
#include "unireg.h"
|
||||
#include "sql_select.h"
|
||||
@@ -71,6 +72,7 @@
|
||||
#include "derived_handler.h"
|
||||
#include "opt_hints.h"
|
||||
#include "opt_group_by_cardinality.h"
|
||||
#include "sql_parallel_workers.h"
|
||||
|
||||
/*
|
||||
A key part number that means we're using a fulltext scan.
|
||||
@@ -2793,6 +2795,122 @@ setup_subq_exit:
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
/*
|
||||
@brief
|
||||
Whether the access method the plan settled on is one the engine will hand
|
||||
out in pieces.
|
||||
|
||||
@description
|
||||
Asked of the driving table only, and asked about the *access method*: the
|
||||
table-level question -- a real base table, no blob-backed columns, not
|
||||
fulltext-searched, not partitioned, an engine that does this at all -- is
|
||||
table_can_be_parallel_scanned()'s, and the caller asks it first. Keeping
|
||||
the two apart matters because the optimizer's cost hook
|
||||
(scale_cost_for_parallel_scan) asks only the table-level one, and a copy
|
||||
here would be free to drift from it.
|
||||
*/
|
||||
|
||||
static bool is_parallel_scan_applicable(JOIN_TAB *join_tab)
|
||||
{
|
||||
/*
|
||||
Two access methods are eligible: a table scan (EXPLAIN type=ALL) and a
|
||||
range scan over one index. A range scan reaches here as either JT_ALL or
|
||||
JT_RANGE, depending on how the plan arrived at it -- make_join_select()
|
||||
promotes a ref to JT_RANGE when the range uses a longer key -- and both
|
||||
read through join_init_read_record. read_first_record is checked next to
|
||||
->type because this runs after make_join_readinfo(), which is what actually
|
||||
picked the reader.
|
||||
|
||||
A complete scan of one index (type=index, JT_NEXT) is NOT eligible, even
|
||||
though InnoDB advertises PSCAN_INDEX_FULL. Handing one to the coordinator
|
||||
trips dict_index_check_search_tuple() in InnoDB -- the search tuple carries
|
||||
more fields than the index has unique fields in the tree. The engine side
|
||||
of that (MDEV-40005) is newer than the worker side, and nothing in the SQL
|
||||
layer has driven it yet; until it does, this is the check that keeps the
|
||||
two apart.
|
||||
*/
|
||||
if (!((join_tab->type == JT_ALL || join_tab->type == JT_RANGE) &&
|
||||
join_tab->read_first_record == join_init_read_record))
|
||||
return false;
|
||||
|
||||
/*
|
||||
The plan may be relying on this table's rows arriving in index order to
|
||||
satisfy ORDER BY or GROUP BY without a filesort, but a parallel scan
|
||||
does not preserve that order. Reject parallelization in that case.
|
||||
*/
|
||||
if (join_tab->join->ordered_index_usage != JOIN::ordered_index_void)
|
||||
return false;
|
||||
|
||||
if (join_tab->filesort || join_tab->filesort_result ||
|
||||
join_tab->need_to_build_rowid_filter || join_tab->rowid_filter ||
|
||||
join_tab->distinct)
|
||||
return false;
|
||||
|
||||
const uint32 pscan_support= join_tab->table->file->parallel_scan_support();
|
||||
SQL_SELECT *sql_select= join_tab->select;
|
||||
|
||||
if (sql_select && sql_select->quick)
|
||||
{
|
||||
/*
|
||||
The case of a range scan.
|
||||
Only a plain range over the clustered index can be handed to the
|
||||
parallel coordinator, and only if it has few enough intervals to be
|
||||
worth splitting.
|
||||
|
||||
OLEGS: we try to make handler API engine-agnostic but here ^^^ we mention
|
||||
"clustered index" which is only valid for InnoDB. In fact, what we currently
|
||||
support is a full table scan which is indeed a scan of the clustered index
|
||||
in InnoDB. Need to think about it
|
||||
|
||||
Why the clustered index and not any index: the coordinator converts each
|
||||
endpoint against it -- ha_innobase's pscan_convert_key() builds the tuple
|
||||
from table->s->primary_key -- so an interval over a secondary index would
|
||||
be read as a primary-key interval and scan the wrong part of the table.
|
||||
Asking for one is what trips dict_index_check_search_tuple(), the same
|
||||
assertion the index scan above hits.
|
||||
*/
|
||||
const uint MAX_PARALLEL_SCAN_RANGES= 128;
|
||||
if (sql_select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE &&
|
||||
sql_select->quick->index < join_tab->table->s->keys &&
|
||||
join_tab->use_quick != 2 /*exclude dynamic range*/ &&
|
||||
((QUICK_RANGE_SELECT*) sql_select->quick)->num_ranges() <=
|
||||
MAX_PARALLEL_SCAN_RANGES)
|
||||
{
|
||||
/*
|
||||
The engine decides whether it can partition this particular index -
|
||||
see ha_innobase::pscan_resolve_index(), which declines spatial, FTS,
|
||||
virtual-column and descending indexes - and we fall back to the serial
|
||||
reader on HA_ERR_UNSUPPORTED.
|
||||
|
||||
TODO: this looks very InnoDB-specific. We should have a more generic
|
||||
way to ask the engine
|
||||
*/
|
||||
if (!(join_tab->table->s->primary_key < MAX_KEY &&
|
||||
sql_select->quick->index == join_tab->table->s->primary_key))
|
||||
return false; // not the clustered index
|
||||
|
||||
/*
|
||||
A rowid-ordered scan collects the row ids, sorts them and then sweeps
|
||||
the clustered index in that order. Taking such a plan would quietly
|
||||
throw the sorted sweep away. Leave it serial.
|
||||
*/
|
||||
if (((QUICK_RANGE_SELECT*) sql_select->quick)->mrr_flags &
|
||||
DSMRR_IMPL_SORT_ROWIDS)
|
||||
return false;
|
||||
|
||||
return (pscan_support & handler::PSCAN_TABLE_RANGE) != 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return (pscan_support & handler::PSCAN_TABLE_FULL) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@brief
|
||||
@@ -3582,6 +3700,45 @@ int JOIN::optimize_stage2()
|
||||
if (init_range_rowid_filters())
|
||||
DBUG_RETURN(1);
|
||||
|
||||
/*
|
||||
Run this query's driving-table scan in parallel workers if possible.
|
||||
|
||||
1) worker threads are available;
|
||||
2) the table itself is one a worker can read and ship
|
||||
(table_can_be_parallel_scanned);
|
||||
3) the access method the plan settled on is one the engine will divide
|
||||
(is_parallel_scan_applicable), which is also where the engine's
|
||||
parallel_scan_support() bitmap is matched against it.
|
||||
|
||||
This is the last thing optimize_stage2() decides, and it has to be:
|
||||
everything above can still change the first table's access method, and
|
||||
make_join_readinfo() -- which is where this check used to live -- runs
|
||||
before test_if_skip_sort_order() has had its say. A table given an ordered
|
||||
index scan after the fact is not one that can be handed out in chunks.
|
||||
|
||||
Engine-intrinsic constraints (consistent-read only, record format,
|
||||
discarded tablespace, ...) are not known here. They are enforced later
|
||||
inside parallel_init_coordinator(), which declines with HA_ERR_UNSUPPORTED,
|
||||
and parallel_init_read_record() then restores the serial reader.
|
||||
*/
|
||||
{
|
||||
JOIN_TAB *first= first_linear_tab(this, WITH_BUSH_ROOTS,
|
||||
WITHOUT_CONST_TABLES);
|
||||
if (thd->variables.parallel_worker_threads > 0 && //1
|
||||
first && table_can_be_parallel_scanned(first->table) && //2
|
||||
is_parallel_scan_applicable(first)) //3
|
||||
{
|
||||
first->use_parallel_scan= true;
|
||||
first->read_first_record= parallel_init_read_record;
|
||||
if (unlikely(thd->trace_started()))
|
||||
{
|
||||
Json_writer_object trace_pscan(thd);
|
||||
trace_pscan.add("chosen_for_parallel_scan",
|
||||
first->table->alias.c_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error= 0;
|
||||
|
||||
if (select_options & SELECT_DESCRIBE)
|
||||
@@ -4372,8 +4529,6 @@ bool JOIN::make_aggr_tables_info()
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool
|
||||
JOIN::create_postjoin_aggr_table(JOIN_TAB *tab, List<Item> *table_fields,
|
||||
ORDER *table_group,
|
||||
@@ -4406,13 +4561,20 @@ JOIN::create_postjoin_aggr_table(JOIN_TAB *tab, List<Item> *table_fields,
|
||||
if (tmp_table_keep_current_rowid)
|
||||
add_fields_for_current_rowid(tab, table_fields);
|
||||
tab->tmp_table_param->skip_create_table= true;
|
||||
|
||||
TABLE* table= create_tmp_table(thd, tab->tmp_table_param, *table_fields,
|
||||
table_group, distinct,
|
||||
save_sum_fields, select_options,
|
||||
table_rows_limit,
|
||||
&empty_clex_str, true, keep_row_order);
|
||||
if (!table)
|
||||
DBUG_RETURN(true);
|
||||
goto err;
|
||||
// instantiate this now so the parallel worker manager can write to it
|
||||
if (instantiate_tmp_table(table, tab->tmp_table_param->keyinfo,
|
||||
tab->tmp_table_param->start_recinfo,
|
||||
&tab->tmp_table_param->recinfo,
|
||||
select_options))
|
||||
goto err;
|
||||
tmp_table_param.using_outer_summary_function=
|
||||
tab->tmp_table_param->using_outer_summary_function;
|
||||
tab->join= this;
|
||||
@@ -4910,10 +5072,14 @@ int JOIN::exec()
|
||||
select_lex->select_number))
|
||||
dbug_serve_apcs(thd, 1);
|
||||
);
|
||||
|
||||
ANALYZE_START_TRACKING(thd, &explain->time_tracker);
|
||||
res= exec_inner();
|
||||
ANALYZE_STOP_TRACKING(thd, &explain->time_tracker);
|
||||
|
||||
if (parallel_work_manager)
|
||||
parallel_work_manager->finalize_parallel_workers(thd, this);
|
||||
|
||||
DBUG_EXECUTE_IF("show_explain_probe_join_exec_end",
|
||||
if (dbug_user_var_equals_int(thd,
|
||||
"show_explain_probe_select_id",
|
||||
@@ -9963,6 +10129,19 @@ best_access_path(JOIN *join,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
A full table scan of the driving table can be shared across
|
||||
parallel_worker_threads workers (see make_join_readinfo). Discount its
|
||||
row-read/copy cost accordingly so the optimizer accounts for the
|
||||
parallelism. Only the driving table (idx == const_tables) is ever
|
||||
parallel-scanned, and 'cost' is a local copy, so the cached per-table
|
||||
estimate is untouched.
|
||||
*/
|
||||
if ((type == JT_ALL || type == JT_RANGE) &&
|
||||
table_can_be_parallel_scanned(table) &&
|
||||
idx == join->const_tables)
|
||||
scale_cost_for_parallel_scan(thd, table, &cost);
|
||||
|
||||
/*
|
||||
Note: the condition checked here is very out of date and incorrect.
|
||||
Below, we use a more accurate check when assigning the value of
|
||||
@@ -13485,7 +13664,9 @@ bool JOIN::get_best_combination()
|
||||
j->index= cur_pos->forced_index;
|
||||
}
|
||||
else
|
||||
{
|
||||
j->type= JT_ALL;
|
||||
}
|
||||
if (cur_pos->use_join_buffer &&
|
||||
tablenr != const_tables)
|
||||
full_join= 1;
|
||||
@@ -16693,7 +16874,6 @@ make_join_readinfo(JOIN *join, ulonglong options, uint no_jbuf_after)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DBUG_RETURN(FALSE);
|
||||
}
|
||||
|
||||
@@ -16830,6 +17010,7 @@ void JOIN_TAB::cleanup()
|
||||
else
|
||||
table->file->ha_index_or_rnd_end();
|
||||
preread_init_done= FALSE;
|
||||
|
||||
if (table->pos_in_table_list && table->pos_in_table_list->jtbm_subselect)
|
||||
{
|
||||
if (table->pos_in_table_list->jtbm_subselect->is_jtbm_const_tab)
|
||||
@@ -17275,6 +17456,16 @@ void JOIN::join_free()
|
||||
bool can_unlock= full;
|
||||
DBUG_ENTER("JOIN::join_free");
|
||||
|
||||
/*
|
||||
Parallel-scan workers read this join's source and batch tables. cleanup()
|
||||
below frees those tables, so the workers must be stopped and reaped first
|
||||
-- otherwise a worker that has not yet seen the stop request dereferences a
|
||||
freed table->file. On normal completion the workers have already finished,
|
||||
so this is a cheap no-op.
|
||||
*/
|
||||
if (parallel_work_manager)
|
||||
parallel_work_manager->quiesce_workers();
|
||||
|
||||
cleanup(full);
|
||||
|
||||
for (tmp_unit= select_lex->first_inner_unit();
|
||||
@@ -17456,6 +17647,12 @@ void JOIN::cleanup(bool full)
|
||||
{
|
||||
set_items_ref_array(items0);
|
||||
}
|
||||
|
||||
if (parallel_work_manager)
|
||||
{
|
||||
delete parallel_work_manager;
|
||||
parallel_work_manager= NULL;
|
||||
}
|
||||
DBUG_VOID_RETURN;
|
||||
}
|
||||
|
||||
@@ -23340,12 +23537,13 @@ bool Virtual_tmp_table::check_assignability_from(const TABLE &table,
|
||||
}
|
||||
|
||||
|
||||
bool open_tmp_table(TABLE *table)
|
||||
bool open_tmp_table(TABLE *table, bool cross_thread)
|
||||
{
|
||||
int error;
|
||||
if (unlikely((error= table->file->ha_open(table, table->s->path.str, O_RDWR,
|
||||
HA_OPEN_TMP_TABLE |
|
||||
HA_OPEN_INTERNAL_TABLE |
|
||||
(cross_thread? 0 :
|
||||
HA_OPEN_INTERNAL_TABLE) |
|
||||
HA_OPEN_SIZE_TRACKING))))
|
||||
{
|
||||
table->file->print_error(error, MYF(0)); /* purecov: inspected */
|
||||
@@ -24352,7 +24550,8 @@ do_select(JOIN *join, Procedure *procedure)
|
||||
bool instantiate_tmp_table(TABLE *table, KEY *keyinfo,
|
||||
TMP_ENGINE_COLUMNDEF *start_recinfo,
|
||||
TMP_ENGINE_COLUMNDEF **recinfo,
|
||||
ulonglong options)
|
||||
ulonglong options,
|
||||
bool cross_thread)
|
||||
{
|
||||
DBUG_ASSERT(table->s->keys == 0 || table->key_info == keyinfo);
|
||||
DBUG_ASSERT(table->s->keys <= 1);
|
||||
@@ -24370,7 +24569,7 @@ bool instantiate_tmp_table(TABLE *table, KEY *keyinfo,
|
||||
empty_record(table);
|
||||
table->status= STATUS_NO_RECORD;
|
||||
}
|
||||
if (open_tmp_table(table))
|
||||
if (open_tmp_table(table, cross_thread))
|
||||
return TRUE;
|
||||
|
||||
return FALSE;
|
||||
@@ -31262,6 +31461,7 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta,
|
||||
tab_type= type == JT_HASH ? JT_HASH_RANGE : JT_RANGE;
|
||||
}
|
||||
eta->type= tab_type;
|
||||
eta->use_parallel_scan= use_parallel_scan;
|
||||
|
||||
/* Build "possible_keys" value */
|
||||
// psergey-todo: why does this use thd MEM_ROOT??? Doesn't this
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "sql_update.h"
|
||||
|
||||
#include "cset_narrowing.h"
|
||||
#include "sql_parallel_workers.h"
|
||||
|
||||
typedef struct st_join_table JOIN_TAB;
|
||||
/* Values in optimize */
|
||||
@@ -667,6 +668,7 @@ typedef struct st_join_table {
|
||||
bool shortcut_for_distinct;
|
||||
bool sorted;
|
||||
bool cached_pfs_batch_update;
|
||||
bool use_parallel_scan;
|
||||
|
||||
/*
|
||||
If it's not 0 the number stored this field indicates that the index
|
||||
@@ -1992,6 +1994,8 @@ public:
|
||||
*/
|
||||
Sql_cmd_dml *sql_cmd_dml;
|
||||
|
||||
pwt_manager *parallel_work_manager{0};
|
||||
|
||||
JOIN(THD *thd_arg, List<Item> &fields_arg, ulonglong select_options_arg,
|
||||
select_result *result_arg)
|
||||
:fields_list(fields_arg)
|
||||
@@ -2024,6 +2028,7 @@ public:
|
||||
int optimize_inner();
|
||||
int optimize_stage2();
|
||||
int optimize_stage2_and_finish();
|
||||
void choose_parallel_scan();
|
||||
bool build_explain();
|
||||
int reinit();
|
||||
int init_execution();
|
||||
@@ -2220,7 +2225,6 @@ private:
|
||||
In this case we can stop scanning t2 when we have found one t1.a
|
||||
*/
|
||||
void optimize_distinct();
|
||||
|
||||
void cleanup_item_list(List<Item> &items) const;
|
||||
bool add_having_as_table_cond(JOIN_TAB *tab);
|
||||
bool make_aggr_tables_info();
|
||||
@@ -2943,8 +2947,9 @@ bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
|
||||
bool instantiate_tmp_table(TABLE *table, KEY *keyinfo,
|
||||
TMP_ENGINE_COLUMNDEF *start_recinfo,
|
||||
TMP_ENGINE_COLUMNDEF **recinfo,
|
||||
ulonglong options);
|
||||
bool open_tmp_table(TABLE *table);
|
||||
ulonglong options,
|
||||
bool cross_thread= false);
|
||||
bool open_tmp_table(TABLE *table, bool cross_thread= false);
|
||||
void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist);
|
||||
void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array);
|
||||
bool sort_and_filter_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse,
|
||||
|
||||
@@ -2496,6 +2496,17 @@ Sys_slave_parallel_workers(
|
||||
ON_UPDATE(fix_slave_parallel_threads));
|
||||
|
||||
|
||||
static Sys_var_on_access_global<Sys_var_ulong,
|
||||
PRIV_SET_SYSTEM_GLOBAL_VAR_PARALLEL_WORKER_THREADS>
|
||||
Sys_parallel_worker_threads(
|
||||
"parallel_worker_threads",
|
||||
"Number of worker threads available for parallel query execution. "
|
||||
"0 means parallel execution is disabled",
|
||||
SESSION_VAR(parallel_worker_threads), CMD_LINE(REQUIRED_ARG),
|
||||
VALID_RANGE(0,100), DEFAULT(0), BLOCK_SIZE(1));
|
||||
/* VALID_RANGE(0,100), DEFAULT(1), BLOCK_SIZE(1)); // testing 123 */
|
||||
|
||||
|
||||
static bool
|
||||
check_slave_domain_parallel_threads(sys_var *self, THD *thd, set_var *var)
|
||||
{
|
||||
|
||||
@@ -15053,7 +15053,7 @@ int ha_innobase::parallel_init_worker(Parallel_worker_ctx *wctx)
|
||||
DBUG_ASSERT(worker_ctx && worker_ctx->m_pcoordinator);
|
||||
auto exec_ctx= worker_ctx->m_pcoordinator->get_job_for_worker(worker_ctx);
|
||||
if (exec_ctx == nullptr)
|
||||
return HA_ERR_END_OF_FILE; // No more data
|
||||
return HA_ERR_END_OF_FILE; // No more data
|
||||
|
||||
/* Save the prebuilt-owned search_tuple: parallel_get_next_row() points
|
||||
m_prebuilt->search_tuple at a chunk boundary key owned by the
|
||||
|
||||
Reference in New Issue
Block a user