From 04009f8788e8180c2dbf5b8200dcbf70238371e8 Mon Sep 17 00:00:00 2001 From: zheng_yuanyue <1139292071@qq.com> Date: Wed, 21 May 2025 19:50:48 +0800 Subject: [PATCH] add sqlbypass --- mysql-test/r/all_persisted_variables.result | 8 +- mysql-test/r/mysqld--help-notwin.result | 4 + mysql-test/suite/sys_vars/r/all_vars.result | 2 + mysql-test/t/all_persisted_variables.test | 2 +- sql/CMakeLists.txt | 1 + sql/mysqld.cc | 14 + sql/mysqld.h | 2 + sql/opt_range.cc | 280 ++++++++++++++++ sql/opt_range.h | 4 + sql/sql_class.cc | 17 +- sql/sql_executor.cc | 6 +- sql/sql_executor.h | 5 + sql/sql_lex.cc | 1 - sql/sql_lex.h | 13 + sql/sql_opt_exec_shared.h | 1 + sql/sql_optimizer.cc | 36 ++- sql/sql_optimizer.h | 8 +- sql/sql_plan_cache.cc | 334 ++++++++++++++++++++ sql/sql_plan_cache.h | 59 ++++ sql/sql_prepare.cc | 2 + sql/sql_resolver.cc | 31 ++ sql/sql_select.cc | 140 ++++++-- sql/sql_union.cc | 31 +- sql/sys_vars.cc | 7 + 24 files changed, 963 insertions(+), 45 deletions(-) create mode 100644 sql/sql_plan_cache.cc create mode 100644 sql/sql_plan_cache.h diff --git a/mysql-test/r/all_persisted_variables.result b/mysql-test/r/all_persisted_variables.result index 2d4eae332..2e5993ce5 100644 --- a/mysql-test/r/all_persisted_variables.result +++ b/mysql-test/r/all_persisted_variables.result @@ -39,7 +39,7 @@ include/assert.inc [Expect 500+ variables in the table. Due to open Bugs, we are # Test SET PERSIST -include/assert.inc [Expect 412 persisted variables in the table.] +include/assert.inc [Expect 413 persisted variables in the table.] ************************************************************ * 3. Restart server, it must preserve the persisted variable @@ -47,9 +47,9 @@ include/assert.inc [Expect 412 persisted variables in the table.] ************************************************************ # restart -include/assert.inc [Expect 412 persisted variables in persisted_variables table.] -include/assert.inc [Expect 412 persisted variables shown as PERSISTED in variables_info table.] -include/assert.inc [Expect 412 persisted variables with matching peristed and global values.] +include/assert.inc [Expect 413 persisted variables in persisted_variables table.] +include/assert.inc [Expect 413 persisted variables shown as PERSISTED in variables_info table.] +include/assert.inc [Expect 413 persisted variables with matching peristed and global values.] ************************************************************ * 4. Test RESET PERSIST IF EXISTS. Verify persisted variable diff --git a/mysql-test/r/mysqld--help-notwin.result b/mysql-test/r/mysqld--help-notwin.result index 231d543a2..6f41abe40 100644 --- a/mysql-test/r/mysqld--help-notwin.result +++ b/mysql-test/r/mysqld--help-notwin.result @@ -996,6 +996,9 @@ The following options may be given as the first argument: even if present. (Defaults to on; use --skip-persisted-globals-load to disable.) --pid-file=name Pid file used by safe_mysqld + --plan-cache Sys_plan_cache when first execute prepare stmt the remove + product execute plan time + (Defaults to on; use --skip-plan-cache to disable.) --plugin-dir=name Directory for plugins --plugin-load=name Optional semicolon-separated list of plugins to load, where each plugin is identified as name=library, where @@ -1743,6 +1746,7 @@ performance-schema-show-processlist FALSE performance-schema-users-size -1 persist-only-admin-x509-subject persisted-globals-load TRUE +plan-cache TRUE port #### port-open-timeout 0 preload-buffer-size 32768 diff --git a/mysql-test/suite/sys_vars/r/all_vars.result b/mysql-test/suite/sys_vars/r/all_vars.result index 6b771ac40..39bef2be2 100644 --- a/mysql-test/suite/sys_vars/r/all_vars.result +++ b/mysql-test/suite/sys_vars/r/all_vars.result @@ -48,6 +48,8 @@ partial_revokes partial_revokes password_require_current password_require_current +plan_cache +plan_cache regexp_stack_limit regexp_stack_limit regexp_time_limit diff --git a/mysql-test/t/all_persisted_variables.test b/mysql-test/t/all_persisted_variables.test index 00c707b52..158f6fa33 100644 --- a/mysql-test/t/all_persisted_variables.test +++ b/mysql-test/t/all_persisted_variables.test @@ -41,7 +41,7 @@ call mtr.add_suppression("\\[Warning\\] .*MY-\\d+.* Changing innodb_extend_and_i call mtr.add_suppression("Failed to initialize TLS for channel: mysql_main"); let $total_global_vars=`SELECT COUNT(*) FROM performance_schema.global_variables where variable_name NOT LIKE 'ndb_%'`; -let $total_persistent_vars=412; +let $total_persistent_vars=413; --echo *************************************************************** --echo * 0. Verify that variables present in performance_schema.global diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 5fd225c1e..2bccb4e18 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -531,6 +531,7 @@ SET(SQL_SHARED_SOURCES sql_partition.cc sql_partition_admin.cc sql_planner.cc + sql_plan_cache.cc sql_plugin.cc sql_plugin_var.cc sql_prepare.cc diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 83643f76a..3b99cf3c0 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -1137,6 +1137,7 @@ LEX_STRING opt_init_connect, opt_init_slave; /* Global variables */ LEX_STRING opt_mandatory_roles; +bool opt_plan_cache = false; bool opt_mandatory_roles_cache = false; bool opt_always_activate_granted_roles = false; bool opt_bin_log; @@ -1363,6 +1364,9 @@ uint sync_binlog_period = 0, sync_relaylog_period = 0, opt_mts_checkpoint_period, opt_mts_checkpoint_group; ulong expire_logs_days = 0; ulong binlog_expire_logs_seconds = 0; + +std::atomic cached_prepared_stmt_count{0}; + /** Soft upper limit for number of sp_head objects that can be stored in the sp_cache for one connection. @@ -8893,6 +8897,13 @@ static int show_prepared_stmt_count(THD *, SHOW_VAR *var, char *buff) { return 0; } +static int show_cached_prepared_stmt_count(THD *, SHOW_VAR *var, char *buff) { + var->type = SHOW_LONG; + var->value = buff; + *((long *)buff) = (long)(cached_prepared_stmt_count.load()); + return 0; +} + static int show_table_definitions(THD *, SHOW_VAR *var, char *buff) { var->type = SHOW_LONG; var->value = buff; @@ -9180,6 +9191,8 @@ SHOW_VAR status_vars[] = { SHOW_SCOPE_ALL}, {"Prepared_stmt_count", (char *)&show_prepared_stmt_count, SHOW_FUNC, SHOW_SCOPE_GLOBAL}, + {"Cached_prepared_stmt_count", (char *)&show_cached_prepared_stmt_count, + SHOW_FUNC, SHOW_SCOPE_GLOBAL}, {"Queries", (char *)&show_queries, SHOW_FUNC, SHOW_SCOPE_ALL}, {"Questions", (char *)offsetof(System_status_var, questions), SHOW_LONGLONG_STATUS, SHOW_SCOPE_ALL}, @@ -9504,6 +9517,7 @@ static int mysql_init_variables() { binlog_cache_use = binlog_cache_disk_use = 0; mysqld_user = mysqld_chroot = opt_init_file = opt_bin_logname = nullptr; prepared_stmt_count = 0; + cached_prepared_stmt_count = 0; mysqld_unix_port = opt_mysql_tmpdir = my_bind_addr_str = NullS; new (&mysql_tmpdir_list) MY_TMPDIR; memset(&global_status_var, 0, sizeof(global_status_var)); diff --git a/sql/mysqld.h b/sql/mysqld.h index a7a80a223..3c8a429c0 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -308,6 +308,7 @@ extern ulong rpl_stop_slave_timeout; extern bool log_bin_use_v1_row_events; extern ulong what_to_log, flush_time; extern ulong max_prepared_stmt_count, prepared_stmt_count; +extern std::atomic cached_prepared_stmt_count; extern ulong open_files_limit; extern bool clone_startup; extern bool clone_recovery_error; @@ -775,6 +776,7 @@ bool update_named_pipe_full_access_group(const char *new_group_name); #endif extern LEX_STRING opt_mandatory_roles; +extern bool opt_plan_cache; extern bool opt_mandatory_roles_cache; extern bool opt_always_activate_granted_roles; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index ec848bf6c..0577ecabd 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -188,6 +188,7 @@ #include "sql/thr_malloc.h" #include "sql/uniques.h" // Unique #include "template_utils.h" +#include "sql/thd_raii.h" using std::max; using std::min; @@ -653,6 +654,10 @@ bool get_quick_keys(PARAM *param, QUICK_RANGE_SELECT *quick, KEY_PART *key, SEL_ARG *key_tree, uchar *min_key, uint min_key_flag, uchar *max_key, uint max_key_flag, uint *desc_flag, uint num_key_parts); +bool get_quick_keys(QUICK_RANGE_SELECT *quick, KEY_PART *key, + SEL_ARG *key_tree, uchar *min_key, uint min_key_flag, + uchar *max_key, uint max_key_flag, uint *desc_flag, + uint num_key_parts); static bool eq_tree(const SEL_ROOT *a, const SEL_ROOT *b); static bool eq_tree(const SEL_ARG *a, const SEL_ARG *b); static bool eq_ranges_exceeds_limit(const SEL_ROOT *keypart, uint *count, @@ -9524,6 +9529,281 @@ end: return false; } +bool get_quick_keys(QUICK_RANGE_SELECT *quick, KEY_PART *key, + SEL_ARG *key_tree, uchar *min_key, uint min_key_flag, + uchar *max_key, uint max_key_flag, uint *desc_flag, + uint num_key_parts) { + QUICK_RANGE *range; + uint flag = 0; + int min_part = key_tree->part - 1, // # of keypart values in min_key buffer + max_part = key_tree->part - 1; // # of keypart values in max_key buffer + + const bool asc = key_tree->is_ascending; + SEL_ARG *cur_key_tree = asc ? key_tree->left : key_tree->right; + if (cur_key_tree != null_element) + if (get_quick_keys(quick, key, cur_key_tree, min_key, min_key_flag, + max_key, max_key_flag, desc_flag, num_key_parts)) + return true; + uchar *tmp_min_key = min_key, *tmp_max_key = max_key; + uchar *param_min_key = min_key, *param_max_key = max_key; + key_tree->store_min_max_values(key[key_tree->part].store_length, &tmp_min_key, + min_key_flag, &tmp_max_key, max_key_flag, + &min_part, &max_part); + if (!asc) flag |= DESC_FLAG; + + // Stop processing key values if this is the last key part that needs to be + // looked into. See get_quick_select() for details. + if ((num_key_parts > 1) && key_tree->next_key_part && + key_tree->next_key_part->type == SEL_ROOT::Type::KEY_RANGE && + key_tree->next_key_part->root->part == + key_tree->part + 1) { // const key as prefix + if ((tmp_min_key - min_key) == (tmp_max_key - max_key) && + memcmp(min_key, max_key, (uint)(tmp_max_key - max_key)) == 0 && + key_tree->min_flag == 0 && key_tree->max_flag == 0) { + if (get_quick_keys(quick, key, key_tree->next_key_part->root, + tmp_min_key, min_key_flag | key_tree->get_min_flag(), + tmp_max_key, max_key_flag | key_tree->get_max_flag(), + (desc_flag ? desc_flag : &flag), num_key_parts - 1)) + return true; + goto end; // Ugly, but efficient + } + { + uint tmp_min_flag = key_tree->get_min_flag(); + uint tmp_max_flag = key_tree->get_max_flag(); + key_tree->store_next_min_max_keys(key, &tmp_min_key, &tmp_min_flag, + &tmp_max_key, &tmp_max_flag, &min_part, + &max_part); + flag |= tmp_min_flag | tmp_max_flag; + } + } else { + if (asc) + flag = (key_tree->min_flag & GEOM_FLAG) + ? key_tree->min_flag + : key_tree->min_flag | key_tree->max_flag; + else { + // Invert flags for DESC keypart + flag |= invert_min_flag(key_tree->min_flag) | + invert_max_flag(key_tree->max_flag); + } + } + + /* + Ensure that some part of min_key and max_key are used. If not, + regard this as no lower/upper range + */ + if ((flag & GEOM_FLAG) == 0) { + if (tmp_min_key != param_min_key) + flag &= ~NO_MIN_RANGE; + else + flag |= NO_MIN_RANGE; + if (tmp_max_key != param_max_key) + flag &= ~NO_MAX_RANGE; + else + flag |= NO_MAX_RANGE; + } + if ((flag & ~DESC_FLAG) == 0) { + uint length = (uint)(tmp_min_key - param_min_key); + if (length == (uint)(tmp_max_key - param_max_key) && + !memcmp(param_min_key, param_max_key, length)) { + const KEY *table_key = quick->head->key_info + quick->index; + flag |= EQ_RANGE; + /* + Note that keys which are extended with PK parts have no + HA_NOSAME flag. So we can use user_defined_key_parts. + */ + if ((table_key->flags & HA_NOSAME) && + key_tree->part == table_key->user_defined_key_parts - 1) { + if ((table_key->flags & HA_NULL_PART_KEY) && + null_part_in_key(key, param_min_key, + (uint)(tmp_min_key - param_min_key))) + flag |= NULL_RANGE; + else + flag |= UNIQUE_RANGE; + } + } + } + /* + Set DESC flag. We need this flag set according to the first keypart. + Depending on it, key values will be scanned either forward or backward, + preserving the order or records in the index along multiple ranges. + */ + if (desc_flag) flag = (flag & ~DESC_FLAG) | *desc_flag; + + /* Get range for retrieving rows in QUICK_SELECT::get_next */ + if (!(range = new (*THR_MALLOC) + QUICK_RANGE(param_min_key, (uint)(tmp_min_key - param_min_key), + min_part >= 0 ? make_keypart_map(min_part) : 0, + param_max_key, (uint)(tmp_max_key - param_max_key), + max_part >= 0 ? make_keypart_map(max_part) : 0, flag, + key_tree->rkey_func_flag))) + return true; // out of memory + + quick->max_used_key_length = + std::max(quick->max_used_key_length, uint(range->min_length)); + quick->max_used_key_length = + std::max(quick->max_used_key_length, uint(range->max_length)); + quick->used_key_parts = + std::max(quick->used_key_parts, uint(key_tree->part + 1)); + if (quick->ranges.push_back(range)) return true; + +end: + cur_key_tree = asc ? key_tree->right : key_tree->left; + if (cur_key_tree != null_element) + return get_quick_keys(quick, key, cur_key_tree, min_key, + min_key_flag, max_key, max_key_flag, desc_flag, + num_key_parts); + return false; +} + +bool QEP_TAB::replace_cache_key(THD *thd) { + PARAM param; + SEL_TREE *tree = nullptr; + MEM_ROOT alloc; + KEY_PART *key_parts; + KEY *key_info; + QUICK_RANGE_SELECT *quick = dynamic_cast(m_qs->quick()); + + if (!quick) { + return true; + } + + TABLE *const head = table(); + table_map prev_tables = 0; + table_map const_tables = join()->found_const_table_map; + table_map read_tables = join()->is_executed() ? + (prefix_tables() & ~added_tables()) : const_tables; + Query_block *query_block = join()->query_block; + Key_map *needed_reg_ptr = &(join()->join_tab->needed_reg); + const enum_order interesting_order = ORDER_NOT_RELEVANT; + Item *cond = join()->where_cond; + + set_skip_records_in_range(true); + table()->init_cost_model(join()->cost_model()); + table()->in_use = thd; + quick->head = table(); + quick->set_handler(table()->file); + + /* set up parameter that is passed to all functions */ + param.thd = thd; + param.baseflag = head->file->ha_table_flags(); + param.prev_tables = prev_tables | const_tables | INNER_TABLE_BIT; + param.read_tables = read_tables | INNER_TABLE_BIT; + param.current_table = head->pos_in_table_list->map(); + param.table = head; + param.query_block = query_block; + param.keys = 0; + param.is_ror_scan = false; + param.mem_root = &alloc; + param.old_root = thd->mem_root; + param.needed_reg = needed_reg_ptr; + param.imerge_cost_buff.reset(); + param.using_real_indexes = true; + param.remove_jump_scans = true; + param.force_default_mrr = (interesting_order == ORDER_DESC); + param.order_direction = interesting_order; + param.use_index_statistics = false; + /* + Set index_merge_allowed from OPTIMIZER_SWITCH_INDEX_MERGE. + Notice also that OPTIMIZER_SWITCH_INDEX_MERGE disables all + index merge sub strategies. + */ + param.index_merge_allowed = + thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE); + param.index_merge_union_allowed = + param.index_merge_allowed && + thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE_UNION); + param.index_merge_sort_union_allowed = + param.index_merge_allowed && + thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION); + param.index_merge_intersect_allowed = + param.index_merge_allowed && + thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT); + + param.skip_records_in_range = skip_records_in_range(); + + init_sql_alloc(key_memory_test_quick_select_exec, &alloc, + thd->variables.range_alloc_block_size, 0); + alloc.set_max_capacity(thd->variables.range_optimizer_max_mem_size); + alloc.set_error_for_capacity_exceeded(true); + thd->push_internal_handler(¶m.error_handler); + if (!(param.key_parts = + (KEY_PART *)alloc.Alloc(sizeof(KEY_PART) * head->s->key_parts)) || + fill_used_fields_bitmap(¶m)) { + thd->pop_internal_handler(); + free_root(&alloc, MYF(0)); // Return memory & allocator + return true; // Can't use range + } + key_parts = param.key_parts; + thd->mem_root = &alloc; + + { + /* + Make an array with description of all key parts of all table keys. + This is used in get_mm_parts function. + */ + key_info = head->key_info; + for (uint idx = 0; idx < head->s->keys; idx++, key_info++) { + KEY_PART_INFO *key_part_info; + + if (hint_key_state(thd, head->pos_in_table_list, idx, + NO_RANGE_HINT_ENUM, 0)) { + continue; + } + + if (key_info->flags & HA_FULLTEXT) { + continue; + } + + param.key[param.keys] = key_parts; + key_part_info = key_info->key_part; + for (uint part = 0; part < actual_key_parts(key_info); + part++, key_parts++, key_part_info++) { + key_parts->key = param.keys; + key_parts->part = part; + key_parts->length = key_part_info->length; + key_parts->store_length = key_part_info->store_length; + key_parts->field = key_part_info->field; + key_parts->null_bit = key_part_info->null_bit; + key_parts->image_type = (part < key_info->user_defined_key_parts && + key_info->flags & HA_SPATIAL) + ? Field::itMBR + : Field::itRAW; + key_parts->flag = key_part_info->key_part_flag; + } + param.real_keynr[param.keys++] = idx; + } + } + param.key_parts_end = key_parts; + + if (cond) { + tree = get_mm_tree(¶m, cond); + } + + if (tree->type == SEL_TREE::IMPOSSIBLE) { + thd->mem_root = param.old_root; + thd->pop_internal_handler(); + free_root(&alloc, MYF(0)); + return false; + } + + thd->mem_root = param.old_root; + uint key_idx = quick->index; + KEY_PART *key = quick->key_parts; + SEL_ARG *key_tree = tree->keys[key_idx]->root; + uchar *min_key = tree->keys[key_idx]->root->min_value; + uchar *max_key = tree->keys[key_idx]->root->max_value; + + if (get_quick_keys(quick, key, key_tree, min_key, 0, max_key, 0, nullptr, + MAX_REF_PARTS) || quick->init()) { + delete quick; + quick = nullptr; + return true; + } + thd->pop_internal_handler(); + free_root(&alloc, MYF(0)); + return false; +} + /* Return 1 if there is only one range and this uses the whole unique key */ diff --git a/sql/opt_range.h b/sql/opt_range.h index 58515d0af..2c347b97e 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -483,7 +483,9 @@ class QUICK_RANGE_SELECT : public QUICK_SELECT_I { friend class QUICK_ROR_INTERSECT_SELECT; friend class QUICK_GROUP_MIN_MAX_SELECT; +public: Quick_ranges ranges; /* ordered array of range ptrs */ +protected: bool free_file; /* TRUE <=> this->file is "owned" by this quick select */ /* Range pointers to be used when not using MRR interface */ @@ -499,8 +501,10 @@ class QUICK_RANGE_SELECT : public QUICK_SELECT_I { uint mrr_buf_size; /* copy from thd->variables.read_rnd_buff_size */ HANDLER_BUFFER *mrr_buf_desc; /* the handler buffer */ +public: /* Info about index we're scanning */ KEY_PART *key_parts; +protected: KEY_PART_INFO *key_part_info; bool dont_free; /* Used by QUICK_SELECT_DESC */ diff --git a/sql/sql_class.cc b/sql/sql_class.cc index e01d33815..9294a2d2c 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -907,6 +907,8 @@ void THD::cleanup_connection(void) { assert(server_status == SERVER_STATUS_AUTOCOMMIT); /* check prepared stmts are cleaned up */ assert(prepared_stmt_count == 0); + /* check prepared stmts cached are cleaned up */ + assert(cached_prepared_stmt_count == 0); /* check diagnostic area is cleaned up */ assert(get_stmt_da()->status() == Diagnostics_area::DA_EMPTY); /* check if temp tables are deleted */ @@ -1749,6 +1751,12 @@ Prepared_statement *Prepared_statement_map::find(ulong id) { } void Prepared_statement_map::erase(Prepared_statement *statement) { + if (statement->lex->query_block->qep_cache_state == + Query_block::QEP_CACHE_READY) { + assert(cached_prepared_stmt_count > 0); + --cached_prepared_stmt_count; + } + if (statement == m_last_found_statement) m_last_found_statement = nullptr; if (statement->name().str) names_hash.erase(to_string(statement->name())); @@ -1767,12 +1775,17 @@ void Prepared_statement_map::claim_memory_ownership(bool claim) { void Prepared_statement_map::reset() { if (!st_hash.empty()) { -#ifdef HAVE_PSI_PS_INTERFACE for (auto &key_and_value : st_hash) { Prepared_statement *stmt = key_and_value.second.get(); + if (stmt->lex->query_block->qep_cache_state == + Query_block::QEP_CACHE_READY) { + assert(cached_prepared_stmt_count > 0); + --cached_prepared_stmt_count; + } +#ifdef HAVE_PSI_PS_INTERFACE MYSQL_DESTROY_PS(stmt->get_PS_prepared_stmt()); - } #endif + } mysql_mutex_lock(&LOCK_prepared_stmt_count); assert(prepared_stmt_count >= st_hash.size()); prepared_stmt_count -= st_hash.size(); diff --git a/sql/sql_executor.cc b/sql/sql_executor.cc index 923d9a214..538ee2ad4 100644 --- a/sql/sql_executor.cc +++ b/sql/sql_executor.cc @@ -136,7 +136,6 @@ using std::unique_ptr; using std::vector; static int read_system(TABLE *table); -static int read_const(TABLE *table, TABLE_REF *ref); static bool alloc_group_fields(JOIN *join, ORDER *group); static inline pair FindKeyBufferAndMap( const TABLE_REF *ref); @@ -2807,7 +2806,8 @@ static AccessPath *ConnectJoins( } void JOIN::create_access_paths() { - assert(m_root_access_path == nullptr); + assert(m_root_access_path == nullptr || + query_block->qep_cache_state == Query_block::QEP_CACHE_READY); AccessPath *path = create_root_access_path_for_join(); path = attach_access_paths_for_having_and_limit(path); @@ -3487,7 +3487,7 @@ int ConstIterator::Read() { return err; } -static int read_const(TABLE *table, TABLE_REF *ref) { +int read_const(TABLE *table, TABLE_REF *ref) { int error; DBUG_TRACE; diff --git a/sql/sql_executor.h b/sql/sql_executor.h index 465e23883..be8cf2814 100644 --- a/sql/sql_executor.h +++ b/sql/sql_executor.h @@ -284,6 +284,10 @@ class QEP_TAB : public QEP_shared_owner { // Cleans up. void cleanup(); + void replace_cache_key(); + + bool replace_cache_key(THD *thd); + // Getters and setters Item *condition_optim() const { return m_condition_optim; } @@ -573,6 +577,7 @@ AccessPath *GetAccessPathForDerivedTable( void ConvertItemsToCopy(const mem_root_deque &items, Field **fields, Temp_table_param *param); +int read_const(TABLE *table, TABLE_REF *ref); std::string RefToString(const TABLE_REF &ref, const KEY *key, bool include_nulls); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 146a46047..0dbc2c612 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4752,7 +4752,6 @@ void Query_block::restore_cmd_properties() { tbl->restore_properties(); tbl->table->m_record_buffer = Record_buffer{0, 0, nullptr}; } - assert(join == nullptr); // Restore GROUP BY list if (group_list_ptrs && group_list_ptrs->size() > 0) { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index ce5ecd548..e2e8261b5 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -695,11 +695,13 @@ class Query_expression { Mem_root_array setup_materialization( THD *thd, TABLE *dst_table, bool union_distinct_only); + public: /** Convert the executor structures to a set of access paths, storing the result in m_root_access_path. */ void create_access_paths(THD *thd); + bool create_root_iterator(THD *thd); public: /** @@ -1171,6 +1173,17 @@ class Query_block { /// @returns a map of all tables references in the query block table_map all_tables_map() const { return (1ULL << leaf_table_count) - 1; } + /// State of plan cache. + enum enum_qep_cache_state { + QEP_NO_CACHE, // Initial state. + QEP_CACHE_START, // plan can cache, set up when statement first execute. + QEP_CACHE_INITIALIZED, // Starting plan cache when statement second execute. + QEP_CACHE_READY, // plan already cache. + QEP_CACHE_INVALID, // can't cache. + }; + /// See enum_cache_state + enum enum_qep_cache_state qep_cache_state { QEP_NO_CACHE }; + void remove_derived(THD *thd, TABLE_LIST *tl); bool remove_aggregates(THD *thd, Query_block *select); diff --git a/sql/sql_opt_exec_shared.h b/sql/sql_opt_exec_shared.h index 2018c7c77..c8de2b050 100644 --- a/sql/sql_opt_exec_shared.h +++ b/sql/sql_opt_exec_shared.h @@ -559,6 +559,7 @@ class QEP_shared_owner { bool skip_records_in_range() const { return m_qs->skip_records_in_range(); } void qs_cleanup(); + void qs_cleanup(bool full); protected: QEP_shared *m_qs; // qs stands for Qep_Shared diff --git a/sql/sql_optimizer.cc b/sql/sql_optimizer.cc index 29a3049e0..d5f776186 100644 --- a/sql/sql_optimizer.cc +++ b/sql/sql_optimizer.cc @@ -1128,10 +1128,17 @@ bool JOIN::alloc_qep(uint n) { ASSERT_BEST_REF_IN_JOIN_ORDER(this); - qep_tab = new (thd->mem_root) - QEP_TAB[n + 1]; // The last one holds only the final op_type. + if (query_block->qep_cache_state == Query_block::QEP_CACHE_INITIALIZED) { + query_block->qep_cache_state = Query_block::QEP_CACHE_READY; + ++cached_prepared_stmt_count; + Prepared_stmt_arena_holder ps_arena_holder(thd); + qep_tab = new (thd->mem_root) QEP_TAB[n + 1]; + } else { + qep_tab = new (thd->mem_root) QEP_TAB[n + 1]; + } if (!qep_tab) return true; /* purecov: inspected */ for (uint i = 0; i < n; ++i) qep_tab[i].init(best_ref[i]); + plan_cache::plan_cache(this); return false; } @@ -2773,7 +2780,13 @@ static JOIN_TAB *alloc_jtab_array(THD *thd, uint table_count) { JOIN_TAB *t = new (thd->mem_root) JOIN_TAB[table_count]; if (!t) return nullptr; /* purecov: inspected */ - QEP_shared *qs = new (thd->mem_root) QEP_shared[table_count]; + QEP_shared *qs = nullptr; + if (thd->lex->query_block->qep_cache_state == Query_block::QEP_CACHE_INITIALIZED) { + Prepared_stmt_arena_holder ps_arena_holder(thd); + qs = new (thd->mem_root) QEP_shared[table_count]; + } else { + qs = new (thd->mem_root) QEP_shared[table_count]; + } if (!qs) return nullptr; /* purecov: inspected */ for (uint i = 0; i < table_count; ++i) t[i].set_qs(qs++); @@ -5139,8 +5152,16 @@ bool JOIN::init_planner_arrays() { if (!(positions = new (thd->mem_root) POSITION[table_count])) return true; - if (!(best_positions = new (thd->mem_root) POSITION[table_count + sj_nests])) - return true; + if (query_block->qep_cache_state == Query_block::QEP_CACHE_INITIALIZED) { + Prepared_stmt_arena_holder ps_arena_holder(thd); + if (!(best_positions = new (thd->mem_root) POSITION[table_count + sj_nests])) { + return true; + } + } else { + if (!(best_positions = new (thd->mem_root) POSITION[table_count + sj_nests])) { + return true; + } + } /* Initialize data structures for tables to be joined. @@ -5515,8 +5536,10 @@ bool JOIN::extract_func_dependent_tables() { join_read_const_table(tab, positions + const_tables - 1); if (status > 0) return true; - else if (status == 0) + else if (status == 0) { found_const_table_map |= tl->map(); + plan_cache::check_query_plan_cachable(query_block); + } break; } else found_ref |= refs; // Table is const if all refs are const @@ -5664,6 +5687,7 @@ bool JOIN::estimate_rowcount() { } } if (records != HA_POS_ERROR) { + plan_cache::check_query_plan_cachable(query_block); tab->found_records = records; tab->read_time = tab->quick() ? tab->quick()->cost_est.total_cost() : 0.0; diff --git a/sql/sql_optimizer.h b/sql/sql_optimizer.h index 53a88995b..c6fcec517 100644 --- a/sql/sql_optimizer.h +++ b/sql/sql_optimizer.h @@ -51,6 +51,7 @@ #include "sql/sql_select.h" // Key_use #include "sql/table.h" #include "sql/temp_table_param.h" +#include "sql/sql_plan_cache.h" enum class Subquery_strategy : int; class COND_EQUAL; @@ -96,8 +97,6 @@ class ORDER_with_src { public: ORDER *order; ///< ORDER expression that we are wrapping with this class Explain_sort_clause src; ///< origin of order list - - private: int flags; ///< bitmap of Explain_sort_property public: @@ -930,6 +929,7 @@ class JOIN { bool alloc_indirection_slices(); +public: /** Convert the executor structures to a set of access paths, storing the result in m_root_access_path. @@ -947,6 +947,7 @@ class JOIN { void create_access_paths_for_index_subquery(); +private: /** @{ Helpers for create_access_paths. */ AccessPath *create_root_access_path_for_join(); AccessPath *attach_access_paths_for_having_and_limit(AccessPath *path); @@ -957,6 +958,9 @@ class JOIN { (after you create an iterator from it). */ AccessPath *m_root_access_path = nullptr; + +public: + plan_cache::Plan_cache_context plan_cache_context; }; /** diff --git a/sql/sql_plan_cache.cc b/sql/sql_plan_cache.cc new file mode 100644 index 000000000..1d5458048 --- /dev/null +++ b/sql/sql_plan_cache.cc @@ -0,0 +1,334 @@ +/* Copyright (c) 2025, Huawei and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + 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, version 2.0, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "sql/filesort.h" +#include "sql/sql_plan_cache.h" +#include "sql/sql_class.h" +#include "sql/sql_optimizer.h" + +namespace plan_cache { + +extern "C" { + ulonglong get_table_ref_version_c(TABLE_SHARE *s) { + return s->get_table_ref_version(); + } + + TABLE* qep_table_c(JOIN *join) { + return join->qep_tab[0].table(); + } +} + +bool Plan_cache_context::is_environment_changed(THD *thd) { + bool result; + + __asm__ __volatile__ ( + "ldr x2, [%[this_ptr], %[ctx_opt_sw]]\n" + "ldr x4, [%[thd_ptr], %[var_opt_sw]]\n" + "cmp x2, x4\n" + "b.ne .Lret_true%=\n" + "ldr x2, [%[this_ptr], %[ctx_charset]]\n" + "ldr x4, [%[thd_ptr], %[var_charset]]\n" + "cmp x2, x4\n" + "b.ne .Lret_true%=\n" + "mov %w[ret], #0\n" + "b .Lexit%=\n" + ".Lret_true%=:\n" + "mov %w[ret], #1\n" + ".Lexit%=:\n" + : [ret] "=r" (result) + : [this_ptr] "r" (this), + [thd_ptr] "r" (thd), + [ctx_opt_sw] "i" (offsetof(Plan_cache_context, optimizer_switch)), + [var_opt_sw] "i" (offsetof(THD, variables) + + offsetof(System_variables, optimizer_switch)), + [ctx_charset] "i" (offsetof(Plan_cache_context, character_set_client)), + [var_charset] "i" (offsetof(THD, variables) + + offsetof(System_variables, character_set_client)) + : "x2", "x4", "memory", "cc" + ); + + return result; +} + +void invalidate_cached_plan(Query_block *query_block) { + JOIN *plan = query_block->join; + if (!plan) { + return; + } + + ::destroy(plan); + query_block->join = nullptr; + if (cached_prepared_stmt_count > 0) { + cached_prepared_stmt_count--; + } + + if (query_block->qep_cache_state != Query_block::QEP_CACHE_INVALID) { + query_block->qep_cache_state = Query_block::QEP_NO_CACHE; + } +} + +void check_query_plan_cachable(Query_block *query_block) { + JOIN *join = query_block->join; + THD *thd = join->thd; + uint leaf_table_count = query_block->leaf_table_count; + + // user asked to disable cache plans + if (!opt_plan_cache) { + return; + } + + // Already checked + if (query_block->qep_cache_state != Query_block::QEP_NO_CACHE) { + return; + } + + if (query_block->parent_lex->sql_command != SQLCOM_SELECT || + query_block->type() != enum_explain_type::EXPLAIN_SIMPLE || + query_block->outer_query_block() || + query_block->first_inner_query_expression() || + leaf_table_count != 1 || + query_block->partitioned_table_count != 0 || + query_block->join->select_distinct || + query_block->join->need_tmp_before_win || + !thd->lex->m_sql_cmd || + query_block->olap == ROLLUP_TYPE || + thd->sp_runtime_ctx || + thd->lex->sroutines_list.elements > 0) { + query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return; + } + + uint total_key_parts = 0; + for (uint i = 0; i < leaf_table_count; ++i) { + JOIN_TAB *join_tab = &query_block->join->join_tab[i]; + uint key_parts = join_tab->ref().key_parts; + total_key_parts += key_parts; + + if (thd->stmt_arena->get_state() == Query_arena::STMT_PREPARED || + thd->stmt_arena->get_state() == Query_arena::STMT_EXECUTED) { + if (join_tab->table()->is_nullable()) { + query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return; + } + } else { + return; + } + } + + if (query_block->where_cond()->type() == Item::COND_ITEM && + (((Item_cond *)query_block->where_cond())->argument_list()->size()) != + total_key_parts) { + query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return; + } + + if (WalkItem(query_block->where_cond(), + enum_walk::POSTFIX, + [](Item *sub_item) { + if (sub_item->type() == Item::FUNC_ITEM && + ((Item_func *)sub_item)->functype() == Item_func::GUSERVAR_FUNC) { + return true; + } + return false; + })) { + query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return; + } + + if (is_temporary_table(query_block->leaf_tables) || + query_block->leaf_tables->schema_table || + query_block->leaf_tables->is_system_view || + is_infoschema_db(query_block->leaf_tables->db) || + is_perfschema_db(query_block->leaf_tables->db)) { + query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return; + } + + query_block->qep_cache_state = Query_block::QEP_CACHE_START; +} + +void plan_cache(JOIN *join) { + __asm__ __volatile__ ( + "ldr x20, [%[join_ptr], #%[thd_off]]\n" + "add x21, %[join_ptr], #%[ctx_off]\n" + "stp x29, x30, [sp, #-32]!\n" + "mov x0, %[join_ptr]\n" + "bl qep_table_c\n" + "mov x23, x0\n" + "ldp x29, x30, [sp], #32\n" + "ldr x24, [%[join_ptr], #%[query_block_off]]\n" + "ldrb w25, [x24, #%[qep_state_off]]\n" + "cmp w25, #%[cache_ready]\n" + "b.ne .Lexit%=\n" + "ldr x26, [x20, #%[opt_switch_off]]\n" + "str x26, [%[join_ptr], #%[ctx_opt_sw_off]]\n" + "ldr x26, [x23, #%[table_file_off]]\n" + "ldr x27, [x26, #%[stats_records_off]]\n" + "str x27, [%[join_ptr], #%[ctx_records_off]]\n" + "stp x29, x30, [sp, #-32]!\n" + "ldr x0, [x23, #%[table_s_off]]\n" + "bl get_table_ref_version_c\n" + "str x0, [%[join_ptr], #%[ctx_table_ver_off]]\n" + "ldp x29, x30, [sp], #32\n" + "ldr x27, [x20, #%[charset_off]]\n" + "str x27, [%[join_ptr], #%[ctx_charset_off]]\n" + ".Lexit%=:\n" + : + : [join_ptr] "r" (join), + [thd_off] "i" (offsetof(JOIN, thd)), + [ctx_off] "i" (offsetof(JOIN, plan_cache_context)), + [query_block_off] "i" (offsetof(JOIN, query_block)), + [qep_state_off] "i" (offsetof(Query_block, qep_cache_state)), + [cache_ready] "i" (Query_block::QEP_CACHE_READY), + [opt_switch_off] "i" (offsetof(THD, variables) + + offsetof(System_variables, optimizer_switch)), + [ctx_opt_sw_off] "i" (offsetof(JOIN, plan_cache_context) + + offsetof(Plan_cache_context, optimizer_switch)), + [table_file_off] "i" (offsetof(TABLE, file)), + [stats_records_off] "i" (offsetof(handler, stats) + + offsetof(ha_statistics, records)), + [ctx_records_off] "i" (offsetof(JOIN, plan_cache_context) + + offsetof(Plan_cache_context, table_records)), + [table_s_off] "i" (offsetof(TABLE, s)), + [ctx_table_ver_off] "i" (offsetof(JOIN, plan_cache_context) + + offsetof(Plan_cache_context, table_version)), + [charset_off] "i" (offsetof(THD, variables) + + offsetof(System_variables, character_set_client)), + [ctx_charset_off] "i" (offsetof(JOIN, plan_cache_context) + + offsetof(Plan_cache_context, character_set_client)) + : "x0", "x1", "x20", "x21", "x23", "x24", "x25", "x26", "x27", + "memory", "cc" + ); +} + +static bool apply_cached_plan(THD *thd, LEX *lex) { + Query_expression *unit = lex->unit; + JOIN *join = unit->first_query_block()->join; + TABLE_LIST *leaf_tables = lex->query_block->leaf_tables; + uint leaf_table_count = lex->query_block->leaf_table_count; + + join->grouped = lex->query_block->is_explicitly_grouped(); + join->implicit_grouping = lex->query_block->is_implicitly_grouped(); + join->select_distinct = lex->query_block->is_distinct(); + join->order.order = lex->query_block->order_list.first; + join->order.src = ESC_ORDER_BY; + join->order.flags = lex->query_block->order_list.first ? + ESP_EXISTS : ESP_none; + join->group_list.order = lex->query_block->group_list.first; + join->group_list.src = ESC_GROUP_BY; + join->group_list.flags = lex->query_block->group_list.first ? + ESP_EXISTS : ESP_none; + join->fields = &(lex->query_block->fields); + + for (uint i = 0; i < leaf_table_count; ++i) { + QEP_TAB *qep_tab = &join->qep_tab[i]; + + assert(leaf_tables); + qep_tab->set_table(leaf_tables->table); + leaf_tables = leaf_tables->next_leaf; + + if (join->plan_is_const() && qep_tab->ref().key_parts) { + qep_tab->replace_cache_key(); + int status = read_const(qep_tab->table(), &qep_tab->ref()); + if (status == 0) { + if (unit->first_query_block()->with_sum_func && join->sum_funcs) { + if (prepare_sum_aggregators(join->sum_funcs, false)) { + return true; + } + if (setup_sum_funcs(thd, join->sum_funcs) || thd->is_fatal_error()) { + return true; + } + } + } else if (status == -1) { + join->create_access_paths_for_zero_rows(); + goto unit_set_optimized; + } else if (status > 0) { + return true; + } + } else { + if (unit->first_query_block()->with_sum_func) { + if (join->sum_funcs) { + if (prepare_sum_aggregators(join->sum_funcs, false)) { + return true; + } + if (setup_sum_funcs(thd, join->sum_funcs) || thd->is_fatal_error()) { + return true; + } + } + } + if (qep_tab->replace_cache_key(thd)) { + join->query_block->qep_cache_state = Query_block::QEP_CACHE_INVALID; + return true; + } + if (!join->group_list.empty() || + (!join->order.empty() && !join->m_windowing_steps)) { + bool keep_buffers = + join->query_block->master_query_expression()->item != nullptr && + join->query_block->master_query_expression()->item->is_uncacheable(); + qep_tab->filesort = new (thd->mem_root) + Filesort(thd, {qep_tab->table()}, keep_buffers, join->order.order, + HA_POS_ERROR, false, false, false, false); + } + } + } + join->create_access_paths(); +unit_set_optimized: + unit->set_optimized(); + unit->create_access_paths(thd); + unit->create_root_iterator(thd); + + return false; +} + +bool apply_cached_plan_if_suitable(THD *thd, LEX *lex) { + Query_block *query_block = lex->query_block; + + if (query_block->qep_cache_state != Query_block::QEP_CACHE_READY) { + return false; + } + + if (opt_plan_cache) { + JOIN *join = query_block->join; + assert(join); + Plan_cache_context &context = join->plan_cache_context; + TABLE_LIST *tl = query_block->leaf_tables; + if (tl->table->s->is_secondary_engine()) { + return false; + } + if (tl->fetch_number_of_rows()) { + return false; + } + if (tl->table->s->get_table_ref_version() != context.table_version || + context.is_environment_changed(thd) || + context.is_table_stats_changed_sharply( + tl->table->file->stats.records) || + apply_cached_plan(thd, lex)) { + invalidate_cached_plan(query_block); + return false; + } + return true; + } else { + invalidate_cached_plan(query_block); + } + return false; +} + +} // namespace plan_cache diff --git a/sql/sql_plan_cache.h b/sql/sql_plan_cache.h new file mode 100644 index 000000000..1193e1ca6 --- /dev/null +++ b/sql/sql_plan_cache.h @@ -0,0 +1,59 @@ +#ifndef PLAN_CACHE_INCLUDED +#define PLAN_CACHE_INCLUDED + +/* Copyright (c) 2025, Huawei and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + 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, version 2.0, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "my_base.h" +#include "sql/mysqld.h" + +class JOIN; +struct LEX; +class Query_block; + +namespace plan_cache { + +struct Plan_cache_context { + static constexpr float allow_change_ratio = 0.2f; + + ulonglong optimizer_switch = 0; + const CHARSET_INFO *character_set_client; + ulonglong table_version = 0; + ha_rows table_records = 0; + + bool is_environment_changed(THD *thd); + bool is_table_stats_changed_sharply(ha_rows new_rows) { + if ((std::abs(longlong(new_rows - table_records)) / + (float)(table_records + 1)) > allow_change_ratio) { + return true; + } + return false; + } +}; + +void plan_cache(JOIN *join); +void invalidate_cached_plan(Query_block *query_block); +void check_query_plan_cachable(Query_block *query_block); +bool apply_cached_plan_if_suitable(THD *thd, LEX *lex); + +} // namespace plan_cache + +#endif // PLAN_CACHE_INCLUDED diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc index 1d4f5983d..d3512233b 100644 --- a/sql/sql_prepare.cc +++ b/sql/sql_prepare.cc @@ -163,6 +163,7 @@ When one supplies long data for a placeholder: #include "sql/sql_digest_stream.h" #include "sql/sql_handler.h" // mysql_ha_rm_tables #include "sql/sql_insert.h" // Query_result_create +#include "sql/sql_plan_cache.h" // invalidate_cached_plan #include "sql/sql_lex.h" #include "sql/sql_list.h" #include "sql/sql_parse.h" // sql_command_flags @@ -3153,6 +3154,7 @@ bool Prepared_statement::reprepare() { Prepared_statement copy(thd); swap_prepared_statement(©); + plan_cache::invalidate_cached_plan(copy.lex->query_block); auto copy_guard = create_scope_guard([&]() { swap_prepared_statement(©); }); diff --git a/sql/sql_resolver.cc b/sql/sql_resolver.cc index 0c116ca63..a19ac8cee 100644 --- a/sql/sql_resolver.cc +++ b/sql/sql_resolver.cc @@ -170,6 +170,37 @@ static Item *create_rollup_switcher(THD *thd, Query_block *query_block, bool Query_block::prepare(THD *thd, mem_root_deque *insert_field_list) { DBUG_TRACE; + if (qep_cache_state == Query_block::QEP_CACHE_READY) { + thd->mark_used_columns = MARK_COLUMNS_READ; + thd->want_privilege = SELECT_ACL; + + is_item_list_lookup = false; + + /* Check that all tables, fields, conds and order are ok */ + + if (!(active_options() & OPTION_SETUP_TABLES_DONE)) { + if (setup_tables(thd, get_table_list(), false)) { + return true; + } + } + + is_item_list_lookup = true; + + if (setup_fields(thd, thd->want_privilege, /*allow_sum_func=*/true, + /*split_sum_funcs=*/true, /*column_update=*/false, + insert_field_list, &fields, base_ref_items)) { + return true; + } + + // Set up join conditions and WHERE clause + if (setup_conds(thd)) { + return true; + } + + assert(!thd->is_error()); + return false; + } + assert(this == thd->lex->current_query_block()); assert(join == nullptr); assert(!thd->is_error()); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 0d5929ff9..1501feb62 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -772,16 +772,18 @@ static bool optimize_secondary_engine(THD *thd) { bool Sql_cmd_dml::execute_inner(THD *thd) { Query_expression *unit = lex->unit; - if (unit->optimize(thd, /*materialize_destination=*/nullptr, - /*create_iterators=*/true)) - return true; + if (!plan_cache::apply_cached_plan_if_suitable(thd, lex)) { + if (unit->optimize(thd, /*materialize_destination=*/nullptr, + /*create_iterators=*/true)) + return true; - // Calculate the current statement cost. It will be made available in - // the Last_query_cost status variable. - thd->m_current_query_cost = accumulate_statement_cost(lex); + // Calculate the current statement cost. It will be made available in + // the Last_query_cost status variable. + thd->m_current_query_cost = accumulate_statement_cost(lex); - // Perform secondary engine optimizations, if needed. - if (optimize_secondary_engine(thd)) return true; + // Perform secondary engine optimizations, if needed. + if (optimize_secondary_engine(thd)) return true; + } // We know by now that execution will complete (successful or with error) lex->set_exec_completed(); @@ -1805,7 +1807,16 @@ bool Query_block::optimize(THD *thd) { DBUG_TRACE; assert(join == nullptr); - JOIN *const join_local = new (thd->mem_root) JOIN(thd, this); + JOIN *join_local = nullptr; + + if (qep_cache_state == Query_block::QEP_CACHE_START) { + qep_cache_state = Query_block::QEP_CACHE_INITIALIZED; + Prepared_stmt_arena_holder ps_arena_holder(thd); + join_local = new (thd->mem_root) JOIN(thd, this); + } else { + join_local = new (thd->mem_root) JOIN(thd, this); + } + if (!join_local) return true; /* purecov: inspected */ /* @@ -2109,16 +2120,35 @@ void calc_length_and_keyparts(Key_use *keyuse, JOIN_TAB *tab, const uint key, bool init_ref(THD *thd, unsigned keyparts, unsigned length, unsigned keyno, TABLE_REF *ref) { + Query_block *queryBlock = thd->lex->query_block; ref->key_parts = keyparts; ref->key_length = length; ref->key = keyno; - if (!(ref->key_buff = thd->mem_root->ArrayAlloc(ALIGN_SIZE(length))) || - !(ref->key_buff2 = - thd->mem_root->ArrayAlloc(ALIGN_SIZE(length))) || - !(ref->key_copy = thd->mem_root->ArrayAlloc(keyparts)) || - !(ref->items = thd->mem_root->ArrayAlloc(keyparts)) || - !(ref->cond_guards = thd->mem_root->ArrayAlloc(keyparts))) { - return true; + if (queryBlock->qep_cache_state == Query_block::QEP_CACHE_INITIALIZED) { + Prepared_stmt_arena_holder ps_arena_holder(thd); + if (!(ref->key_buff = + thd->mem_root->ArrayAlloc(ALIGN_SIZE(ref->key_length))) || + !(ref->key_buff2 = + thd->mem_root->ArrayAlloc(ALIGN_SIZE(ref->key_length))) || + !(ref->key_copy = + thd->mem_root->ArrayAlloc(ref->key_parts)) || + !(ref->items = thd->mem_root->ArrayAlloc(ref->key_parts)) || + !(ref->cond_guards = + thd->mem_root->ArrayAlloc(ref->key_parts))) { + return true; + } + } else { + if (!(ref->key_buff = + thd->mem_root->ArrayAlloc(ALIGN_SIZE(ref->key_length))) || + !(ref->key_buff2 = + thd->mem_root->ArrayAlloc(ALIGN_SIZE(ref->key_length))) || + !(ref->key_copy = + thd->mem_root->ArrayAlloc(ref->key_parts)) || + !(ref->items = thd->mem_root->ArrayAlloc(ref->key_parts)) || + !(ref->cond_guards = + thd->mem_root->ArrayAlloc(ref->key_parts))) { + return true; + } } ref->key_err = true; ref->null_rejecting = 0; @@ -3425,6 +3455,44 @@ void QEP_TAB::cleanup() { } } +void QEP_TAB::replace_cache_key() { + TABLE_REF *t_ref = &ref(); + THD *const thd = join()->thd; + Key_use *keyuse = NULL; + KEY *const keyinfo = table()->key_info + t_ref->key; + uchar *key_buff = t_ref->key_buff; + + memset(key_buff, 0, ALIGN_SIZE(t_ref->key_length) * 2); + + for (uint part_no = 0; part_no < t_ref->key_parts; part_no++) { + keyuse = &position()->key[part_no]; + bool maybe_null = keyinfo->key_part[part_no].null_bit; + + store_key *s_key = get_store_key(thd, keyuse->val, keyuse->used_tables, + join()->const_table_map, + &keyinfo->key_part[part_no], key_buff, + maybe_null); + if (unlikely(!s_key || thd->is_fatal_error())) { + assert(0); + } + + bool dummy_value = false; + uchar *arg = pointer_cast(&dummy_value); + keyuse->val->walk(&Item::repoint_const_outer_ref, enum_walk::PREFIX, arg); + + (void)s_key->copy(); + + if (s_key->null_key) { + t_ref->key_copy[part_no] = s_key; // Reevaluate in JOIN::exec() + } + else { + t_ref->key_copy[part_no] = nullptr; + } + + key_buff += keyinfo->key_part[part_no].store_length; + } +} + void QEP_shared_owner::qs_cleanup() { /* Skip non-existing derived tables/views result tables */ if (table() && @@ -3442,6 +3510,32 @@ void QEP_shared_owner::qs_cleanup() { delete quick(); } +void QEP_shared_owner::qs_cleanup(bool full) { + /* Skip non-existing derived tables/views result tables */ + if (table() && + (table()->s->tmp_table != INTERNAL_TMP_TABLE || table()->is_created())) { + table()->set_keyread(false); + table()->file->ha_index_or_rnd_end(); + free_io_cache(table()); + filesort_free_buffers(table(), true); + TABLE_LIST *const table_ref = table()->pos_in_table_list; + if (table_ref) { + table_ref->derived_keys_ready = false; + table_ref->derived_key_list.clear(); + } + } + if (full && + (join()->query_block->qep_cache_state == Query_block::QEP_CACHE_READY)) { + QUICK_RANGE_SELECT *squick = dynamic_cast(quick()); + if (squick) { + squick->ranges.clear(); + } + + return; + } + delete quick(); +} + uint QEP_TAB::sjm_query_block_id() const { assert(sj_is_materialize_strategy(get_sj_strategy())); for (uint i = 0; i < join()->primary_tables; ++i) { @@ -3985,9 +4079,17 @@ bool JOIN::alloc_func_list() { } /* This must use calloc() as rollup_make_fields depends on this */ - sum_funcs = - (Item_sum **)thd->mem_calloc(sizeof(Item_sum **) * (func_count + 1) + - sizeof(Item_sum ***) * (group_parts + 1)); + if (query_block->with_sum_func && + query_block->qep_cache_state == Query_block::QEP_CACHE_INITIALIZED) { + Prepared_stmt_arena_holder ps_arena_holder(thd); + sum_funcs = + (Item_sum **)thd->mem_calloc(sizeof(Item_sum **) * (func_count + 1) + + sizeof(Item_sum ***) * (group_parts + 1)); + } else { + sum_funcs = + (Item_sum **)thd->mem_calloc(sizeof(Item_sum **) * (func_count + 1) + + sizeof(Item_sum ***) * (group_parts + 1)); + } return sum_funcs == nullptr; } diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 76977f95a..481f2f34b 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -72,6 +72,7 @@ #include "sql/opt_explain.h" // explain_no_table #include "sql/opt_explain_format.h" #include "sql/opt_trace.h" +#include "sql/opt_range.h" #include "sql/parse_tree_node_base.h" #include "sql/parse_tree_nodes.h" // PT_with_clause #include "sql/pfs_batch_mode.h" @@ -991,6 +992,14 @@ void Query_expression::create_access_paths(THD *thd) { } } +bool Query_expression::create_root_iterator(THD *thd) +{ + JOIN *join = first_query_block()->join; + m_root_iterator = CreateIteratorFromAccessPath( + thd, m_root_access_path, join, /*eligible_for_batch_mode=*/true); + return false; +} + /** Explain query starting from this unit. @@ -1526,13 +1535,21 @@ static void destroy_materialized(TABLE_LIST *list) { void Query_block::cleanup(THD *thd, bool full) { if (join) { - if (full) { - assert(join->query_block == this); - join->destroy(); - ::destroy(join); - join = nullptr; - } else - join->cleanup(); + if (qep_cache_state == Query_block::QEP_CACHE_READY) { + if (full && leaf_tables->table) { + assert(join->query_block == this); + join->qep_tab->qs_cleanup(true); + } + } else { + if (full) { + assert(join->query_block == this); + join->destroy(); + ::destroy(join); + join = nullptr; + } else { + join->cleanup(); + } + } } if (full) { diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 3b8473bd1..6af8be88d 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -7015,6 +7015,13 @@ static Sys_var_charptr Sys_protocol_compression_algorithms( NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_set_protocol_compression_algorithms), ON_UPDATE(nullptr)); +static Sys_var_bool Sys_plan_cache("plan_cache", + "Sys_plan_cache " + "when first execute prepare stmt " + "the remove product execute plan time ", + GLOBAL_VAR(opt_plan_cache), + CMD_LINE(OPT_ARG), DEFAULT(true)); + static bool check_set_require_row_format(sys_var *, THD *thd, set_var *var) { /* Should own SUPER or SYSTEM_VARIABLES_ADMIN or SESSION_VARIABLES_ADMIN -- Gitee