From 460a8da1f695930dbd46a539891a6dcd4c593bdb Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 1 Oct 2025 17:58:15 +0200 Subject: [PATCH 01/10] parsing of table/index/field attributes: auto-alias all boolean values also allow TRUE/FALSE for booleans --- .../r/innodb-encryption-alter.result | 6 ++-- .../encryption/t/innodb-encryption-alter.test | 4 +-- sql/create_options.cc | 29 +++++++++++++++++-- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/mysql-test/suite/encryption/r/innodb-encryption-alter.result b/mysql-test/suite/encryption/r/innodb-encryption-alter.result index a610b404228d0..f6d95711a6b0e 100644 --- a/mysql-test/suite/encryption/r/innodb-encryption-alter.result +++ b/mysql-test/suite/encryption/r/innodb-encryption-alter.result @@ -1,6 +1,6 @@ SET GLOBAL innodb_encrypt_tables = ON; SET GLOBAL innodb_encryption_threads = 4; -CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED=NO ENCRYPTION_KEY_ID=4; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED='False' ENCRYPTION_KEY_ID=4; Warnings: Warning 140 InnoDB: ENCRYPTED=NO implies ENCRYPTION_KEY_ID=1 DROP TABLE t1; @@ -27,14 +27,14 @@ Warning 140 InnoDB: ENCRYPTION_KEY_ID 99 not available Error 1005 Can't create table `test`.`t1` (errno: 140 "Wrong create options") Warning 1030 Got error 140 "Wrong create options" from storage engine InnoDB set innodb_default_encryption_key_id = 4; -CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED=YES; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED='on'; SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( `pk` int(11) NOT NULL AUTO_INCREMENT, `c` varchar(256) DEFAULT NULL, PRIMARY KEY (`pk`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `ENCRYPTED`=YES `ENCRYPTION_KEY_ID`=4 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `ENCRYPTED`='on' `ENCRYPTION_KEY_ID`=4 DROP TABLE t1; CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB; SHOW CREATE TABLE t1; diff --git a/mysql-test/suite/encryption/t/innodb-encryption-alter.test b/mysql-test/suite/encryption/t/innodb-encryption-alter.test index 6e5d449b10a23..b7909846015d4 100644 --- a/mysql-test/suite/encryption/t/innodb-encryption-alter.test +++ b/mysql-test/suite/encryption/t/innodb-encryption-alter.test @@ -10,7 +10,7 @@ SET GLOBAL innodb_encrypt_tables = ON; SET GLOBAL innodb_encryption_threads = 4; -CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED=NO ENCRYPTION_KEY_ID=4; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED='False' ENCRYPTION_KEY_ID=4; DROP TABLE t1; set @save_global = @@GLOBAL.innodb_default_encryption_key_id; set innodb_default_encryption_key_id = 99; @@ -23,7 +23,7 @@ SHOW WARNINGS; CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED=YES; SHOW WARNINGS; set innodb_default_encryption_key_id = 4; -CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED=YES; +CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB ENCRYPTED='on'; SHOW CREATE TABLE t1; DROP TABLE t1; CREATE TABLE t1 (pk INT PRIMARY KEY AUTO_INCREMENT, c VARCHAR(256)) ENGINE=INNODB; diff --git a/sql/create_options.cc b/sql/create_options.cc index 91211fbdda348..b08de5185ebd4 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -27,7 +27,7 @@ #define FRM_QUOTED_VALUE 0x8000U -static const char *bools="NO,OFF,0,YES,ON,1"; +static const char *bools="NO,OFF,FALSE,0,YES,ON,TRUE,1"; /** Links this item to the given list end @@ -182,6 +182,31 @@ static bool set_one_value(ha_create_table_option *opt, THD *thd, DBUG_RETURN(0); } + /* check boolean aliases. */ + uint bool_val= value->find_in_list(bools); + if (bool_val != UINT_MAX) + { + bool_val= bool_val > 3; + + static const LEX_CSTRING vals[2]= { + { STRING_WITH_LEN("NO") }, + { STRING_WITH_LEN("YES") }, + }; + const LEX_CSTRING &str_val= vals[bool_val]; + const char *str= opt->values; + size_t len= 0; + for (int num= 0; str[len]; num++) + { + for (len= 0; str[len] && str[len] != ','; len++) /* no-op */; + if (str_val.length == len && !strncasecmp(str_val.str, str, len)) + { + *val= num; + DBUG_RETURN(0); + } + str+= len+1; + } + } + DBUG_RETURN(report_wrong_value(thd, opt->name, value->str, suppress_warning)); } @@ -196,7 +221,7 @@ static bool set_one_value(ha_create_table_option *opt, THD *thd, uint num= value->find_in_list(bools); if (num != UINT_MAX) { - *val= num > 2; + *val= num > 3; DBUG_RETURN(0); } From f5608958347333846920cba7b5a28b275bf08703 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 16 Jan 2026 10:11:40 +0000 Subject: [PATCH 02/10] fixup: fix options parsing avoid reading after the end of the current string. --- sql/create_options.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sql/create_options.cc b/sql/create_options.cc index b08de5185ebd4..ebfecb58a193f 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -203,7 +203,10 @@ static bool set_one_value(ha_create_table_option *opt, THD *thd, *val= num; DBUG_RETURN(0); } - str+= len+1; + str+= len; + if (*str == ',') + ++str; + len= 0; } } From 8965748e0f1e64a313366bea0dd2af34e2b06f44 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 6 Oct 2025 18:33:50 +0200 Subject: [PATCH 03/10] MDEV-37815 field and index engine attributes in partitioning are broken just like table attributes, field and index attributes must be parsed using the underlying engine, not ha_partition. --- sql/sql_table.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 13ed810cac2dd..c7e140af71a89 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3398,10 +3398,10 @@ mysql_prepare_create_table_finalize(THD *thd, HA_CREATE_INFO *create_info, auto_increment++; extend_option_list(thd, create_info->db_type, !sql_field->field, &sql_field->option_list, - create_info->db_type->field_options); + file->partition_ht()->field_options); if (parse_option_list(thd, &sql_field->option_struct, &sql_field->option_list, - create_info->db_type->field_options, FALSE, + file->partition_ht()->field_options, FALSE, thd->mem_root)) DBUG_RETURN(TRUE); /* @@ -3622,7 +3622,7 @@ mysql_prepare_create_table_finalize(THD *thd, HA_CREATE_INFO *create_info, Create_field *auto_increment_key= 0; Key_part_spec *column; st_plugin_int *index_plugin= hton2plugin[create_info->db_type->slot]; - ha_create_table_option *index_options= create_info->db_type->index_options; + ha_create_table_option *index_options= file->partition_ht()->index_options; if (key->type == Key::IGNORE_KEY) { From bcab0c66faea9e0087aa7c4f57af10110e61ccc3 Mon Sep 17 00:00:00 2001 From: Monty Date: Wed, 25 Jun 2025 17:10:03 +0300 Subject: [PATCH 04/10] MDEV-37070 Implement table options to enable/disable features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added ADAPTIVE_HASH_INDEX=YES|NO table and index option to InnoDB. The table and index options only have an effect if InnoDB adaptive hash index feature is enabled. - Having the ADAPTIVE_HASH_INDEX TABLE option set to NO will disable adaptive hash index for all indexes in the table that does not have the index option adaptive_hash_index=yes. - Having the ADAPTIVE_HASH_INDEX TABLE option set to YES will enable the adaptive hash index for all indexes in the table that does not have the index option adaptive_hash_index=no. - Using adaptive_hash_index=default deletes the old setting. - One can also use OFF/ON as the options. This is to make it work similar as other existing options. - innodb.adaptive_hash_index has been changed from a bool to an enum with values OFF, ON and IF_SPECIFIED. If IF_SPECIFIED is used, adaptive hash index are only used for tables and indexes that specifies adaptive_hash_index=on. - The following new options can be used for further optimize adaptive hash index for an index: - complete_fields (default 0): - 0 to the number of columns the key is defined on - bytes_from_incomplete_fields (default 0): - This is only usable for memcmp() comparable index fields, such as VARBINARY or INT. For example, a 3-byte prefix on an INT will return an identical hash value for 0‥255, another one for 256‥511, and so on. - for_equal_hash_point_to_last_record (default 0) - Default is the first record, known as left_side in the code. Example: we have an INT column with the values 1,4,10 and bytes=3, will that hash value point to the record 1 or the record 10? Note: all values will necessarily have the same hash value computed on the big endian byte prefix 0x800000, for all of the values 0x80000001, 0x80000004, 0x8000000a. InnoDB inverts the sign bit in order to have memcmp() compatible comparison Example: CREATE TABLE t1 (a int primary key, b varchar(100), c int, index (b) adaptive_hash_index=no, index (c)) engine=innodb, adaptive_hash_index=yes; Notable changes in InnoDB - btr_search.enabled was changed from a bool to a ulong to be able to handle options OFF, ON as IF_ENABLED. ulong is needed to compile with MariaDB enum variables. - To be able to find all instances where btr_search.enabled was used I changed all code to use btr_search.get_enabled() when accessing the value and used btr_search.is_enabled(index) to test if AHI is enabled for the index. - btr_search.enabled() was changed to always take two parameters, resize and value of enabled. This was needed as enabled can now have values 0, 1, and 2. Visible user changes: - select @@global.adaptive_hash_index will now return a string instead of 0 or 1. Other things (for Marko) - Check in buf0buff.cc buf_pool_t::resize(). The first call to btr_search.enable will enver happen as ahi_disabled is always 0 here. --- .../innodb/r/innodb_buffer_pool_resize.result | 4 +- ...innodb_buffer_pool_resize_temporary.result | 2 +- .../r/innodb_adaptive_hash_index_basic.result | 183 +++++++++++++++++- .../suite/sys_vars/r/sysvars_innodb.result | 6 +- .../t/innodb_adaptive_hash_index_basic.test | 81 +++++++- sql/handler.cc | 3 + sql/handler.h | 15 ++ storage/innobase/btr/btr0cur.cc | 4 +- storage/innobase/btr/btr0sea.cc | 83 ++++---- storage/innobase/buf/buf0buf.cc | 16 +- storage/innobase/handler/ha_innodb.cc | 88 ++++++++- storage/innobase/handler/ha_innodb.h | 11 +- storage/innobase/ibuf/ibuf0ibuf.cc | 4 +- storage/innobase/include/btr0sea.h | 40 +++- storage/innobase/include/dict0mem.h | 17 ++ storage/innobase/row/row0sel.cc | 2 +- storage/innobase/srv/srv0srv.cc | 2 +- 17 files changed, 471 insertions(+), 90 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize.result b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize.result index 7b254daf4925d..46f5549c3e09b 100644 --- a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize.result +++ b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize.result @@ -20,12 +20,12 @@ SET STATEMENT foreign_key_checks=0, unique_checks=0 FOR INSERT INTO t2 SELECT seq*4,seq*4 FROM seq_1_to_16384; SELECT @@GLOBAL.innodb_adaptive_hash_index; @@GLOBAL.innodb_adaptive_hash_index -1 +ON SET STATEMENT max_statement_time=1e-9 FOR SET GLOBAL innodb_buffer_pool_size = 7340032; SELECT @@GLOBAL.innodb_adaptive_hash_index; @@GLOBAL.innodb_adaptive_hash_index -1 +ON FOUND 1 /innodb_buffer_pool_size=7m.*resized from|innodb_buffer_pool_size change aborted/ in mysqld.1.err set global innodb_buffer_pool_size = 7340032; select count(val) from t1; diff --git a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_temporary.result b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_temporary.result index 12fb02a40c757..7af3268b970c7 100644 --- a/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_temporary.result +++ b/mysql-test/suite/innodb/r/innodb_buffer_pool_resize_temporary.result @@ -10,7 +10,7 @@ SET GLOBAL innodb_buffer_pool_size=8388608; ERROR HY000: innodb_buffer_pool_size change aborted SELECT @@GLOBAL.innodb_adaptive_hash_index,@@GLOBAL.innodb_buffer_pool_size; @@GLOBAL.innodb_adaptive_hash_index @@GLOBAL.innodb_buffer_pool_size -1 16777216 +ON 16777216 SET GLOBAL innodb_adaptive_hash_index = @old_innodb_adaptive_hash_index; CREATE TEMPORARY TABLE t1 (a INT PRIMARY KEY) ENGINE=InnoDB; INSERT INTO t1 SELECT seq FROM seq_1_to_200; diff --git a/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result b/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result index 1471ae3624493..46c8e524b0622 100644 --- a/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result @@ -1,7 +1,7 @@ SET @start_global_value = @@global.innodb_adaptive_hash_index; -Valid values are 'ON' and 'OFF' -select @@global.innodb_adaptive_hash_index in (0, 1); -@@global.innodb_adaptive_hash_index in (0, 1) +Valid values are 'ON', 'OFF' and 'IF_SPECIFIED' +select @@global.innodb_adaptive_hash_index in ("ON", "OFF", "IF_SPECIFIED"); +@@global.innodb_adaptive_hash_index in ("ON", "OFF", "IF_SPECIFIED") 1 select @@session.innodb_adaptive_hash_index; ERROR HY000: Variable 'innodb_adaptive_hash_index' is a GLOBAL variable @@ -20,7 +20,7 @@ INNODB_ADAPTIVE_HASH_INDEX set global innodb_adaptive_hash_index='OFF'; select @@global.innodb_adaptive_hash_index; @@global.innodb_adaptive_hash_index -0 +OFF select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX OFF @@ -30,7 +30,7 @@ INNODB_ADAPTIVE_HASH_INDEX OFF set @@global.innodb_adaptive_hash_index=1; select @@global.innodb_adaptive_hash_index; @@global.innodb_adaptive_hash_index -1 +ON select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX ON @@ -40,17 +40,27 @@ INNODB_ADAPTIVE_HASH_INDEX ON set global innodb_adaptive_hash_index=0; select @@global.innodb_adaptive_hash_index; @@global.innodb_adaptive_hash_index -0 +OFF select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX OFF select * from information_schema.session_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX OFF +set @@global.innodb_adaptive_hash_index='IF_SPECIFIED'; +select @@global.innodb_adaptive_hash_index; +@@global.innodb_adaptive_hash_index +IF_SPECIFIED +select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; +VARIABLE_NAME VARIABLE_VALUE +INNODB_ADAPTIVE_HASH_INDEX IF_SPECIFIED +select * from information_schema.session_variables where variable_name='innodb_adaptive_hash_index'; +VARIABLE_NAME VARIABLE_VALUE +INNODB_ADAPTIVE_HASH_INDEX IF_SPECIFIED set @@global.innodb_adaptive_hash_index='ON'; select @@global.innodb_adaptive_hash_index; @@global.innodb_adaptive_hash_index -1 +ON select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX ON @@ -65,13 +75,11 @@ set global innodb_adaptive_hash_index=1.1; ERROR 42000: Incorrect argument type to variable 'innodb_adaptive_hash_index' set global innodb_adaptive_hash_index=1e1; ERROR 42000: Incorrect argument type to variable 'innodb_adaptive_hash_index' -set global innodb_adaptive_hash_index=2; -ERROR 42000: Variable 'innodb_adaptive_hash_index' can't be set to the value of '2' set global innodb_adaptive_hash_index=-3; ERROR 42000: Variable 'innodb_adaptive_hash_index' can't be set to the value of '-3' select @@global.innodb_adaptive_hash_index; @@global.innodb_adaptive_hash_index -1 +ON select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX ON @@ -80,4 +88,159 @@ VARIABLE_NAME VARIABLE_VALUE INNODB_ADAPTIVE_HASH_INDEX ON set global innodb_adaptive_hash_index='AUTO'; ERROR 42000: Variable 'innodb_adaptive_hash_index' can't be set to the value of 'AUTO' +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=yes; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=yes +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=no; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=no +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index='off'; +ALTER TABLE t1 adaptive_hash_index=default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=yes +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=no) engine=innodb, adaptive_hash_index=yes; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index='off') engine=innodb, adaptive_hash_index='on'; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`='off' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`='on' +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=0) engine=innodb, adaptive_hash_index=1; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=0 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=1 +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb, adaptive_hash_index=no; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=yes +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=no +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb, adaptive_hash_index=yes; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=yes +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=yes +alter table t1 add column c int, add index (c); +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + `c` int(11) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=yes, + KEY `c` (`c`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`=yes +alter table t1 drop index b, add index b (b); +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=default) engine=innodb, adaptive_hash_index=default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index='on') engine=innodb, adaptive_hash_index='off'; +drop table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_fields=1 for_equal_hash_point_to_last_record=1) engine=innodb; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + `c` int(11) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `c` (`c`,`b`) `adaptive_hash_index`=yes `complete_fields`=1 `bytes_from_incomplete_fields`=1 `for_equal_hash_point_to_last_record`=1 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_fields=default; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + `c` int(11) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `c` (`c`,`b`) `complete_fields`=2 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +drop table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index(b)) engine=innodb, adaptive_hash_index='on' ENGINE=innodb PARTITION BY KEY (a) PARTITIONS 2; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`='on' + PARTITION BY KEY (`a`) +PARTITIONS 2 +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index(b) adaptive_hash_index=yes) engine=innodb adaptive_hash_index='off' ENGINE=innodb PARTITION BY KEY (a) PARTITIONS 2; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `b` (`b`) `adaptive_hash_index`=yes +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci `adaptive_hash_index`='off' + PARTITION BY KEY (`a`) +PARTITIONS 2 +drop table t1; +# +# Error handling +# +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=on) engine=innodb, adaptive_hash_index=off; +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'on) engine=innodb, adaptive_hash_index=off' at line 1 SET @@global.innodb_adaptive_hash_index = @start_global_value; diff --git a/mysql-test/suite/sys_vars/r/sysvars_innodb.result b/mysql-test/suite/sys_vars/r/sysvars_innodb.result index 36bd5ca6894ec..fc3d5c8ff15df 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_innodb.result +++ b/mysql-test/suite/sys_vars/r/sysvars_innodb.result @@ -36,12 +36,12 @@ VARIABLE_NAME INNODB_ADAPTIVE_HASH_INDEX SESSION_VALUE NULL DEFAULT_VALUE OFF VARIABLE_SCOPE GLOBAL -VARIABLE_TYPE BOOLEAN -VARIABLE_COMMENT Enable InnoDB adaptive hash index (disabled by default) +VARIABLE_TYPE ENUM +VARIABLE_COMMENT Enable InnoDB adaptive hash index. Values OFF (default), ON or IF_SPECIFIED (enabled only tables or indexes that have adaptive_hash_index=on) NUMERIC_MIN_VALUE NULL NUMERIC_MAX_VALUE NULL NUMERIC_BLOCK_SIZE NULL -ENUM_VALUE_LIST OFF,ON +ENUM_VALUE_LIST OFF,ON,IF_SPECIFIED READ_ONLY NO COMMAND_LINE_ARGUMENT OPTIONAL VARIABLE_NAME INNODB_ADAPTIVE_HASH_INDEX_CELLS diff --git a/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test b/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test index 88e239574d2c0..a3cbddb343747 100644 --- a/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test +++ b/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test @@ -1,17 +1,13 @@ - - -# 2010-01-25 - Added -# - --source include/have_innodb.inc +--source include/have_partition.inc SET @start_global_value = @@global.innodb_adaptive_hash_index; # # exists as global only # ---echo Valid values are 'ON' and 'OFF' -select @@global.innodb_adaptive_hash_index in (0, 1); +--echo Valid values are 'ON', 'OFF' and 'IF_SPECIFIED' +select @@global.innodb_adaptive_hash_index in ("ON", "OFF", "IF_SPECIFIED"); --error ER_INCORRECT_GLOBAL_LOCAL_VAR select @@session.innodb_adaptive_hash_index; --replace_column 2 # @@ -36,6 +32,10 @@ set global innodb_adaptive_hash_index=0; select @@global.innodb_adaptive_hash_index; select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; select * from information_schema.session_variables where variable_name='innodb_adaptive_hash_index'; +set @@global.innodb_adaptive_hash_index='IF_SPECIFIED'; +select @@global.innodb_adaptive_hash_index; +select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; +select * from information_schema.session_variables where variable_name='innodb_adaptive_hash_index'; set @@global.innodb_adaptive_hash_index='ON'; select @@global.innodb_adaptive_hash_index; select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; @@ -53,8 +53,6 @@ set global innodb_adaptive_hash_index=1.1; --error ER_WRONG_TYPE_FOR_VAR set global innodb_adaptive_hash_index=1e1; --error ER_WRONG_VALUE_FOR_VAR -set global innodb_adaptive_hash_index=2; ---error ER_WRONG_VALUE_FOR_VAR set global innodb_adaptive_hash_index=-3; select @@global.innodb_adaptive_hash_index; select * from information_schema.global_variables where variable_name='innodb_adaptive_hash_index'; @@ -62,6 +60,71 @@ select * from information_schema.session_variables where variable_name='innodb_a --error ER_WRONG_VALUE_FOR_VAR set global innodb_adaptive_hash_index='AUTO'; +# +# Test table level adaptive_hash_index options +# + +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=yes; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=no; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index='off'; +ALTER TABLE t1 adaptive_hash_index=default; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b)) engine=innodb adaptive_hash_index=default; +show create table t1; + +# +# Test index level adaptive_hash_index options +# + +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=no) engine=innodb, adaptive_hash_index=yes; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index='off') engine=innodb, adaptive_hash_index='on'; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=0) engine=innodb, adaptive_hash_index=1; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb, adaptive_hash_index=no; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=yes) engine=innodb, adaptive_hash_index=yes; +show create table t1; +alter table t1 add column c int, add index (c); +show create table t1; +alter table t1 drop index b, add index b (b); +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=default) engine=innodb, adaptive_hash_index=default; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index='on') engine=innodb, adaptive_hash_index='off'; + +drop table t1; + +# +# Test extra index options +# + +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_fields=1 for_equal_hash_point_to_last_record=1) engine=innodb; +show create table t1; +alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_fields=default; +show create table t1; +drop table t1; + +# +# Test partitioned tables +# + +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index(b)) engine=innodb, adaptive_hash_index='on' ENGINE=innodb PARTITION BY KEY (a) PARTITIONS 2; +show create table t1; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index(b) adaptive_hash_index=yes) engine=innodb adaptive_hash_index='off' ENGINE=innodb PARTITION BY KEY (a) PARTITIONS 2; +show create table t1; +drop table t1; + +--echo # +--echo # Error handling +--echo # + +--error 1064 +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index=on) engine=innodb, adaptive_hash_index=off; + # # Cleanup # diff --git a/sql/handler.cc b/sql/handler.cc index 42e4a4485319d..72f5fb4e27331 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -68,6 +68,9 @@ #include "wsrep_var.h" /* wsrep_hton_check() */ #endif /* WITH_WSREP */ +/* Note that DEFAULT is handled automatically */ +const char *table_hint_options= "YES,NO"; + /** @def MYSQL_TABLE_LOCK_WAIT Instrumentation helper for table io_waits. diff --git a/sql/handler.h b/sql/handler.h index 5c26203dd03d4..94683b71c89a6 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -6152,4 +6152,19 @@ class handler_binlog_xid_info { }; +/* + This is used when creating table options that affects optimizations and + features, like QUERY_CACHE=OFF. + The user can use YES and NO as synonyms for ON/OFF (needed as some options + are already using ON/OFF and others using YES/NO and we don't want to confuse + the user. + DEFAULT is automatically handled by sys_vars. +*/ + +enum table_hint_options +{ + TABLE_HINT_DEFAULT, TABLE_HINT_YES, TABLE_HINT_NO +}; + +extern const char *table_hint_options; #endif /* HANDLER_INCLUDED */ diff --git a/storage/innobase/btr/btr0cur.cc b/storage/innobase/btr/btr0cur.cc index b57f5ec5d2e69..83ccbd1bc2fd3 100644 --- a/storage/innobase/btr/btr0cur.cc +++ b/storage/innobase/btr/btr0cur.cc @@ -1137,7 +1137,7 @@ dberr_t btr_cur_t::search_leaf(const dtuple_t *tuple, page_cur_mode_t mode, ut_ad(mode == PAGE_CUR_L || mode == PAGE_CUR_G); /* We do a dirty read of btr_search.enabled below, and btr_search_guess_on_hash() will have to check it again. */ - else if (!btr_search.enabled); + else if (!btr_search.is_enabled(index())) ; else if (btr_search_guess_on_hash(index(), tuple, mode != PAGE_CUR_LE, latch_mode, this, mtr)) { @@ -1661,7 +1661,7 @@ dberr_t btr_cur_t::pessimistic_search_leaf(const dtuple_t *tuple, /* We do a dirty read of btr_search.enabled here. We will recheck in btr_search_build_page_hash_index() before building a page hash index, while holding search latch. */ - if (!btr_search.enabled); + if (!btr_search.is_enabled(index())) ; else if (tuple->info_bits & REC_INFO_MIN_REC_FLAG) /* This may be a search tuple for btr_pcur_t::restore_position(). */ ut_ad(tuple->is_metadata() || diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc index 05e3d67ae2c7f..545fbd97d134f 100644 --- a/storage/innobase/btr/btr0sea.cc +++ b/storage/innobase/btr/btr0sea.cc @@ -343,7 +343,7 @@ ATTRIBUTE_COLD ATTRIBUTE_NOINLINE void btr_sea::partition::rollback_insert() noexcept { ut_ad(latch.have_any()); - ut_ad(!btr_search.enabled); + ut_ad(!btr_search.get_enabled()); if (buf_block_t *block= spare.exchange(nullptr)) { MEM_MAKE_ADDRESSABLE(block->page.frame, srv_page_size); @@ -386,18 +386,18 @@ ATTRIBUTE_COLD void btr_search_lazy_free(dict_index_t *index) noexcept } } -ATTRIBUTE_COLD bool btr_sea::disable_and_lock() noexcept +ATTRIBUTE_COLD ulong btr_sea::disable_and_lock() noexcept { dict_sys.freeze(SRW_LOCK_CALL); for (ulong i= 0; i < n_parts; i++) parts[i].latch.wr_lock(SRW_LOCK_CALL); - const bool was_enabled{enabled}; + const ulong was_enabled{enabled}; if (was_enabled) { - enabled= false; + enabled= 0; btr_search_disable(dict_sys.table_LRU); btr_search_disable(dict_sys.table_non_LRU); dict_sys.unfreeze(); @@ -418,16 +418,18 @@ ATTRIBUTE_COLD void btr_sea::unlock() noexcept parts[i].latch.wr_unlock(); } -ATTRIBUTE_COLD bool btr_sea::disable() noexcept +ATTRIBUTE_COLD ulong btr_sea::disable() noexcept { - const bool was_enabled{disable_and_lock()}; + const ulong was_enabled{disable_and_lock()}; unlock(); return was_enabled; } /** Enable the adaptive hash search system. -@param resize whether buf_pool_t::resize() is the caller */ -ATTRIBUTE_COLD void btr_sea::enable(bool resize) noexcept +@param resize whether buf_pool_t::resize() is the caller +@param resize Type of adaptive_hash_index. + 1 (enable for all tables) or 2 (if_specified */ +ATTRIBUTE_COLD void btr_sea::enable(bool resize, ulong enable_opt) noexcept { if (!resize) { @@ -442,10 +444,15 @@ ATTRIBUTE_COLD void btr_sea::enable(bool resize) noexcept parts[i].latch.wr_lock(SRW_LOCK_CALL); if (!parts[0].table.array) - enabled= alloc(n_cells); + { + if (alloc(n_cells)) + enabled= enable_opt; + } else + { + enabled= enable_opt; ut_ad(enabled); - + } unlock(); } @@ -480,7 +487,7 @@ void btr_sea::partition::insert(uint32_t fold, const rec_t *rec) noexcept #if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG ut_a(block->page.frame == page_align(rec)); #endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); hash_chain &cell{table.cell_get(fold)}; page_hash_latch &hash_lock{table.lock_get(cell)}; @@ -593,7 +600,8 @@ static void btr_search_update_hash_ref(const btr_cur_t &cursor, if (ut_d(const dict_index_t *block_index=) block->index) { ut_ad(block_index == index); - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); + ut_ad(index->search_info.ahi_enabled); uint32_t bytes_fields{block->ahi_left_bytes_fields}; if (bytes_fields != left_bytes_fields) goto skip; @@ -632,7 +640,7 @@ static void btr_search_update_hash_ref(const btr_cur_t &cursor, # if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG ut_a(!block->n_pointers); # endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ - if (!btr_search.enabled) + if (!btr_search.get_enabled()) { ut_ad(!index->any_ahi_pages()); part.rollback_insert(); @@ -841,7 +849,7 @@ buf_block_t * btr_sea::partition::cleanup_after_erase(ahi_node *erase) noexcept { ut_ad(latch.have_wr()); - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); const ahi_node *const top= cleanup_after_erase_start(); @@ -864,7 +872,7 @@ btr_sea::partition::cleanup_after_erase(ahi_node *erase, page_hash_latch *l) { ut_ad(latch.have_rd()); ut_ad(l->is_write_locked()); - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); const ahi_node *const top= cleanup_after_erase_start(); @@ -922,7 +930,7 @@ btr_sea::partition::erase_status btr_sea::partition::erase(hash_chain &cell, const rec_t *rec) noexcept { ut_ad(ex ? latch.have_wr() : latch.have_rd()); - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); page_hash_latch *const hash_lock{ex ? nullptr : &table.lock_get(cell)}; buf_block_t *block= nullptr; @@ -1000,7 +1008,7 @@ static bool ha_search_and_update_if_found(btr_sea::hash_table *table, #if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG ut_a(new_block->page.frame == page_align(new_data)); #endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); btr_sea::hash_chain &cell{table->cell_get(fold)}; page_hash_latch &hash_lock{table->lock_get(cell)}; @@ -1115,7 +1123,7 @@ btr_search_guess_on_hash( !index->search_info.n_hash_potential) { ahi_unusable: - if (!index->table->is_temporary() && btr_search.enabled) + if (!index->table->is_temporary() && btr_search.get_enabled()) cursor->flag= BTR_CUR_HASH_ABORT; return false; } @@ -1142,7 +1150,7 @@ btr_search_guess_on_hash( page_hash_latch *hash_lock= nullptr; part.latch.rd_lock(SRW_LOCK_CALL); - if (!btr_search.enabled) + if (!btr_search.get_enabled()) { ut_ad(!index->any_ahi_pages()); ahi_release_and_fail: @@ -1312,7 +1320,7 @@ static void btr_search_drop_page_hash_index(buf_block_t *block, return; } - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); bool holding_x= index->freed(); @@ -1526,7 +1534,7 @@ static void btr_search_build_page_hash_index(dict_index_t *index, { ut_ad(!index->table->is_temporary()); - if (!btr_search.enabled) + if (!btr_search.is_enabled(index)) return; ut_ad(block->page.id().space() == index->table->space_id); @@ -1541,7 +1549,7 @@ static void btr_search_build_page_hash_index(dict_index_t *index, const bool rebuild= block_index && (block_index != index || block->ahi_left_bytes_fields != left_bytes_fields); - const bool enabled= btr_search.enabled; + const ulong enabled= btr_search.get_enabled(); ut_ad(enabled || !index->any_ahi_pages()); part.latch.rd_unlock(); @@ -1638,7 +1646,7 @@ static void btr_search_build_page_hash_index(dict_index_t *index, { ut_ad(!block->n_pointers); - if (!btr_search.enabled) + if (!btr_search.get_enabled()) { ut_ad(!index->any_ahi_pages()); part.rollback_insert(); @@ -1663,7 +1671,7 @@ static void btr_search_build_page_hash_index(dict_index_t *index, else { ut_ad(!block->n_pointers); - if (!btr_search.enabled) + if (!btr_search.get_enabled()) { ut_ad(!index->any_ahi_pages()); part.rollback_insert(); @@ -1730,10 +1738,12 @@ void btr_search_move_or_delete_hash_entries(buf_block_t *new_block, ut_ad(block->page.lock.have_x()); ut_ad(new_block->page.lock.have_x()); - if (!btr_search.enabled) + dict_index_t *index= block->index; + + if (!btr_search.may_be_enabled(index)) return; - dict_index_t *index= block->index, *new_block_index= new_block->index; + dict_index_t *new_block_index= new_block->index; assert_block_ahi_valid(block); assert_block_ahi_valid(new_block); @@ -1742,7 +1752,8 @@ void btr_search_move_or_delete_hash_entries(buf_block_t *new_block, { ut_ad(!index || index == new_block_index); drop_exit: - btr_search_drop_page_hash_index(block, nullptr); + if (btr_search.is_enabled(new_block_index)) + btr_search_drop_page_hash_index(block, nullptr); return; } @@ -1775,7 +1786,7 @@ void btr_search_move_or_delete_hash_entries(buf_block_t *new_block, void btr_search_update_hash_on_delete(btr_cur_t *cursor) noexcept { ut_ad(page_is_leaf(btr_cur_get_page(cursor))); - if (!btr_search.enabled) + if (!btr_search.get_enabled()) return; buf_block_t *block= btr_cur_get_block(cursor); @@ -1783,7 +1794,7 @@ void btr_search_update_hash_on_delete(btr_cur_t *cursor) noexcept assert_block_ahi_valid(block); dict_index_t *index= block->index; - if (!index) + if (!index || !btr_search.is_enabled(index)) return; ut_ad(!cursor->index()->table->is_temporary()); @@ -1807,7 +1818,7 @@ void btr_search_update_hash_on_delete(btr_cur_t *cursor) noexcept if (ut_d(dict_index_t *block_index=) block->index) { - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); ut_ad(block_index == index); btr_sea::partition::erase_status s= @@ -1840,7 +1851,7 @@ void btr_search_update_hash_on_delete(btr_cur_t *cursor) noexcept } else { - ut_ad(btr_search.enabled || !index->any_ahi_pages()); + ut_ad(btr_search.get_enabled() || !index->any_ahi_pages()); part.latch.rd_unlock(); } } @@ -1887,7 +1898,7 @@ void btr_search_update_hash_on_insert(btr_cur_t *cursor, bool reorg) noexcept part.latch.rd_lock(SRW_LOCK_CALL); if (!block->index) goto unlock_exit; - ut_ad(btr_search.enabled); + ut_ad(btr_search.get_enabled()); locked= true; if (page_is_comp(page)) { @@ -1956,7 +1967,7 @@ void btr_search_update_hash_on_insert(btr_cur_t *cursor, bool reorg) noexcept if (!block->index) { rollback: - if (!btr_search.enabled) + if (!btr_search.get_enabled()) { ut_ad(!index->any_ahi_pages()); part.rollback_insert(); @@ -2080,7 +2091,7 @@ static bool btr_search_hash_table_validate(THD *thd, ulint hash_table_id) ulint cell_count; btr_search_x_lock_all(); - if (!btr_search.enabled || (thd && thd_kill_level(thd))) { + if (!btr_search.get_enabled() || (thd && thd_kill_level(thd))) { func_exit: btr_search_x_unlock_all(); return ok; @@ -2112,7 +2123,7 @@ static bool btr_search_hash_table_validate(THD *thd, ulint hash_table_id) btr_search_x_lock_all(); - if (!btr_search.enabled + if (!btr_search.get_enabled() || (thd && thd_kill_level(thd))) { goto func_exit; } @@ -2202,7 +2213,7 @@ static bool btr_search_hash_table_validate(THD *thd, ulint hash_table_id) btr_search_x_lock_all(); - if (!btr_search.enabled + if (!btr_search.get_enabled() || (thd && thd_kill_level(thd))) { goto func_exit; } diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 1e2f02b2f0c09..3fc440eee1fd4 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1071,7 +1071,7 @@ inline void buf_pool_t::garbage_collect() noexcept my_cond_wait(&done_flush_list, &flush_list_mutex.m_mutex); mysql_mutex_unlock(&flush_list_mutex); # ifdef BTR_CUR_HASH_ADAPT - bool ahi_disabled= btr_search.disable(); + ulong ahi_disabled= btr_search.disable(); # endif /* BTR_CUR_HASH_ADAPT */ time_t start= time(nullptr); mysql_mutex_lock(&mutex); @@ -1092,7 +1092,7 @@ inline void buf_pool_t::garbage_collect() noexcept shrunk(size, reduce_size); # ifdef BTR_CUR_HASH_ADAPT if (ahi_disabled) - btr_search.enable(true); + btr_search.enable(true, ahi_disabled); # endif mysql_mutex_unlock(&mutex); sql_print_information("InnoDB: Memory pressure event shrunk" @@ -1502,8 +1502,8 @@ bool buf_pool_t::create() noexcept buf_LRU_old_ratio_update(100 * 3 / 8, false); #ifdef BTR_CUR_HASH_ADAPT - if (btr_search.enabled) - btr_search.enable(); + if (btr_search.get_enabled()) + btr_search.enable(false, btr_search.get_enabled()); #endif #ifdef __linux__ @@ -1959,7 +1959,7 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept } #ifdef BTR_CUR_HASH_ADAPT - bool ahi_disabled= false; + ulong ahi_disabled= 0; #endif const bool significant_change= @@ -2075,7 +2075,7 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept #ifdef BTR_CUR_HASH_ADAPT if (ahi_disabled) - btr_search.enable(true); + btr_search.enable(true, ahi_disabled); #endif if (n_blocks_removed) sql_print_information("InnoDB: innodb_buffer_pool_size=%zum (%zu pages)" @@ -2161,7 +2161,7 @@ ATTRIBUTE_COLD void buf_pool_t::resize(size_t size, THD *thd) noexcept MYF(ME_ERROR_LOG)); #ifdef BTR_CUR_HASH_ADAPT if (ahi_disabled) - btr_search.enable(true); + btr_search.enable(true, ahi_disabled); #endif mysql_mutex_lock(&LOCK_global_system_variables); } @@ -3696,7 +3696,7 @@ ATTRIBUTE_COLD void buf_pool_t::clear_hash_index() noexcept std::set garbage; mysql_mutex_lock(&mutex); - ut_ad(!btr_search.enabled); + ut_ad(!btr_search.get_enabled()); for (char *extent= memory, *end= memory + block_descriptors_in_bytes(n_blocks); diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 4fda59511556b..186699342249f 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -674,10 +674,24 @@ ha_create_table_option innodb_table_option_list[]= HA_TOPTION_ENUM("ENCRYPTED", encryption, "DEFAULT,YES,NO", 0), /* With this option the user defines the key identifier using for the encryption */ HA_TOPTION_SYSVAR("ENCRYPTION_KEY_ID", encryption_key_id, default_encryption_key_id), - + HA_TOPTION_ENUM("ADAPTIVE_HASH_INDEX", adaptive_hash_index, + table_hint_options, 0), HA_TOPTION_END }; + +ha_create_table_option innodb_index_option_list[]= +{ + HA_IOPTION_ENUM("ADAPTIVE_HASH_INDEX", adaptive_hash_index, + table_hint_options, 0), + HA_IOPTION_NUMBER("COMPLETE_FIELDS", complete_fields, 0, 1, 32, 1), + HA_IOPTION_NUMBER("BYTES_FROM_INCOMPLETE_FIELDS", + bytes_from_incomplete_fields, 0, 1, 8192, 1), + HA_IOPTION_BOOL("FOR_EQUAL_HASH_POINT_TO_LAST_RECORD", + for_equal_hash_point_to_last_record, 0), + HA_IOPTION_END +}; + /*************************************************************//** Check whether valid argument given to innodb_ft_*_stopword_table. This function is registered as a callback with MySQL. @@ -2891,6 +2905,20 @@ innobase_copy_frm_flags_from_table_share( table_share->stats_auto_recalc, innodb_table->stat_initialized())) innodb_table->stats_sample_pages= table_share->stats_sample_pages; +# ifdef BTR_CUR_HASH_ADAPT + /* + ahi_enabled is set to 0 if the user has disabled AHI index for this table + by setting adaptive_hash_index=yes, 1 if it should be enabled if global + ahi is enabled for all tables and 2 if ahi is marked as if_specified + */ + if (!table_share->option_struct || + table_share->option_struct->adaptive_hash_index == 0) + innodb_table->ahi_enabled= 1; // Not defined, use default ahi setting + else if (table_share->option_struct->adaptive_hash_index == 1) + innodb_table->ahi_enabled= 2; // Enabled for table + else + innodb_table->ahi_enabled= 0; // Disabled by for table +#endif } /*********************************************************************//** @@ -3592,10 +3620,13 @@ static MYSQL_SYSVAR_UINT(log_write_ahead_size, log_sys.write_size, static void innodb_adaptive_hash_index_update(THD*, st_mysql_sys_var*, void*, const void *save) noexcept { + ulong option; /* Prevent a possible deadlock with innobase_fts_load_stopword() */ mysql_mutex_unlock(&LOCK_global_system_variables); - if (*static_cast(save)) - btr_search.enable(); + + option= *static_cast(save); + if (option) + btr_search.enable(false, option); else btr_search.disable(); mysql_mutex_lock(&LOCK_global_system_variables); @@ -4121,6 +4152,7 @@ static int innodb_init(void* p) innobase_hton->tablefile_extensions = ha_innobase_exts; innobase_hton->table_options = innodb_table_option_list; + innobase_hton->index_options = innodb_index_option_list; /* System Versioning */ innobase_hton->prepare_commit_versioned @@ -6021,10 +6053,12 @@ ha_innobase::open(const char* name, int, uint) initialize_auto_increment(m_prebuilt->table, *ai, *table->s); } - /* Set plugin parser for fulltext index */ + /* Set plugin parser for fulltext index and ahi */ for (uint i = 0; i < table->s->keys; i++) { + dict_index_t* index = 0; + uint8_t ahi; if (table->key_info[i].flags & HA_USES_PARSER) { - dict_index_t* index = innobase_get_index(i); + index = innobase_get_index(i); plugin_ref parser = table->key_info[i].parser; ut_ad(index->type & DICT_FTS); @@ -6035,7 +6069,28 @@ ha_innobase::open(const char* name, int, uint) DBUG_EXECUTE_IF("fts_instrument_use_default_parser", index->parser = &fts_default_parser;); } +#ifdef BTR_CUR_HASH_ADAPT + /* + Enable AHI if index option is 'yes' or if no index + option is given and table level AHI is enabled + See btre_sea::is_enabled() for usage of this. + */ + ahi= 0; + if (table->key_info[i].option_struct->adaptive_hash_index == 0 && + ib_table->ahi_enabled) + ahi= ib_table->ahi_enabled; // 1 (default) or 2 (forced) + else if (table->key_info[i].option_struct->adaptive_hash_index == 1) + ahi= 2; // Force index usage + + if (ahi) + { + if (!index) + index = innobase_get_index(i); + if (index) // Not primary key + index->search_info.ahi_enabled= ahi; + } } +#endif /* BTR_CUR_HASH_ADAPT */ ut_ad(!m_prebuilt->table || table->versioned() == m_prebuilt->table->versioned()); @@ -11359,7 +11414,6 @@ create_table_info_t::check_table_options() return "PAGE_COMPRESSION_LEVEL"; } } - return NULL; } @@ -19306,10 +19360,26 @@ static MYSQL_SYSVAR_BOOL(stats_traditional, srv_stats_sample_traditional, NULL, NULL, TRUE); #ifdef BTR_CUR_HASH_ADAPT -static MYSQL_SYSVAR_BOOL(adaptive_hash_index, *(my_bool*) &btr_search.enabled, +/** Possible values for the variable adaptive_hash_index */ +const char* innodb_ahi_names[] = { + "OFF", + "ON", + "IF_SPECIFIED", + NullS +}; + +/** Used to define an enumerate type of the system variable +innodb_checksum_algorithm. */ +TYPELIB innodb_ahi_typelib = + CREATE_TYPELIB_FOR(innodb_ahi_names); + +static MYSQL_SYSVAR_ENUM(adaptive_hash_index, *(ulong*) &btr_search.enabled, PLUGIN_VAR_OPCMDARG, - "Enable InnoDB adaptive hash index (disabled by default)", - NULL, innodb_adaptive_hash_index_update, false); + "Enable InnoDB adaptive hash index. Values OFF (default), ON or " + "IF_SPECIFIED (enabled only tables or indexes that " + "have adaptive_hash_index=on)", + NULL, innodb_adaptive_hash_index_update, false, + &innodb_ahi_typelib); static MYSQL_SYSVAR_ULONG(adaptive_hash_index_parts, btr_search.n_parts, PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_READONLY, diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index b6fb571078bea..1a49f2049845a 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -44,10 +44,19 @@ struct ha_table_option_struct innodb_use_atomic_writes. Atomic writes are not used if value OFF.*/ - uint encryption; /*!< DEFAULT, ON, OFF */ + uint adaptive_hash_index; /*!< DEFAULT, ON, OFF */ + uint encryption; /*!< DEFAULT, ON, OFF */ ulonglong encryption_key_id; /*!< encryption key id */ }; +struct ha_index_option_struct +{ + uint adaptive_hash_index; /*!< DEFAULT, ON, OFF */ + ulonglong complete_fields; + ulonglong bytes_from_incomplete_fields; + my_bool for_equal_hash_point_to_last_record; +}; + /** The class defining a handle to an Innodb table */ class ha_innobase final : public handler { diff --git a/storage/innobase/ibuf/ibuf0ibuf.cc b/storage/innobase/ibuf/ibuf0ibuf.cc index f2c4f9bc0cd76..19f5c692b0bd5 100644 --- a/storage/innobase/ibuf/ibuf0ibuf.cc +++ b/storage/innobase/ibuf/ibuf0ibuf.cc @@ -900,7 +900,7 @@ ATTRIBUTE_COLD dberr_t ibuf_upgrade() sql_print_information("InnoDB: Upgrading the change buffer"); #ifdef BTR_CUR_HASH_ADAPT - const bool ahi= btr_search.enabled; + const ulong ahi= btr_search.get_enabled(); if (ahi) btr_search.disable(); #endif @@ -1007,7 +1007,7 @@ ATTRIBUTE_COLD dberr_t ibuf_upgrade() #ifdef BTR_CUR_HASH_ADAPT if (ahi) - btr_search.enable(); + btr_search.enable(false, ahi); #endif ibuf_index->lock.free(); diff --git a/storage/innobase/include/btr0sea.h b/storage/innobase/include/btr0sea.h index 685d9a4feb2b1..f38a2bb1389b1 100644 --- a/storage/innobase/include/btr0sea.h +++ b/storage/innobase/include/btr0sea.h @@ -108,24 +108,54 @@ struct btr_sea { /** the actual value of innodb_adaptive_hash_index, protected by all partition::latch. Note that if buf_block_t::index is not nullptr - while a thread is holding a partition::latch, then also this must hold. */ - Atomic_relaxed enabled; + while a thread is holding a partition::latch, then also this must hold. + Values 0 (disabled), 1 (enabled) or 2 (enabled if table or index option + enabled ahi */ + Atomic_relaxed enabled; private: /** Disable the adaptive hash search system and empty the index. @return whether the adaptive hash index was enabled */ - ATTRIBUTE_COLD bool disable_and_lock() noexcept; + ATTRIBUTE_COLD ulong disable_and_lock() noexcept; /** Unlock the adaptive hash search system. */ ATTRIBUTE_COLD void unlock() noexcept; + public: + + /** Check if ahi is enable for an index + @return true if enabled */ + inline bool is_enabled(const dict_index_t *index) const noexcept + { + /* + Index is enabled if global ahi is enabled and index can be enabled. + If enabled is set to 2 (IF_SPECIFIED), only enable indexes declared + with ahi enabled on (ahi_enabled == 2). + We don't have to check if index->search_info.ahi_enabled != 0 + as the test enabled <= ahi_enabled will not be true in this case. + */ + return (unlikely(enabled != 0) && + enabled <= index->search_info.ahi_enabled); + } + /* + Same as above, but if index == 0 return 1. This to handle the case + where we do not yet know if ahi for the index is enabled or not + */ + inline bool may_be_enabled(const dict_index_t *index) const noexcept + { + return (unlikely(enabled != 0) && + (!index || enabled <= index->search_info.ahi_enabled)); + } + + inline ulong get_enabled() { return enabled; } + /** Disable the adaptive hash search system and empty the index. @return whether the adaptive hash index was enabled */ - ATTRIBUTE_COLD bool disable() noexcept; + ATTRIBUTE_COLD ulong disable() noexcept; /** Enable the adaptive hash search system. @param resize whether buf_pool_t::resize() is the caller */ - ATTRIBUTE_COLD void enable(bool resize= false) noexcept; + ATTRIBUTE_COLD void enable(bool resize, ulong enable_type) noexcept; /** Hash cell chain in hash_table */ struct hash_chain diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index e02519a64e0cd..ed31d73259569 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1089,6 +1089,12 @@ struct dict_index_t { search, and the calculation itself is not always accurate! */ Atomic_relaxed last_hash_succ{false}; + /** If adaptive hash indexes are enabled for this index + Values 0 (not enabled), 1 (enabled if ahi is globally enabled), + 2 (adaptive_hash_index=on was set for the index) + */ + Atomic_relaxed ahi_enabled{0}; + /** recommended parameters; @see buf_block_t::left_bytes_fields */ Atomic_relaxed left_bytes_fields{buf_block_t::LEFT_SIDE | 1}; /** number of buf_block_t::index pointers to this index */ @@ -2311,6 +2317,17 @@ struct dict_table_t { /*!< True if the table belongs to a system database (mysql, information_schema or performance_schema) */ +#ifdef BTR_CUR_HASH_ADAPT + uint8_t ahi_enabled; /*!< set to 0 if ahi is by default disabled + for this table, + 1 if should ahi should be enabled if global ahi + is enabled and 2 if it should be enabled if + global ahi == if_specified + Used to update index->ahi_enabled. + Only used in ha_innodb.cc to set ahi_enabled for + each index. + */ +#endif /* BTR_CUR_HASH_ADAPT */ dict_frm_t dict_frm_mismatch; /*!< !DICT_FRM_CONSISTENT==0 if data dictionary information and diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index c3c17dd1d305e..38a64ce2324ca 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -4541,7 +4541,7 @@ row_search_mvcc( if (UNIV_UNLIKELY(direction == 0) && unique_search - && btr_search.enabled + && btr_search.get_enabled() && dict_index_is_clust(index) && !index->table->is_temporary() && !prebuilt->templ_contains_blob diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index f9fffde59bb2e..6bd5d774e7d92 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -739,7 +739,7 @@ srv_printf_innodb_monitor( os_aio_print(file); #ifdef BTR_CUR_HASH_ADAPT - if (btr_search.enabled) { + if (btr_search.get_enabled()) { fputs("-------------------\n" "ADAPTIVE HASH INDEX\n" "-------------------\n", file); From 0c2e79cb6c05851b4ca82f0aee45f30f7beee1e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 2 Jan 2026 13:35:22 +0200 Subject: [PATCH 05/10] fixup! d52fe8f7e7241d1f780f40eba39f4679b8a7a8f6 --- storage/innobase/btr/btr0sea.cc | 2 +- storage/innobase/include/btr0sea.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc index 545fbd97d134f..4dbb4bbcdb004 100644 --- a/storage/innobase/btr/btr0sea.cc +++ b/storage/innobase/btr/btr0sea.cc @@ -458,7 +458,7 @@ ATTRIBUTE_COLD void btr_sea::enable(bool resize, ulong enable_opt) noexcept ATTRIBUTE_COLD void btr_sea::resize(uint n_cells) noexcept { - const bool was_enabled{disable_and_lock()}; + const ulong was_enabled{disable_and_lock()}; clear(); ut_ad(!parts[0].table.array); diff --git a/storage/innobase/include/btr0sea.h b/storage/innobase/include/btr0sea.h index f38a2bb1389b1..0c33cbda240ab 100644 --- a/storage/innobase/include/btr0sea.h +++ b/storage/innobase/include/btr0sea.h @@ -134,6 +134,7 @@ struct btr_sea We don't have to check if index->search_info.ahi_enabled != 0 as the test enabled <= ahi_enabled will not be true in this case. */ + const ulong enabled{get_enabled()}; return (unlikely(enabled != 0) && enabled <= index->search_info.ahi_enabled); } @@ -143,11 +144,12 @@ struct btr_sea */ inline bool may_be_enabled(const dict_index_t *index) const noexcept { + const ulong enabled{get_enabled()}; return (unlikely(enabled != 0) && (!index || enabled <= index->search_info.ahi_enabled)); } - inline ulong get_enabled() { return enabled; } + inline ulong get_enabled() const noexcept { return enabled; } /** Disable the adaptive hash search system and empty the index. @return whether the adaptive hash index was enabled */ From 6e56bfd804436a27eda63808269d72d581b057bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 2 Jan 2026 13:36:44 +0200 Subject: [PATCH 06/10] Test case by Thiru FIXME: Correctly implement the per-index parameters and adjust the test --- .../suite/innodb/r/index_ahi_option,ahi.rdiff | 38 +++++ .../suite/innodb/r/index_ahi_option.result | 148 ++++++++++++++++++ .../innodb/t/index_ahi_option.combinations | 2 + .../suite/innodb/t/index_ahi_option.test | 63 ++++++++ 4 files changed, 251 insertions(+) create mode 100644 mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff create mode 100644 mysql-test/suite/innodb/r/index_ahi_option.result create mode 100644 mysql-test/suite/innodb/t/index_ahi_option.combinations create mode 100644 mysql-test/suite/innodb/t/index_ahi_option.test diff --git a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff new file mode 100644 index 0000000000000..4157eba5ed7d5 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff @@ -0,0 +1,38 @@ +--- index_ahi_option.result ++++ index_ahi_option,ahi.result +@@ -2,7 +2,7 @@ + # Test InnoDB index-level adaptive_hash_index options + # + SET @start_global_value = @@global.innodb_adaptive_hash_index; +-SET GLOBAL innodb_adaptive_hash_index=OFF; ++SET GLOBAL innodb_adaptive_hash_index=ON; + # + # Scenario 1: complete_fields parameter with point lookups + # +@@ -49,7 +49,7 @@ + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { +- "pages_accessed": 6 ++ "pages_accessed": 5 + }, + "filtered": 100, + "r_total_filtered": 100, +@@ -90,7 +90,7 @@ + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { +- "pages_accessed": 4 ++ "pages_accessed": 3 + }, + "filtered": 100, + "r_total_filtered": 100, +@@ -132,7 +132,7 @@ + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { +- "pages_accessed": 6 ++ "pages_accessed": 5 + }, + "filtered": 100, + "r_total_filtered": 100, diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result new file mode 100644 index 0000000000000..74267e75228c1 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -0,0 +1,148 @@ +# +# Test InnoDB index-level adaptive_hash_index options +# +SET @start_global_value = @@global.innodb_adaptive_hash_index; +SET GLOBAL innodb_adaptive_hash_index=OFF; +# +# Scenario 1: complete_fields parameter with point lookups +# +CREATE TABLE t1 ( +id INT PRIMARY KEY, +col1 INT, col2 INT, col3 INT, +INDEX idx_1 (col1), +INDEX idx_2 (col1, col2), +INDEX idx_3 (col1, col2, col3) +) ENGINE=InnoDB; +INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; +INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), +(50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), +(50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), +(50009, 50, 9, 4); +# Analyze format idx_1 +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.076308585, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["idx_1"], + "key": "idx_1", + "key_length": "5", + "used_key_parts": ["col1"], + "ref": ["const"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.076308585, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +# Analyze format idx_2 +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_2"], + "key": "idx_2", + "key_length": "10", + "used_key_parts": ["col1", "col2"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 4 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +# Analyze format idx_3 +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_3"], + "key": "idx_3", + "key_length": "15", + "used_key_parts": ["col1", "col2", "col3"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +DROP TABLE t1; +SET @@global.innodb_adaptive_hash_index = @start_global_value; diff --git a/mysql-test/suite/innodb/t/index_ahi_option.combinations b/mysql-test/suite/innodb/t/index_ahi_option.combinations new file mode 100644 index 0000000000000..c04124526ecb2 --- /dev/null +++ b/mysql-test/suite/innodb/t/index_ahi_option.combinations @@ -0,0 +1,2 @@ +[ahi] +[no_ahi] diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test new file mode 100644 index 0000000000000..4cf46d15e6a97 --- /dev/null +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -0,0 +1,63 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc + +--echo # +--echo # Test InnoDB index-level adaptive_hash_index options +--echo # + +SET @start_global_value = @@global.innodb_adaptive_hash_index; +if ($MTR_COMBINATION_AHI == 1) { +SET GLOBAL innodb_adaptive_hash_index=ON; +} +if (!$MTR_COMBINATION_AHI) { +SET GLOBAL innodb_adaptive_hash_index=OFF; +} + +--echo # +--echo # Scenario 1: complete_fields parameter with point lookups +--echo # + +CREATE TABLE t1 ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, + INDEX idx_1 (col1), + INDEX idx_2 (col1, col2), + INDEX idx_3 (col1, col2, col3) +) ENGINE=InnoDB; + +INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; + +INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), +(50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), +(50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), +(50009, 50, 9, 4); + +let $i = 120; +while ($i) +{ + --disable_query_log + --disable_result_log + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0; + SELECT count(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 and col2 >= 0 and col3 = 0; + --enable_result_log + --enable_query_log + dec $i; +} + +--echo # Analyze format idx_1 +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + +--echo # Analyze format idx_2 +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; + +--echo # Analyze format idx_3 +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +DROP TABLE t1; +SET @@global.innodb_adaptive_hash_index = @start_global_value; From 670eb1b5826a8f047f75e34b0ba4fd08dac9b6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 5 Jan 2026 09:44:08 +0200 Subject: [PATCH 07/10] WIP: Consistently handle the adaptive_hash_index attribute innodb_ahi_enable(): Apply the adative_hash_index table and index options to the InnoDB table and index. FIXME: The value 2, which the logic makes use of, is never being used. --- .../suite/innodb/r/index_ahi_option,ahi.rdiff | 6 +- .../suite/innodb/r/index_ahi_option.result | 255 +++++++++++++++++- .../suite/innodb/t/index_ahi_option.test | 41 ++- storage/innobase/handler/ha_innodb.cc | 117 ++++---- storage/innobase/handler/ha_innodb.h | 12 +- storage/innobase/handler/handler0alter.cc | 8 +- 6 files changed, 364 insertions(+), 75 deletions(-) diff --git a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff index 4157eba5ed7d5..609f55034c303 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff @@ -9,7 +9,7 @@ # # Scenario 1: complete_fields parameter with point lookups # -@@ -49,7 +49,7 @@ +@@ -48,7 +48,7 @@ "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { @@ -18,7 +18,7 @@ }, "filtered": 100, "r_total_filtered": 100, -@@ -90,7 +90,7 @@ +@@ -88,7 +88,7 @@ "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { @@ -27,7 +27,7 @@ }, "filtered": 100, "r_total_filtered": 100, -@@ -132,7 +132,7 @@ +@@ -129,7 +129,7 @@ "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result index 74267e75228c1..71fae93710cec 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option.result +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -11,14 +11,13 @@ id INT PRIMARY KEY, col1 INT, col2 INT, col3 INT, INDEX idx_1 (col1), INDEX idx_2 (col1, col2), -INDEX idx_3 (col1, col2, col3) -) ENGINE=InnoDB; +INDEX idx_3 (col1, col2, col3) +) ENGINE=InnoDB STATS_PERSISTENT=0; INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), (50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), (50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), (50009, 50, 9, 4); -# Analyze format idx_1 ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; ANALYZE @@ -60,7 +59,6 @@ ANALYZE ] } } -# Analyze format idx_2 ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; ANALYZE @@ -102,7 +100,254 @@ ANALYZE ] } } -# Analyze format idx_3 +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_3"], + "key": "idx_3", + "key_length": "15", + "used_key_parts": ["col1", "col2", "col3"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +ALTER TABLE t1 adaptive_hash_index=OFF, FORCE; +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.076308585, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["idx_1"], + "key": "idx_1", + "key_length": "5", + "used_key_parts": ["col1"], + "ref": ["const"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.076308585, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 4 + }, + "filtered": 100, + "r_total_filtered": 100, + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_2"], + "key": "idx_2", + "key_length": "10", + "used_key_parts": ["col1", "col2"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_3"], + "key": "idx_3", + "key_length": "15", + "used_key_parts": ["col1", "col2", "col3"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +ALTER TABLE t1 adaptive_hash_index='ON'; +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.076308585, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["idx_1"], + "key": "idx_1", + "key_length": "5", + "used_key_parts": ["col1"], + "ref": ["const"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.076308585, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 4 + }, + "filtered": 100, + "r_total_filtered": 100, + "r_filtered": 100, + "using_index": true + } + } + ] + } +} +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; +ANALYZE +{ + "query_optimization": { + "r_total_time_ms": "REPLACED" + }, + "query_block": { + "select_id": 1, + "cost": 0.077099705, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "nested_loop": [ + { + "table": { + "table_name": "t1", + "access_type": "range", + "possible_keys": ["idx_2"], + "key": "idx_2", + "key_length": "10", + "used_key_parts": ["col1", "col2"], + "loops": 1, + "r_loops": 1, + "rows": 509, + "r_rows": 509, + "cost": 0.077099705, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": { + "pages_accessed": 6 + }, + "filtered": 100, + "r_total_filtered": 100, + "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", + "r_filtered": 100, + "using_index": true + } + } + ] + } +} ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; ANALYZE diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 4cf46d15e6a97..9f749e1f15be6 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -22,8 +22,8 @@ CREATE TABLE t1 ( col1 INT, col2 INT, col3 INT, INDEX idx_1 (col1), INDEX idx_2 (col1, col2), - INDEX idx_3 (col1, col2, col3) -) ENGINE=InnoDB; + INDEX idx_3 (col1, col2, col3) +) ENGINE=InnoDB STATS_PERSISTENT=0; INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; @@ -45,19 +45,46 @@ while ($i) dec $i; } ---echo # Analyze format idx_1 --replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; - ---echo # Analyze format idx_2 --replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; - ---echo # Analyze format idx_3 --replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +ALTER TABLE t1 adaptive_hash_index=OFF, FORCE; +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; +ALTER TABLE t1 adaptive_hash_index='ON'; +let $i = 120; +while ($i) +{ + --disable_query_log + --disable_result_log + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0; + SELECT count(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 and col2 >= 0 and col3 = 0; + --enable_result_log + --enable_query_log + dec $i; +}--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; +--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +ANALYZE FORMAT=JSON +SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; + DROP TABLE t1; SET @@global.innodb_adaptive_hash_index = @start_global_value; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 186699342249f..a369974870eb6 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2873,6 +2873,49 @@ static bool innodb_copy_stat_flags(dict_table_t *table, return true; } +#ifdef BTR_CUR_HASH_ADAPT +/** Enable the adaptive hash index for indexes where needed. +@param innodb_table InnoDB table definition +@param option_struct MariaDB table options +@param table MariaDB table handle */ +static void innodb_ahi_enable(dict_table_t *innodb_table, + const ha_table_option_struct *option_struct, + const TABLE *table) +{ + const uint8_t table_ahi= option_struct + ? uint8_t((option_struct->adaptive_hash_index + 1) % 3) + : uint8_t{1}; + innodb_table->ahi_enabled= table_ahi; + + if (table_ahi) + { + /* In case there is no PRIMARY KEY or UNIQUE INDEX on NOT NULL + columns, there will be GEN_CLUST_INDEX(DB_ROW_ID). Default to the + table option for it. If a PRIMARY KEY is defined, this default + value may be updated in the loop below. */ + UT_LIST_GET_FIRST(innodb_table->indexes)->search_info.ahi_enabled= + table_ahi; + for (auto i= table->s->keys; i--; ) + { + uint8_t ahi= table_ahi; + const KEY &key= table->key_info[i]; + switch (key.option_struct->adaptive_hash_index) { + default: + break; + case 1: + ahi= 2; + /* fall through */ + case 0: + dict_index_t *index= + dict_table_get_index_on_name(innodb_table, key.name.str); + if (index) + index->search_info.ahi_enabled= ahi; + } + } + } +} +#endif + /*********************************************************************//** Copy table flags from MySQL's HA_CREATE_INFO into an InnoDB table object. Those flags are stored in .frm file and end up in the MySQL table object, @@ -2883,42 +2926,39 @@ void innobase_copy_frm_flags_from_create_info( /*=====================================*/ dict_table_t* innodb_table, /*!< in/out: InnoDB table */ - const HA_CREATE_INFO* create_info) /*!< in: create info */ + const HA_CREATE_INFO* create_info, /*!< in: create info */ + const TABLE* table) /*!< in: MariaDB table */ { if (innodb_copy_stat_flags(innodb_table, create_info->table_options, create_info->stats_auto_recalc, false)) + { innodb_table->stats_sample_pages= create_info->stats_sample_pages; +#ifdef BTR_CUR_HASH_ADAPT + innodb_ahi_enable(innodb_table, create_info->option_struct, table); +#endif + } } -/*********************************************************************//** -Copy table flags from MySQL's TABLE_SHARE into an InnoDB table object. +/** +Copy table flags from MariaDB TABLE into an InnoDB table object. Those flags are stored in .frm file and end up in the MySQL table object, but are frequently used inside InnoDB so we keep their copies into the -InnoDB table object. */ -void -innobase_copy_frm_flags_from_table_share( -/*=====================================*/ - dict_table_t* innodb_table, /*!< in/out: InnoDB table */ - const TABLE_SHARE* table_share) /*!< in: table share */ +InnoDB table object. +@param innodb_table InnoDB table +@param table MariaDB table handle */ +void innobase_copy_frm_flags_from_table(dict_table_t *innodb_table, + const TABLE *table) noexcept { + const TABLE_SHARE *const table_share{table->s}; if (innodb_copy_stat_flags(innodb_table, table_share->db_create_options, table_share->stats_auto_recalc, innodb_table->stat_initialized())) + { innodb_table->stats_sample_pages= table_share->stats_sample_pages; -# ifdef BTR_CUR_HASH_ADAPT - /* - ahi_enabled is set to 0 if the user has disabled AHI index for this table - by setting adaptive_hash_index=yes, 1 if it should be enabled if global - ahi is enabled for all tables and 2 if ahi is marked as if_specified - */ - if (!table_share->option_struct || - table_share->option_struct->adaptive_hash_index == 0) - innodb_table->ahi_enabled= 1; // Not defined, use default ahi setting - else if (table_share->option_struct->adaptive_hash_index == 1) - innodb_table->ahi_enabled= 2; // Enabled for table - else - innodb_table->ahi_enabled= 0; // Disabled by for table +#ifdef BTR_CUR_HASH_ADAPT + innodb_ahi_enable(innodb_table, table_share->option_struct, table); #endif + } } /*********************************************************************//** @@ -5878,7 +5918,7 @@ ha_innobase::open(const char* name, int, uint) DBUG_RETURN(HA_ERR_CRASHED_ON_USAGE); } - innobase_copy_frm_flags_from_table_share(ib_table, table->s); + innobase_copy_frm_flags_from_table(ib_table, table); MONITOR_INC(MONITOR_TABLE_OPEN); @@ -6053,12 +6093,10 @@ ha_innobase::open(const char* name, int, uint) initialize_auto_increment(m_prebuilt->table, *ai, *table->s); } - /* Set plugin parser for fulltext index and ahi */ + /* Set plugin parser for fulltext index */ for (uint i = 0; i < table->s->keys; i++) { - dict_index_t* index = 0; - uint8_t ahi; if (table->key_info[i].flags & HA_USES_PARSER) { - index = innobase_get_index(i); + dict_index_t *index = innobase_get_index(i); plugin_ref parser = table->key_info[i].parser; ut_ad(index->type & DICT_FTS); @@ -6069,28 +6107,7 @@ ha_innobase::open(const char* name, int, uint) DBUG_EXECUTE_IF("fts_instrument_use_default_parser", index->parser = &fts_default_parser;); } -#ifdef BTR_CUR_HASH_ADAPT - /* - Enable AHI if index option is 'yes' or if no index - option is given and table level AHI is enabled - See btre_sea::is_enabled() for usage of this. - */ - ahi= 0; - if (table->key_info[i].option_struct->adaptive_hash_index == 0 && - ib_table->ahi_enabled) - ahi= ib_table->ahi_enabled; // 1 (default) or 2 (forced) - else if (table->key_info[i].option_struct->adaptive_hash_index == 1) - ahi= 2; // Force index usage - - if (ahi) - { - if (!index) - index = innobase_get_index(i); - if (index) // Not primary key - index->search_info.ahi_enabled= ahi; - } } -#endif /* BTR_CUR_HASH_ADAPT */ ut_ad(!m_prebuilt->table || table->versioned() == m_prebuilt->table->versioned()); @@ -13189,7 +13206,7 @@ void create_table_info_t::create_table_update_dict(dict_table_t *table, DBUG_ASSERT(!table->fts == !table->fts_doc_id_index); - innobase_copy_frm_flags_from_create_info(table, &info); + innobase_copy_frm_flags_from_create_info(table, &info, &t); /* Load server stopword into FTS cache */ if (table->flags2 & DICT_TF2_FTS && @@ -17466,7 +17483,7 @@ ha_innobase::check_if_incompatible_data( m_prebuilt->table->stats_mutex_lock(); if (!m_prebuilt->table->stat_initialized()) { innobase_copy_frm_flags_from_create_info( - m_prebuilt->table, info); + m_prebuilt->table, info, table); } m_prebuilt->table->stats_mutex_unlock(); diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 1a49f2049845a..781dec388e9f3 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -806,14 +806,14 @@ innobase_fts_check_doc_id_index_in_def( MY_ATTRIBUTE((warn_unused_result)); /** -Copy table flags from MySQL's TABLE_SHARE into an InnoDB table object. +Copy table flags from MariaDB TABLE into an InnoDB table object. Those flags are stored in .frm file and end up in the MySQL table object, but are frequently used inside InnoDB so we keep their copies into the -InnoDB table object. */ -void -innobase_copy_frm_flags_from_table_share( - dict_table_t* innodb_table, /*!< in/out: InnoDB table */ - const TABLE_SHARE* table_share); /*!< in: table share */ +InnoDB table object. +@param innodb_table InnoDB table +@param table MariaDB table handle */ +void innobase_copy_frm_flags_from_table(dict_table_t *innodb_table, + const TABLE *table) noexcept; /** Set up base columns for virtual column @param[in] table the InnoDB table diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 7d3ede43c1a89..f443c4cbab9a2 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -11707,8 +11707,8 @@ ha_innobase::commit_inplace_alter_table( DBUG_ASSERT(ctx->need_rebuild() == new_clustered); - innobase_copy_frm_flags_from_table_share( - ctx->new_table, altered_table->s); + innobase_copy_frm_flags_from_table( + ctx->new_table, altered_table); if (new_clustered) { DBUG_PRINT("to_be_dropped", @@ -11783,8 +11783,8 @@ ha_innobase::commit_inplace_alter_table( ctx->prebuilt->table = innobase_reload_table( m_user_thd, ctx->prebuilt->table, table->s->table_name, *ctx); - innobase_copy_frm_flags_from_table_share( - ctx->prebuilt->table, altered_table->s); + innobase_copy_frm_flags_from_table( + ctx->prebuilt->table, altered_table); } unlock_and_close_files(deleted, trx); From d829b1960193a8b3a686364fbc36232cf881e97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Mon, 5 Jan 2026 12:38:11 +0200 Subject: [PATCH 08/10] fixup! 7442b09bbc9ae6ea25dca14e595333b476ca36cf Cover SET GLOBAL innodb_adaptive_hash_index=if_specified. TODO: Only distinct 2 values of the table option adaptive_hash_index are still being observed. --- mysql-test/suite/innodb/r/index_ahi_option.result | 2 +- mysql-test/suite/innodb/t/index_ahi_option.combinations | 1 + mysql-test/suite/innodb/t/index_ahi_option.test | 9 ++++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result index 71fae93710cec..cc35000d2be77 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option.result +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -12,7 +12,7 @@ col1 INT, col2 INT, col3 INT, INDEX idx_1 (col1), INDEX idx_2 (col1, col2), INDEX idx_3 (col1, col2, col3) -) ENGINE=InnoDB STATS_PERSISTENT=0; +) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), (50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), diff --git a/mysql-test/suite/innodb/t/index_ahi_option.combinations b/mysql-test/suite/innodb/t/index_ahi_option.combinations index c04124526ecb2..f9099f637b0fa 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.combinations +++ b/mysql-test/suite/innodb/t/index_ahi_option.combinations @@ -1,2 +1,3 @@ [ahi] [no_ahi] +[if_specified] diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 9f749e1f15be6..40b6a61ae1abc 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -6,12 +6,15 @@ --echo # SET @start_global_value = @@global.innodb_adaptive_hash_index; -if ($MTR_COMBINATION_AHI == 1) { +if ($MTR_COMBINATION_AHI) { SET GLOBAL innodb_adaptive_hash_index=ON; } -if (!$MTR_COMBINATION_AHI) { +if ($MTR_COMBINATION_NO_AHI) { SET GLOBAL innodb_adaptive_hash_index=OFF; } +if ($MTR_COMBINATION_IF_SPECIFIED) { +SET GLOBAL innodb_adaptive_hash_index=if_specified; +} --echo # --echo # Scenario 1: complete_fields parameter with point lookups @@ -23,7 +26,7 @@ CREATE TABLE t1 ( INDEX idx_1 (col1), INDEX idx_2 (col1, col2), INDEX idx_3 (col1, col2, col3) -) ENGINE=InnoDB STATS_PERSISTENT=0; +) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; From b4c3eea5febcd1233c7f39e987a2ec09b7dc3722 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Thu, 15 Jan 2026 16:49:18 +0000 Subject: [PATCH 09/10] fixup: allocate table options in share root fix MSAN/ASAN errors due to use after free of `option_struct` in test `parts.partition_special_innodb`. TODO somebody more knowledgeable about partitions shall review this --- sql/create_options.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/create_options.cc b/sql/create_options.cc index ebfecb58a193f..2cea91171ce56 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -520,8 +520,8 @@ bool parse_engine_table_options(THD *thd, handlerton *ht, TABLE_SHARE *share) */ bool parse_engine_part_options(THD *thd, TABLE *table) { - MEM_ROOT *root= &table->mem_root; TABLE_SHARE *share= table->s; + MEM_ROOT *root= &share->mem_root; partition_info *part_info= table->part_info; engine_option_value *tmp_option_list; handlerton *ht; From 769793fb11e41c34768b1e0ae13e5ceb683adbb2 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 9 Jan 2026 15:58:03 +0000 Subject: [PATCH 10/10] fixup: implement and test per-index AHI options - make per-table/per-index `adaptive_hash_index` and per-index `for_equal_hash_point_to_last_record` options have 3 possible values: DEFAULT (0), YES (1), NO (2) - renamed `bytes_from_incomplete_fields` to `bytes_from_incomplete_field` - per-index `complete_fields` and `bytes_from_incomplete_field` options now default to `ULONGLONG_MAX` (which means DEFAULT/unset) and can be set to any legal value including 0 - `complete_fields` in [0, 64] - `bytes_from_incomplete_field` in [0, 16383] - store all AHI related options in per-index `dict_index_t::ahi` bit-packed 32-bit atomic field `ahi_enabled_fixed_mask` - static assertions and debug assertions ensure that all options fit into the 32-bit field - packing details: - `enabled`, `adaptive_hash_index` (first 2 bits) - `fields`, `complete_fields` (7 bit) - `bytes`, `bytes_from_incomplete_field` (14 bits) - `left`, `~for_equal_hash_point_to_last_record` (1 bit) - `is_fields_set`, `fields` set flag (1 bit) - `is_bytes_set`, `bytes` set flag (1 bit) - `is_left_set`, `left` set flag (last 1 bit) - 5 bits spare after `is_left_set` - remove unused per-table `ahi_enabled` option in `dict_table_t` - in `innodb_ahi_enable` set per-index options in any case to avoid stale values being picked up later - in `innodb_ahi_enable` ensure that the primary key is not updated twice if both per-table and per-index options are set - in `btr_sea::resize` avoid losing the previous `btr_sea::enabled` setting - in `btr_search_update_hash_ref` apply the per-index AHI options using bit-masking to override internal heuristic values with user preferences - in `innodb.index_ahi_option` replace `ANALYZE FORMAT=JSON` on `SELECT` over warmed-up AHI with a stored procedure which checks if AHI is used during a burst of index lookups checking delta in `adaptive_hash_searches` InnoDB monitor variable as this is more stable - in `innodb.index_ahi_option` test a combination of per-table and per-index AHI options - in `innodb.index_ahi_option` test that the maximum number of fields per (secondary) index is 64 (32+32) - in `innodb.index_ahi_option_debug` test debug builds with `index_ahi_option_debug_check` debug variable enabled to verify that the proper per-index AHI options are applied during index lookups - in `innodb.index_ahi_option_debug` test that illegal per-index AHI are non-destructive and just lead to no AHI usage - in `sys_vars.innodb_adaptive_hash_index_basic` replace: - `for_equal_hash_point_to_last_record=1` with - `for_equal_hash_point_to_last_record=no` to reflect the new 3-value logic - in `sys_vars.innodb_adaptive_hash_index_basic check that both `complete_fields` and `bytes_from_incomplete_field` can be set to 0 --- .../suite/innodb/r/index_ahi_option,ahi.rdiff | 67 +-- .../r/index_ahi_option,if_specified.rdiff | 41 ++ .../suite/innodb/r/index_ahi_option.result | 503 +++++------------- .../innodb/r/index_ahi_option_debug,ahi.rdiff | 46 ++ .../index_ahi_option_debug,if_specified.rdiff | 42 ++ .../innodb/r/index_ahi_option_debug.result | 72 +++ .../suite/innodb/t/index_ahi_option.test | 190 ++++--- .../t/index_ahi_option_debug.combinations | 3 + .../innodb/t/index_ahi_option_debug.test | 152 ++++++ .../r/innodb_adaptive_hash_index_basic.result | 16 +- .../t/innodb_adaptive_hash_index_basic.test | 6 +- sql/handler.cc | 4 +- storage/innobase/btr/btr0sea.cc | 186 ++++++- storage/innobase/handler/ha_innodb.cc | 109 ++-- storage/innobase/handler/ha_innodb.h | 6 +- storage/innobase/include/btr0sea.h | 10 +- storage/innobase/include/dict0mem.h | 155 +++++- 17 files changed, 1064 insertions(+), 544 deletions(-) create mode 100644 mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff create mode 100644 mysql-test/suite/innodb/r/index_ahi_option_debug,ahi.rdiff create mode 100644 mysql-test/suite/innodb/r/index_ahi_option_debug,if_specified.rdiff create mode 100644 mysql-test/suite/innodb/r/index_ahi_option_debug.result create mode 100644 mysql-test/suite/innodb/t/index_ahi_option_debug.combinations create mode 100644 mysql-test/suite/innodb/t/index_ahi_option_debug.test diff --git a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff index 609f55034c303..2b78361ec61b1 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff @@ -6,33 +6,40 @@ SET @start_global_value = @@global.innodb_adaptive_hash_index; -SET GLOBAL innodb_adaptive_hash_index=OFF; +SET GLOBAL innodb_adaptive_hash_index=ON; - # - # Scenario 1: complete_fields parameter with point lookups - # -@@ -48,7 +48,7 @@ - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { -- "pages_accessed": 6 -+ "pages_accessed": 5 - }, - "filtered": 100, - "r_total_filtered": 100, -@@ -88,7 +88,7 @@ - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { -- "pages_accessed": 4 -+ "pages_accessed": 3 - }, - "filtered": 100, - "r_total_filtered": 100, -@@ -129,7 +129,7 @@ - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { -- "pages_accessed": 6 -+ "pages_accessed": 5 - }, - "filtered": 100, - "r_total_filtered": 100, + CREATE TABLE t1 ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, +@@ -14,10 +14,10 @@ + SET GLOBAL innodb_monitor_enable = module_adaptive_hash; + CALL run_and_check_idx(1, 240); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) +@@ -27,17 +27,17 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; + CALL run_and_check_idx(1, 240); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) diff --git a/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff new file mode 100644 index 0000000000000..883d557437fe1 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff @@ -0,0 +1,41 @@ +--- index_ahi_option.result ++++ index_ahi_option,if_specified.result +@@ -2,7 +2,7 @@ + # Test InnoDB index-level adaptive_hash_index options + # + SET @start_global_value = @@global.innodb_adaptive_hash_index; +-SET GLOBAL innodb_adaptive_hash_index=OFF; ++SET GLOBAL innodb_adaptive_hash_index=if_specified; + CREATE TABLE t1 ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, +@@ -17,7 +17,7 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) +@@ -27,17 +27,17 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; + CALL run_and_check_idx(1, 240); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(2, 240); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(3, 240); + result_msg + # No AHI used in SELECT (idx_3) diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result index cc35000d2be77..49647a4a2c094 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option.result +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -3,391 +3,130 @@ # SET @start_global_value = @@global.innodb_adaptive_hash_index; SET GLOBAL innodb_adaptive_hash_index=OFF; -# -# Scenario 1: complete_fields parameter with point lookups -# CREATE TABLE t1 ( id INT PRIMARY KEY, col1 INT, col2 INT, col3 INT, -INDEX idx_1 (col1), -INDEX idx_2 (col1, col2), -INDEX idx_3 (col1, col2, col3) +INDEX idx_1 (col1) adaptive_hash_index=DEFAULT, +INDEX idx_2 (col1, col2) adaptive_hash_index=YES, +INDEX idx_3 (col1, col2, col3) adaptive_hash_index=NO ) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; -INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; -INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), -(50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), -(50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), -(50009, 50, 9, 4); -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.076308585, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "ref", - "possible_keys": ["idx_1"], - "key": "idx_1", - "key_length": "5", - "used_key_parts": ["col1"], - "ref": ["const"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.076308585, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_2"], - "key": "idx_2", - "key_length": "10", - "used_key_parts": ["col1", "col2"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 4 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_3"], - "key": "idx_3", - "key_length": "15", - "used_key_parts": ["col1", "col2", "col3"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ALTER TABLE t1 adaptive_hash_index=OFF, FORCE; -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.076308585, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "ref", - "possible_keys": ["idx_1"], - "key": "idx_1", - "key_length": "5", - "used_key_parts": ["col1"], - "ref": ["const"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.076308585, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 4 - }, - "filtered": 100, - "r_total_filtered": 100, - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_2"], - "key": "idx_2", - "key_length": "10", - "used_key_parts": ["col1", "col2"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_3"], - "key": "idx_3", - "key_length": "15", - "used_key_parts": ["col1", "col2", "col3"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ALTER TABLE t1 adaptive_hash_index='ON'; -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.076308585, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "ref", - "possible_keys": ["idx_1"], - "key": "idx_1", - "key_length": "5", - "used_key_parts": ["col1"], - "ref": ["const"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.076308585, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 4 - }, - "filtered": 100, - "r_total_filtered": 100, - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_2"], - "key": "idx_2", - "key_length": "10", - "used_key_parts": ["col1", "col2"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; -ANALYZE -{ - "query_optimization": { - "r_total_time_ms": "REPLACED" - }, - "query_block": { - "select_id": 1, - "cost": 0.077099705, - "r_loops": 1, - "r_total_time_ms": "REPLACED", - "nested_loop": [ - { - "table": { - "table_name": "t1", - "access_type": "range", - "possible_keys": ["idx_3"], - "key": "idx_3", - "key_length": "15", - "used_key_parts": ["col1", "col2", "col3"], - "loops": 1, - "r_loops": 1, - "rows": 509, - "r_rows": 509, - "cost": 0.077099705, - "r_table_time_ms": "REPLACED", - "r_other_time_ms": "REPLACED", - "r_engine_stats": { - "pages_accessed": 6 - }, - "filtered": 100, - "r_total_filtered": 100, - "attached_condition": "t1.col1 = 50 and t1.col2 between 0 and 9 and t1.col3 between 0 and 9", - "r_filtered": 100, - "using_index": true - } - } - ] - } -} +INSERT INTO t1 SELECT seq, seq, seq << 8, seq << 16 FROM seq_0_to_255; +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +CALL run_and_check_idx(1, 240); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(2, 240); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(3, 240); +result_msg +# No AHI used in SELECT (idx_3) +ALTER TABLE t1 adaptive_hash_index=OFF, ALGORITHM=INSTANT, LOCK=NONE; +CALL run_and_check_idx(1, 240); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(2, 240); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(3, 240); +result_msg +# No AHI used in SELECT (idx_3) +ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; +CALL run_and_check_idx(1, 240); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(2, 240); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(3, 240); +result_msg +# No AHI used in SELECT (idx_3) +DROP PROCEDURE run_and_check_idx; DROP TABLE t1; SET @@global.innodb_adaptive_hash_index = @start_global_value; +SET GLOBAL innodb_monitor_disable = module_adaptive_hash; +SET GLOBAL innodb_monitor_disable = default; +SET GLOBAL innodb_monitor_enable = default; +# +# Test maximum number of fields per (secondary) index is 64 (32+32) +# +CREATE TABLE t2 (dummy INT NOT NULL, c1 INT NOT NULL, c2 INT NOT NULL, c3 INT NOT NULL, c4 INT NOT NULL, c5 INT NOT NULL, c6 INT NOT NULL, c7 INT NOT NULL, c8 INT NOT NULL, c9 INT NOT NULL, c10 INT NOT NULL, c11 INT NOT NULL, c12 INT NOT NULL, c13 INT NOT NULL, c14 INT NOT NULL, c15 INT NOT NULL, c16 INT NOT NULL, c17 INT NOT NULL, c18 INT NOT NULL, c19 INT NOT NULL, c20 INT NOT NULL, c21 INT NOT NULL, c22 INT NOT NULL, c23 INT NOT NULL, c24 INT NOT NULL, c25 INT NOT NULL, c26 INT NOT NULL, c27 INT NOT NULL, c28 INT NOT NULL, c29 INT NOT NULL, c30 INT NOT NULL, c31 INT NOT NULL, c32 INT NOT NULL, c33 INT NOT NULL, c34 INT NOT NULL, c35 INT NOT NULL, c36 INT NOT NULL, c37 INT NOT NULL, c38 INT NOT NULL, c39 INT NOT NULL, c40 INT NOT NULL, c41 INT NOT NULL, c42 INT NOT NULL, c43 INT NOT NULL, c44 INT NOT NULL, c45 INT NOT NULL, c46 INT NOT NULL, c47 INT NOT NULL, c48 INT NOT NULL, c49 INT NOT NULL, c50 INT NOT NULL, c51 INT NOT NULL, c52 INT NOT NULL, c53 INT NOT NULL, c54 INT NOT NULL, c55 INT NOT NULL, c56 INT NOT NULL, c57 INT NOT NULL, c58 INT NOT NULL, c59 INT NOT NULL, c60 INT NOT NULL, c61 INT NOT NULL, c62 INT NOT NULL, c63 INT NOT NULL, c64 INT NOT NULL, c65 INT NOT NULL, PRIMARY KEY (dummy)) ENGINE=InnoDB; +ALTER TABLE t2 DROP PRIMARY KEY, ADD PRIMARY KEY (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31, c32, c33); +ERROR 42000: Too many key parts specified; max 32 parts allowed +ALTER TABLE t2 DROP PRIMARY KEY, ADD PRIMARY KEY (c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, c16, c17, c18, c19, c20, c21, c22, c23, c24, c25, c26, c27, c28, c29, c30, c31, c32); +CREATE INDEX idx_fail ON t2 (c33, c34, c35, c36, c37, c38, c39, c40, c41, c42, c43, c44, c45, c46, c47, c48, c49, c50, c51, c52, c53, c54, c55, c56, c57, c58, c59, c60, c61, c62, c63, c64, c65); +ERROR 42000: Too many key parts specified; max 32 parts allowed +CREATE INDEX idx_ok ON t2 (c33, c34, c35, c36, c37, c38, c39, c40, c41, c42, c43, c44, c45, c46, c47, c48, c49, c50, c51, c52, c53, c54, c55, c56, c57, c58, c59, c60, c61, c62, c63, c64); +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `dummy` int(11) NOT NULL, + `c1` int(11) NOT NULL, + `c2` int(11) NOT NULL, + `c3` int(11) NOT NULL, + `c4` int(11) NOT NULL, + `c5` int(11) NOT NULL, + `c6` int(11) NOT NULL, + `c7` int(11) NOT NULL, + `c8` int(11) NOT NULL, + `c9` int(11) NOT NULL, + `c10` int(11) NOT NULL, + `c11` int(11) NOT NULL, + `c12` int(11) NOT NULL, + `c13` int(11) NOT NULL, + `c14` int(11) NOT NULL, + `c15` int(11) NOT NULL, + `c16` int(11) NOT NULL, + `c17` int(11) NOT NULL, + `c18` int(11) NOT NULL, + `c19` int(11) NOT NULL, + `c20` int(11) NOT NULL, + `c21` int(11) NOT NULL, + `c22` int(11) NOT NULL, + `c23` int(11) NOT NULL, + `c24` int(11) NOT NULL, + `c25` int(11) NOT NULL, + `c26` int(11) NOT NULL, + `c27` int(11) NOT NULL, + `c28` int(11) NOT NULL, + `c29` int(11) NOT NULL, + `c30` int(11) NOT NULL, + `c31` int(11) NOT NULL, + `c32` int(11) NOT NULL, + `c33` int(11) NOT NULL, + `c34` int(11) NOT NULL, + `c35` int(11) NOT NULL, + `c36` int(11) NOT NULL, + `c37` int(11) NOT NULL, + `c38` int(11) NOT NULL, + `c39` int(11) NOT NULL, + `c40` int(11) NOT NULL, + `c41` int(11) NOT NULL, + `c42` int(11) NOT NULL, + `c43` int(11) NOT NULL, + `c44` int(11) NOT NULL, + `c45` int(11) NOT NULL, + `c46` int(11) NOT NULL, + `c47` int(11) NOT NULL, + `c48` int(11) NOT NULL, + `c49` int(11) NOT NULL, + `c50` int(11) NOT NULL, + `c51` int(11) NOT NULL, + `c52` int(11) NOT NULL, + `c53` int(11) NOT NULL, + `c54` int(11) NOT NULL, + `c55` int(11) NOT NULL, + `c56` int(11) NOT NULL, + `c57` int(11) NOT NULL, + `c58` int(11) NOT NULL, + `c59` int(11) NOT NULL, + `c60` int(11) NOT NULL, + `c61` int(11) NOT NULL, + `c62` int(11) NOT NULL, + `c63` int(11) NOT NULL, + `c64` int(11) NOT NULL, + `c65` int(11) NOT NULL, + PRIMARY KEY (`c1`,`c2`,`c3`,`c4`,`c5`,`c6`,`c7`,`c8`,`c9`,`c10`,`c11`,`c12`,`c13`,`c14`,`c15`,`c16`,`c17`,`c18`,`c19`,`c20`,`c21`,`c22`,`c23`,`c24`,`c25`,`c26`,`c27`,`c28`,`c29`,`c30`,`c31`,`c32`), + KEY `idx_ok` (`c33`,`c34`,`c35`,`c36`,`c37`,`c38`,`c39`,`c40`,`c41`,`c42`,`c43`,`c44`,`c45`,`c46`,`c47`,`c48`,`c49`,`c50`,`c51`,`c52`,`c53`,`c54`,`c55`,`c56`,`c57`,`c58`,`c59`,`c60`,`c61`,`c62`,`c63`,`c64`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +DROP TABLE t2; diff --git a/mysql-test/suite/innodb/r/index_ahi_option_debug,ahi.rdiff b/mysql-test/suite/innodb/r/index_ahi_option_debug,ahi.rdiff new file mode 100644 index 0000000000000..f7b3e6d88dae6 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option_debug,ahi.rdiff @@ -0,0 +1,46 @@ +--- index_ahi_option_debug.result ++++ index_ahi_option_debug,ahi.result +@@ -4,7 +4,7 @@ + # Test InnoDB index-level adaptive_hash_index options (debug) + # + SET @start_global_value = @@global.innodb_adaptive_hash_index; +-SET GLOBAL innodb_adaptive_hash_index=OFF; ++SET GLOBAL innodb_adaptive_hash_index=ON; + CREATE TABLE t1_ahi_default ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, +@@ -16,10 +16,10 @@ + SET GLOBAL innodb_monitor_enable = module_adaptive_hash; + CALL run_and_check_idx(240, 1, 't1_ahi_default'); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_default'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_default'); + result_msg + # No AHI used in SELECT (idx_3) +@@ -31,7 +31,7 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_no'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_no'); + result_msg + # No AHI used in SELECT (idx_3) +@@ -40,10 +40,10 @@ + RENAME TABLE t1 TO t1_ahi_yes; + CALL run_and_check_idx(240, 1, 't1_ahi_yes'); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_yes'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_yes'); + result_msg + # No AHI used in SELECT (idx_3) diff --git a/mysql-test/suite/innodb/r/index_ahi_option_debug,if_specified.rdiff b/mysql-test/suite/innodb/r/index_ahi_option_debug,if_specified.rdiff new file mode 100644 index 0000000000000..52f8015c404b7 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option_debug,if_specified.rdiff @@ -0,0 +1,42 @@ +--- index_ahi_option_debug.result ++++ index_ahi_option_debug,if_specified.result +@@ -4,7 +4,7 @@ + # Test InnoDB index-level adaptive_hash_index options (debug) + # + SET @start_global_value = @@global.innodb_adaptive_hash_index; +-SET GLOBAL innodb_adaptive_hash_index=OFF; ++SET GLOBAL innodb_adaptive_hash_index=if_specified; + CREATE TABLE t1_ahi_default ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, +@@ -19,7 +19,7 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_default'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_default'); + result_msg + # No AHI used in SELECT (idx_3) +@@ -31,7 +31,7 @@ + # No AHI used in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_no'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_no'); + result_msg + # No AHI used in SELECT (idx_3) +@@ -40,10 +40,10 @@ + RENAME TABLE t1 TO t1_ahi_yes; + CALL run_and_check_idx(240, 1, 't1_ahi_yes'); + result_msg +-# No AHI used in SELECT (idx_1) ++# Used AHI in SELECT (idx_1) + CALL run_and_check_idx(240, 2, 't1_ahi_yes'); + result_msg +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + CALL run_and_check_idx(240, 3, 't1_ahi_yes'); + result_msg + # No AHI used in SELECT (idx_3) diff --git a/mysql-test/suite/innodb/r/index_ahi_option_debug.result b/mysql-test/suite/innodb/r/index_ahi_option_debug.result new file mode 100644 index 0000000000000..101b2096bc9fd --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option_debug.result @@ -0,0 +1,72 @@ +SET @orig_debug=@@debug_dbug; +SET GLOBAL DEBUG_DBUG='+d,index_ahi_option_debug_check'; +# +# Test InnoDB index-level adaptive_hash_index options (debug) +# +SET @start_global_value = @@global.innodb_adaptive_hash_index; +SET GLOBAL innodb_adaptive_hash_index=OFF; +CREATE TABLE t1_ahi_default ( +id INT PRIMARY KEY, +col1 INT, col2 INT, col3 INT, +INDEX idx_1 (col1) adaptive_hash_index=DEFAULT complete_fields=0 bytes_from_incomplete_field=3 for_equal_hash_point_to_last_record=0, +INDEX idx_2 (col1, col2) adaptive_hash_index=YES complete_fields=1 bytes_from_incomplete_field=2 for_equal_hash_point_to_last_record=0, +INDEX idx_3 (col1, col2, col3) adaptive_hash_index=NO complete_fields=2 bytes_from_incomplete_field=2 for_equal_hash_point_to_last_record=1 +) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; +INSERT INTO t1_ahi_default SELECT seq, seq, seq << 8, seq << 16 FROM seq_0_to_255; +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +CALL run_and_check_idx(240, 1, 't1_ahi_default'); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(240, 2, 't1_ahi_default'); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(240, 3, 't1_ahi_default'); +result_msg +# No AHI used in SELECT (idx_3) +RENAME TABLE t1_ahi_default TO t1; +ALTER TABLE t1 adaptive_hash_index=OFF, ALGORITHM=INSTANT, LOCK=NONE; +RENAME TABLE t1 TO t1_ahi_no; +CALL run_and_check_idx(240, 1, 't1_ahi_no'); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(240, 2, 't1_ahi_no'); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(240, 3, 't1_ahi_no'); +result_msg +# No AHI used in SELECT (idx_3) +RENAME TABLE t1_ahi_no TO t1; +ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; +RENAME TABLE t1 TO t1_ahi_yes; +CALL run_and_check_idx(240, 1, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_1) +CALL run_and_check_idx(240, 2, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_2) +CALL run_and_check_idx(240, 3, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_3) +ALTER TABLE t1_ahi_yes ADD INDEX idx_4 (col2) complete_fields=2; +ALTER TABLE t1_ahi_yes ADD INDEX idx_5 (col3) bytes_from_incomplete_field=5; +ALTER TABLE t1_ahi_yes ADD INDEX idx_6 (col2, col1) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=0; +ALTER TABLE t1_ahi_yes ADD INDEX idx_7 (col3, col2) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=1; +CALL run_and_check_idx(240, 4, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_4) +CALL run_and_check_idx(240, 5, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_5) +CALL run_and_check_idx(240, 6, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_6) +CALL run_and_check_idx(240, 7, 't1_ahi_yes'); +result_msg +# No AHI used in SELECT (idx_7) +DROP PROCEDURE run_and_check_idx; +DROP TABLE t1_ahi_yes; +SET @@global.innodb_adaptive_hash_index = @start_global_value; +SET GLOBAL innodb_monitor_disable = module_adaptive_hash; +SET GLOBAL innodb_monitor_disable = default; +SET GLOBAL innodb_monitor_enable = default; +SET GLOBAL DEBUG_DBUG=@orig_debug; diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 40b6a61ae1abc..df47990cdf407 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -7,87 +7,145 @@ SET @start_global_value = @@global.innodb_adaptive_hash_index; if ($MTR_COMBINATION_AHI) { -SET GLOBAL innodb_adaptive_hash_index=ON; + SET GLOBAL innodb_adaptive_hash_index=ON; } if ($MTR_COMBINATION_NO_AHI) { -SET GLOBAL innodb_adaptive_hash_index=OFF; + SET GLOBAL innodb_adaptive_hash_index=OFF; } if ($MTR_COMBINATION_IF_SPECIFIED) { -SET GLOBAL innodb_adaptive_hash_index=if_specified; + SET GLOBAL innodb_adaptive_hash_index=if_specified; } ---echo # ---echo # Scenario 1: complete_fields parameter with point lookups ---echo # - CREATE TABLE t1 ( id INT PRIMARY KEY, col1 INT, col2 INT, col3 INT, - INDEX idx_1 (col1), - INDEX idx_2 (col1, col2), - INDEX idx_3 (col1, col2, col3) + INDEX idx_1 (col1) adaptive_hash_index=DEFAULT, + INDEX idx_2 (col1, col2) adaptive_hash_index=YES, + INDEX idx_3 (col1, col2, col3) adaptive_hash_index=NO ) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; -INSERT INTO t1 SELECT seq, seq % 100, seq % 10, seq % 5 FROM seq_1_to_50000; +INSERT INTO t1 SELECT seq, seq, seq << 8, seq << 16 FROM seq_0_to_255; + +# +# AHI usage in searches is verified by measuring an increase in +# the "count" of "adaptive_hash_searches" InnoDB metric. +# An alternative strategy would use "ANALYZE FORMAT=JSON" and +# check the "pages_accessed" entry: lower values w.r.t. the +# base case would imply that AHI was used for the search. +# Unfortunately, that is not really stable. +# Hopefully using "innodb_metrics" is more stable. +# + +--disable_query_log +--disable_result_log +DELIMITER $$; + +CREATE PROCEDURE run_and_check_idx(p_idx_num INT, p_rounds INT) +BEGIN + DECLARE start_val BIGINT; + DECLARE end_val BIGINT; + DECLARE i INT DEFAULT 0; + DECLARE dummy INT; + + SELECT count INTO start_val FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches'; + + WHILE i < p_rounds DO + IF p_idx_num = 1 THEN + SELECT COUNT(*) INTO dummy FROM t1 FORCE INDEX(idx_1) WHERE col1 >= 0 AND col1 < (1 << 8); + ELSEIF p_idx_num = 2 THEN + SELECT COUNT(*) INTO dummy FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0 AND col2 < (1 << 16); + ELSEIF p_idx_num = 3 THEN + SELECT COUNT(*) INTO dummy FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 = (50 << 8) AND col3 >= 0 AND col3 < (1 << 24); + END IF; + SET i = i + 1; + END WHILE; + + IF dummy = 0 THEN + SELECT '# WARNING: No rows selected' AS warning_msg; + END IF; + + SELECT count INTO end_val FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches'; + + IF end_val > start_val THEN + SELECT CONCAT('# Used AHI in SELECT (idx_', p_idx_num, ')') AS result_msg; + ELSE + SELECT CONCAT('# No AHI used in SELECT (idx_', p_idx_num, ')') AS result_msg; + END IF; + +END$$ + +DELIMITER ;$$ +--enable_result_log +--enable_query_log + +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +let $query_rounds= 240; + +eval CALL run_and_check_idx(1, $query_rounds); +eval CALL run_and_check_idx(2, $query_rounds); +eval CALL run_and_check_idx(3, $query_rounds); -INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), -(50003, 50, 3, 3), (50004, 50, 4, 4), (50005, 50, 5, 0), -(50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), -(50009, 50, 9, 4); +ALTER TABLE t1 adaptive_hash_index=OFF, ALGORITHM=INSTANT, LOCK=NONE; -let $i = 120; -while ($i) +eval CALL run_and_check_idx(1, $query_rounds); +eval CALL run_and_check_idx(2, $query_rounds); +eval CALL run_and_check_idx(3, $query_rounds); + +ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; + +eval CALL run_and_check_idx(1, $query_rounds); +eval CALL run_and_check_idx(2, $query_rounds); +eval CALL run_and_check_idx(3, $query_rounds); + +DROP PROCEDURE run_and_check_idx; +DROP TABLE t1; +SET @@global.innodb_adaptive_hash_index = @start_global_value; +SET GLOBAL innodb_monitor_disable = module_adaptive_hash; +--disable_warnings +SET GLOBAL innodb_monitor_disable = default; +SET GLOBAL innodb_monitor_enable = default; +--enable_warnings + +--echo # +--echo # Test maximum number of fields per (secondary) index is 64 (32+32) +--echo # + +# Build a list of 65 columns: c1 INT NOT NULL, ..., c65 INT NOT NULL +let $k= 1; +let $c1_c65=; +while ($k <= 65) { - --disable_query_log - --disable_result_log - SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; - SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0; - SELECT count(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 and col2 >= 0 and col3 = 0; - --enable_result_log - --enable_query_log - dec $i; + if ($k == 1) { let $c1_c65= c$k INT NOT NULL; } + if ($k > 1) { let $c1_c65= $c1_c65, c$k INT NOT NULL; } + inc $k; } +eval CREATE TABLE t2 (dummy INT NOT NULL, $c1_c65, PRIMARY KEY (dummy)) ENGINE=InnoDB; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; -ALTER TABLE t1 adaptive_hash_index=OFF, FORCE; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; -ALTER TABLE t1 adaptive_hash_index='ON'; -let $i = 120; -while ($i) +# Build a list of 32 columns: c1, ..., c32 +let $k= 1; +let $c1_c32=; +while ($k <= 32) { - --disable_query_log - --disable_result_log - SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; - SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0; - SELECT count(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 and col2 >= 0 and col3 = 0; - --enable_result_log - --enable_query_log - dec $i; -}--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; ---replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ -ANALYZE FORMAT=JSON -SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; + if ($k == 1) { let $c1_c32= c$k; } + if ($k > 1) { let $c1_c32= $c1_c32, c$k; } + inc $k; +} +--error ER_TOO_MANY_KEY_PARTS +eval ALTER TABLE t2 DROP PRIMARY KEY, ADD PRIMARY KEY ($c1_c32, c33); +eval ALTER TABLE t2 DROP PRIMARY KEY, ADD PRIMARY KEY ($c1_c32); -DROP TABLE t1; -SET @@global.innodb_adaptive_hash_index = @start_global_value; +# Build a list of 32 columns: c33, ..., c64 +let $k= 33; +let $c33_c64=; +while ($k <= 64) +{ + if ($k == 33) { let $c33_c64= c$k; } + if ($k > 33) { let $c33_c64= $c33_c64, c$k; } + inc $k; +} +--error ER_TOO_MANY_KEY_PARTS +eval CREATE INDEX idx_fail ON t2 ($c33_c64, c65); +eval CREATE INDEX idx_ok ON t2 ($c33_c64); + +SHOW CREATE TABLE t2; +DROP TABLE t2; diff --git a/mysql-test/suite/innodb/t/index_ahi_option_debug.combinations b/mysql-test/suite/innodb/t/index_ahi_option_debug.combinations new file mode 100644 index 0000000000000..f9099f637b0fa --- /dev/null +++ b/mysql-test/suite/innodb/t/index_ahi_option_debug.combinations @@ -0,0 +1,3 @@ +[ahi] +[no_ahi] +[if_specified] diff --git a/mysql-test/suite/innodb/t/index_ahi_option_debug.test b/mysql-test/suite/innodb/t/index_ahi_option_debug.test new file mode 100644 index 0000000000000..68bccf21c0295 --- /dev/null +++ b/mysql-test/suite/innodb/t/index_ahi_option_debug.test @@ -0,0 +1,152 @@ +--source include/have_innodb.inc +--source include/have_sequence.inc +--source include/have_debug.inc + +# See index_ahi_option.test for info on AHI usage detection strategy + +SET @orig_debug=@@debug_dbug; +SET GLOBAL DEBUG_DBUG='+d,index_ahi_option_debug_check'; + +--echo # +--echo # Test InnoDB index-level adaptive_hash_index options (debug) +--echo # + +SET @start_global_value = @@global.innodb_adaptive_hash_index; +if ($MTR_COMBINATION_AHI) { + SET GLOBAL innodb_adaptive_hash_index=ON; +} +if ($MTR_COMBINATION_NO_AHI) { + SET GLOBAL innodb_adaptive_hash_index=OFF; +} +if ($MTR_COMBINATION_IF_SPECIFIED) { + SET GLOBAL innodb_adaptive_hash_index=if_specified; +} + +CREATE TABLE t1_ahi_default ( + id INT PRIMARY KEY, + col1 INT, col2 INT, col3 INT, + INDEX idx_1 (col1) adaptive_hash_index=DEFAULT complete_fields=0 bytes_from_incomplete_field=3 for_equal_hash_point_to_last_record=0, + INDEX idx_2 (col1, col2) adaptive_hash_index=YES complete_fields=1 bytes_from_incomplete_field=2 for_equal_hash_point_to_last_record=0, + INDEX idx_3 (col1, col2, col3) adaptive_hash_index=NO complete_fields=2 bytes_from_incomplete_field=2 for_equal_hash_point_to_last_record=1 +) ENGINE=InnoDB STATS_PERSISTENT=0 adaptive_hash_index=DEFAULT; + +INSERT INTO t1_ahi_default SELECT seq, seq, seq << 8, seq << 16 FROM seq_0_to_255; + +--disable_query_log +--disable_result_log +DELIMITER $$; + +CREATE PROCEDURE run_and_check_idx(p_rounds INT, p_idx_num INT, p_table_name VARCHAR(64)) +BEGIN + DECLARE start_val BIGINT; + DECLARE end_val BIGINT; + DECLARE i INT DEFAULT 0; + DECLARE dummy INT; + + SELECT count INTO start_val FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches'; + + IF p_table_name = 't1_ahi_default' THEN + WHILE i < p_rounds DO + IF p_idx_num = 1 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_default FORCE INDEX(idx_1) WHERE col1 >= 0 AND col1 < (1 << 8); + ELSEIF p_idx_num = 2 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_default FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0 AND col2 < (1 << 16); + ELSEIF p_idx_num = 3 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_default FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 = (50 << 8) AND col3 >= 0 AND col3 < (1 << 24); + END IF; + SET i = i + 1; + END WHILE; + ELSEIF p_table_name = 't1_ahi_no' THEN + WHILE i < p_rounds DO + IF p_idx_num = 1 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_no FORCE INDEX(idx_1) WHERE col1 >= 0 AND col1 < (1 << 8); + ELSEIF p_idx_num = 2 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_no FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0 AND col2 < (1 << 16); + ELSEIF p_idx_num = 3 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_no FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 = (50 << 8) AND col3 >= 0 AND col3 < (1 << 24); + END IF; + SET i = i + 1; + END WHILE; + ELSEIF p_table_name = 't1_ahi_yes' THEN + WHILE i < p_rounds DO + IF p_idx_num = 1 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_1) WHERE col1 >= 0 AND col1 < (1 << 8); + ELSEIF p_idx_num = 2 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 >= 0 AND col2 < (1 << 16); + ELSEIF p_idx_num = 3 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 = (50 << 8) AND col3 >= 0 AND col3 < (1 << 24); + ELSEIF p_idx_num = 4 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_4) WHERE col2 = (50 << 8); + ELSEIF p_idx_num = 5 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_5) WHERE col3 = (50 << 16); + ELSEIF p_idx_num = 6 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_6) WHERE col2 = (50 << 8) AND col1 = 50; + ELSEIF p_idx_num = 7 THEN + SELECT COUNT(*) INTO dummy FROM t1_ahi_yes FORCE INDEX(idx_7) WHERE col3 = (50 << 16) AND col2 = (50 << 8); + END IF; + SET i = i + 1; + END WHILE; + END IF; + + IF dummy = 0 THEN + SELECT '# WARNING: No rows selected' AS warning_msg; + END IF; + + SELECT count INTO end_val FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches'; + + IF end_val > start_val THEN + SELECT CONCAT('# Used AHI in SELECT (idx_', p_idx_num, ')') AS result_msg; + ELSE + SELECT CONCAT('# No AHI used in SELECT (idx_', p_idx_num, ')') AS result_msg; + END IF; + +END$$ + +DELIMITER ;$$ +--enable_result_log +--enable_query_log + +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +let $query_rounds= 240; + +eval CALL run_and_check_idx($query_rounds, 1, 't1_ahi_default'); +eval CALL run_and_check_idx($query_rounds, 2, 't1_ahi_default'); +eval CALL run_and_check_idx($query_rounds, 3, 't1_ahi_default'); + +RENAME TABLE t1_ahi_default TO t1; +ALTER TABLE t1 adaptive_hash_index=OFF, ALGORITHM=INSTANT, LOCK=NONE; +RENAME TABLE t1 TO t1_ahi_no; + +eval CALL run_and_check_idx($query_rounds, 1, 't1_ahi_no'); +eval CALL run_and_check_idx($query_rounds, 2, 't1_ahi_no'); +eval CALL run_and_check_idx($query_rounds, 3, 't1_ahi_no'); + +RENAME TABLE t1_ahi_no TO t1; +ALTER TABLE t1 adaptive_hash_index='ON', ALGORITHM=INSTANT, LOCK=NONE; +RENAME TABLE t1 TO t1_ahi_yes; + +eval CALL run_and_check_idx($query_rounds, 1, 't1_ahi_yes'); +eval CALL run_and_check_idx($query_rounds, 2, 't1_ahi_yes'); +eval CALL run_and_check_idx($query_rounds, 3, 't1_ahi_yes'); + +# Test "illegal" options +ALTER TABLE t1_ahi_yes ADD INDEX idx_4 (col2) complete_fields=2; +ALTER TABLE t1_ahi_yes ADD INDEX idx_5 (col3) bytes_from_incomplete_field=5; +ALTER TABLE t1_ahi_yes ADD INDEX idx_6 (col2, col1) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=0; +ALTER TABLE t1_ahi_yes ADD INDEX idx_7 (col3, col2) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=1; + +eval CALL run_and_check_idx($query_rounds, 4, 't1_ahi_yes'); +eval CALL run_and_check_idx($query_rounds, 5, 't1_ahi_yes'); +eval CALL run_and_check_idx($query_rounds, 6, 't1_ahi_yes'); +eval CALL run_and_check_idx($query_rounds, 7, 't1_ahi_yes'); + +DROP PROCEDURE run_and_check_idx; +DROP TABLE t1_ahi_yes; +SET @@global.innodb_adaptive_hash_index = @start_global_value; +SET GLOBAL innodb_monitor_disable = module_adaptive_hash; +--disable_warnings +SET GLOBAL innodb_monitor_disable = default; +SET GLOBAL innodb_monitor_enable = default; +--enable_warnings + +SET GLOBAL DEBUG_DBUG=@orig_debug; diff --git a/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result b/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result index 46c8e524b0622..69ddfa0d11694 100644 --- a/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result +++ b/mysql-test/suite/sys_vars/r/innodb_adaptive_hash_index_basic.result @@ -194,7 +194,7 @@ t1 CREATE TABLE `t1` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index (b) adaptive_hash_index='on') engine=innodb, adaptive_hash_index='off'; drop table t1; -CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_fields=1 for_equal_hash_point_to_last_record=1) engine=innodb; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_field=1 for_equal_hash_point_to_last_record=no) engine=innodb; show create table t1; Table Create Table t1 CREATE TABLE `t1` ( @@ -202,9 +202,9 @@ t1 CREATE TABLE `t1` ( `b` varchar(100) DEFAULT NULL, `c` int(11) DEFAULT NULL, PRIMARY KEY (`a`), - KEY `c` (`c`,`b`) `adaptive_hash_index`=yes `complete_fields`=1 `bytes_from_incomplete_fields`=1 `for_equal_hash_point_to_last_record`=1 + KEY `c` (`c`,`b`) `adaptive_hash_index`=yes `complete_fields`=1 `bytes_from_incomplete_field`=1 `for_equal_hash_point_to_last_record`=no ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci -alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_fields=default; +alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_field=default for_equal_hash_point_to_last_record=default; show create table t1; Table Create Table t1 CREATE TABLE `t1` ( @@ -214,6 +214,16 @@ t1 CREATE TABLE `t1` ( PRIMARY KEY (`a`), KEY `c` (`c`,`b`) `complete_fields`=2 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +alter table t1 drop index c, add index (c,b) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=yes; +show create table t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` int(11) NOT NULL, + `b` varchar(100) DEFAULT NULL, + `c` int(11) DEFAULT NULL, + PRIMARY KEY (`a`), + KEY `c` (`c`,`b`) `complete_fields`=0 `bytes_from_incomplete_field`=0 `for_equal_hash_point_to_last_record`=yes +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci drop table t1; CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), index(b)) engine=innodb, adaptive_hash_index='on' ENGINE=innodb PARTITION BY KEY (a) PARTITIONS 2; show create table t1; diff --git a/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test b/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test index a3cbddb343747..88c7965c2ac67 100644 --- a/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test +++ b/mysql-test/suite/sys_vars/t/innodb_adaptive_hash_index_basic.test @@ -102,9 +102,11 @@ drop table t1; # Test extra index options # -CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_fields=1 for_equal_hash_point_to_last_record=1) engine=innodb; +CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b) adaptive_hash_index=yes complete_fields=1 bytes_from_incomplete_field=1 for_equal_hash_point_to_last_record=no) engine=innodb; show create table t1; -alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_fields=default; +alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_field=default for_equal_hash_point_to_last_record=default; +show create table t1; +alter table t1 drop index c, add index (c,b) complete_fields=0 bytes_from_incomplete_field=0 for_equal_hash_point_to_last_record=yes; show create table t1; drop table t1; diff --git a/sql/handler.cc b/sql/handler.cc index 72f5fb4e27331..acf8b4138585b 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -68,8 +68,8 @@ #include "wsrep_var.h" /* wsrep_hton_check() */ #endif /* WITH_WSREP */ -/* Note that DEFAULT is handled automatically */ -const char *table_hint_options= "YES,NO"; +/* DEFAULT (0), YES (1), NO (2) */ +const char *table_hint_options= "DEFAULT,YES,NO"; /** @def MYSQL_TABLE_LOCK_WAIT diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc index 4dbb4bbcdb004..ff4a042e7e959 100644 --- a/storage/innobase/btr/btr0sea.cc +++ b/storage/innobase/btr/btr0sea.cc @@ -464,7 +464,7 @@ ATTRIBUTE_COLD void btr_sea::resize(uint n_cells) noexcept ut_ad(!parts[0].table.array); if (was_enabled) - enabled= alloc(n_cells); + enabled= alloc(n_cells) ? was_enabled : 0; if (!was_enabled || enabled) this->n_cells= n_cells; @@ -601,7 +601,7 @@ static void btr_search_update_hash_ref(const btr_cur_t &cursor, { ut_ad(block_index == index); ut_ad(btr_search.get_enabled()); - ut_ad(index->search_info.ahi_enabled); + ut_ad(index->search_info.get_ahi_enabled()); uint32_t bytes_fields{block->ahi_left_bytes_fields}; if (bytes_fields != left_bytes_fields) goto skip; @@ -686,9 +686,22 @@ static uint32_t btr_search_info_update_hash(const btr_cur_t &cursor) noexcept uint8_t n_hash_potential= info.n_hash_potential; uint32_t ret; + uint32_t ahi_enabled_fixed_mask, fixed, mask; + ut_d(ahi_enabled_fixed_mask= 0); + ut_d(fixed= 0); + ut_d(mask= 0); + ut_d(bool fixed_mask_set= false); + if (!n_hash_potential) { - info.left_bytes_fields= left_bytes_fields= buf_block_t::LEFT_SIDE | 1; + left_bytes_fields= buf_block_t::LEFT_SIDE | 1; + /* Override with fixed values */ + ut_ad(!fixed_mask_set); + ahi_enabled_fixed_mask= info.get_ahi_enabled_fixed_mask(); + info.get_ahi_fixed_mask(ahi_enabled_fixed_mask, fixed, mask); + ut_d(fixed_mask_set= true); + left_bytes_fields= (left_bytes_fields & ~mask) | (fixed & mask); + info.left_bytes_fields= left_bytes_fields; info.hash_analysis_reset(); increment_potential: if (n_hash_potential < BTR_SEARCH_BUILD_LIMIT) @@ -748,6 +761,12 @@ static uint32_t btr_search_info_update_hash(const btr_cur_t &cursor) noexcept left_bytes_fields|= uint32_t(cursor.up_bytes + 1) << 16; } } + /* Override with fixed values */ + ut_ad(!fixed_mask_set); + ahi_enabled_fixed_mask= info.get_ahi_enabled_fixed_mask(); + info.get_ahi_fixed_mask(ahi_enabled_fixed_mask, fixed, mask); + ut_d(fixed_mask_set= true); + left_bytes_fields= (left_bytes_fields & ~mask) | (fixed & mask); /* We have to set a new recommendation; skip the hash analysis for a while to avoid unnecessary CPU time usage when there is no chance for success */ @@ -803,6 +822,167 @@ static uint32_t btr_search_info_update_hash(const btr_cur_t &cursor) noexcept else if (cursor.flag == BTR_CUR_HASH_FAIL) btr_search_update_hash_ref(cursor, block, left_bytes_fields); + ut_ad(!ret || ((ret & mask) == (fixed & mask))); + DBUG_EXECUTE_IF("index_ahi_option_debug_check", + { + /* Since enabled comes from the same atomic variable, it's coherent */ + const uint8_t enabled= info.get_ahi_enabled(ahi_enabled_fixed_mask); + const char* idx_name= index->name; + const char* table_name= index->table->name.m_name; + if (!fixed_mask_set) {} + else if (!strcmp(idx_name, "idx_1")) + { + /* + INDEX idx_1 (col1) + adaptive_hash_index=DEFAULT + complete_fields=0 + bytes_from_incomplete_field=3 + for_equal_hash_point_to_last_record=0 + */ + if (!strcmp(table_name, "test/t1_ahi_no")) + { + ut_ad(enabled == 0); + ut_ad(false); + } + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(enabled == 1); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0xFFFFFFFF); + ut_ad((fixed & mask) == ((3 << 16) | buf_block_t::LEFT_SIDE)); + } + else if (!strcmp(idx_name, "idx_2")) + { + /* + INDEX idx_2 (col1, col2) + adaptive_hash_index=YES + complete_fields=1 + bytes_from_incomplete_field=2 + for_equal_hash_point_to_last_record=0 + */ + ut_ad(enabled == 2); + if (!strcmp(table_name, "test/t1_ahi_no")) {} + else if (!strcmp(table_name, "test/t1_ahi_default")) {} + else if (!strcmp(table_name, "test/t1_ahi_yes")) {} + else + goto not_under_test; + ut_ad(mask == 0xFFFFFFFF); + ut_ad((fixed & mask) == (1 | (2 << 16) | buf_block_t::LEFT_SIDE)); + } + else if (!strcmp(idx_name, "idx_3")) + { + /* + INDEX idx_3 (col1, col2, col3) + adaptive_hash_index=NO + complete_fields=2 + bytes_from_incomplete_field=2 + for_equal_hash_point_to_last_record=1 + */ + ut_ad(enabled == 0); + if (!strcmp(table_name, "test/t1_ahi_no")) {} + else if (!strcmp(table_name, "test/t1_ahi_default")) {} + else if (!strcmp(table_name, "test/t1_ahi_yes")) {} + else + goto not_under_test; + ut_ad(mask == 0xFFFFFFFF); + ut_ad((fixed & mask) == (2 | (2 << 16) | buf_block_t::LEFT_SIDE)); + } + else if (!strcmp(idx_name, "PRIMARY")) + { + /* id INT PRIMARY KEY */ + if (!strcmp(table_name, "test/t1_ahi_no")) + { + ut_ad(enabled == 0); + ut_ad(false); + } + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(enabled == 1); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0); + /* Since mask is 0, fixed can be anything */ + } + else if (!strcmp(idx_name, "idx_4")) + { + /* INDEX idx_4 (col2) complete_fields=2 */ + if (!strcmp(table_name, "test/t1_ahi_no")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0x0000FFFF); + ut_ad((fixed & mask) == 2); + } + else if (!strcmp(idx_name, "idx_5")) + { + /* INDEX idx_5 (col3) bytes_from_incomplete_field=5 */ + if (!strcmp(table_name, "test/t1_ahi_no")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0x7FFF0000); + ut_ad((fixed & mask) == (5 << 16)); + } + else if (!strcmp(idx_name, "idx_6")) + { + /* + INDEX idx_6 (col2, col1) + complete_fields=0 + bytes_from_incomplete_field=0 + for_equal_hash_point_to_last_record=0 + */ + if (!strcmp(table_name, "test/t1_ahi_no")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0xFFFFFFFF); + ut_ad((fixed & mask) == buf_block_t::LEFT_SIDE); + } + else if (!strcmp(idx_name, "idx_7")) + { + /* + INDEX idx_7 (col3, col2) + complete_fields=0 + bytes_from_incomplete_field=0 + for_equal_hash_point_to_last_record=1 + */ + if (!strcmp(table_name, "test/t1_ahi_no")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_default")) + ut_ad(false); + else if (!strcmp(table_name, "test/t1_ahi_yes")) + ut_ad(enabled == 2); + else + goto not_under_test; + ut_ad(mask == 0xFFFFFFFF); + ut_ad((fixed & mask) == 0); + } + else + { +not_under_test: + /* AHI access to mysql/innodb_table_stats or mysql/innodb_index_stats + seems possible even with STATS_PERSISTENT=0, due to usage of + RENAME TABLE. Just check that no attributes were set there. */ + ut_ad(enabled == 1); + ut_ad(mask == 0); + } + }); + return ret; } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index a369974870eb6..5f70bf00bfa2d 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -675,20 +675,38 @@ ha_create_table_option innodb_table_option_list[]= /* With this option the user defines the key identifier using for the encryption */ HA_TOPTION_SYSVAR("ENCRYPTION_KEY_ID", encryption_key_id, default_encryption_key_id), HA_TOPTION_ENUM("ADAPTIVE_HASH_INDEX", adaptive_hash_index, - table_hint_options, 0), + table_hint_options, TABLE_HINT_DEFAULT), HA_TOPTION_END }; +constexpr int max_bytes_from_incomplete_field= REDUNDANT_REC_MAX_DATA_SIZE; +constexpr int max_complete_fields= 64; /* 32 PK + 32 secondary index fields */ +static_assert( + max_complete_fields <= dict_index_t::ahi::max_fields, + "max_complete_fields too large for dict_index_t::ahi" +); +static_assert( + max_bytes_from_incomplete_field <= dict_index_t::ahi::max_bytes, + "max_bytes_from_incomplete_field too large for dict_index_t::ahi" +); +/* Max value in table_hint_options is TABLE_HINT_NO */ +static_assert(TABLE_HINT_NO <= dict_index_t::ahi::max_enabled, + "TABLE_HINT enum values too large for dict_index_t::ahi" +); + ha_create_table_option innodb_index_option_list[]= { HA_IOPTION_ENUM("ADAPTIVE_HASH_INDEX", adaptive_hash_index, - table_hint_options, 0), - HA_IOPTION_NUMBER("COMPLETE_FIELDS", complete_fields, 0, 1, 32, 1), - HA_IOPTION_NUMBER("BYTES_FROM_INCOMPLETE_FIELDS", - bytes_from_incomplete_fields, 0, 1, 8192, 1), - HA_IOPTION_BOOL("FOR_EQUAL_HASH_POINT_TO_LAST_RECORD", - for_equal_hash_point_to_last_record, 0), + table_hint_options, TABLE_HINT_DEFAULT), + HA_IOPTION_NUMBER("COMPLETE_FIELDS", complete_fields, ULONGLONG_MAX, 0, + max_complete_fields, 1), + HA_IOPTION_NUMBER("BYTES_FROM_INCOMPLETE_FIELD", + bytes_from_incomplete_field, ULONGLONG_MAX, 0, + max_bytes_from_incomplete_field, 1), + HA_IOPTION_ENUM("FOR_EQUAL_HASH_POINT_TO_LAST_RECORD", + for_equal_hash_point_to_last_record, table_hint_options, + TABLE_HINT_DEFAULT), HA_IOPTION_END }; @@ -2882,37 +2900,64 @@ static void innodb_ahi_enable(dict_table_t *innodb_table, const ha_table_option_struct *option_struct, const TABLE *table) { + /* + Mapping from adaptive_hash_index to get_ahi_enabled(): + TABLE_HINT_NO (2) -> 0 (force disabled) + TABLE_HINT_DEFAULT (0) -> 1 (default, use global setting) + TABLE_HINT_YES (1) -> 2 (prefer enabled, if not globally disabled) + + Index preference, if set to YES|NO, will override table preference. + */ const uint8_t table_ahi= option_struct ? uint8_t((option_struct->adaptive_hash_index + 1) % 3) : uint8_t{1}; - innodb_table->ahi_enabled= table_ahi; - if (table_ahi) + /* In case there is no PRIMARY KEY or UNIQUE INDEX on NOT NULL + columns, there will be GEN_CLUST_INDEX(DB_ROW_ID). Default to the + table option for it. If a PRIMARY KEY is defined, this default + value may be updated in the loop below. */ + auto* def_search_info= + &UT_LIST_GET_FIRST(innodb_table->indexes)->search_info; + for (auto i= table->s->keys; i--; ) { - /* In case there is no PRIMARY KEY or UNIQUE INDEX on NOT NULL - columns, there will be GEN_CLUST_INDEX(DB_ROW_ID). Default to the - table option for it. If a PRIMARY KEY is defined, this default - value may be updated in the loop below. */ - UT_LIST_GET_FIRST(innodb_table->indexes)->search_info.ahi_enabled= - table_ahi; - for (auto i= table->s->keys; i--; ) - { - uint8_t ahi= table_ahi; - const KEY &key= table->key_info[i]; - switch (key.option_struct->adaptive_hash_index) { - default: - break; - case 1: - ahi= 2; - /* fall through */ - case 0: - dict_index_t *index= - dict_table_get_index_on_name(innodb_table, key.name.str); - if (index) - index->search_info.ahi_enabled= ahi; - } - } + const KEY &key= table->key_info[i]; + dict_index_t *index= + dict_table_get_index_on_name(innodb_table, key.name.str); + if (!index) + continue; + const uint8_t index_ahi= + uint8_t((key.option_struct->adaptive_hash_index + 1) % 3); + /* Use index preference if set, otherwise use table preference */ + const uint8_t mask= uint8_t(0 - (index_ahi & 1)); + /* mask == 0xFF if index_ahi == 1, indicates index preference unset */ + const uint8_t ahi= uint8_t((table_ahi & mask) | (index_ahi & ~mask)); + const uint64_t fields= key.option_struct->complete_fields; + const uint64_t bytes= key.option_struct->bytes_from_incomplete_field; + const uint32_t right= + key.option_struct->for_equal_hash_point_to_last_record; + ut_ad(ahi <= 2); + ut_ad(fields == ULONGLONG_MAX || fields <= max_complete_fields); + ut_ad(bytes == ULONGLONG_MAX || bytes <= max_bytes_from_incomplete_field); + ut_ad(right <= TABLE_HINT_NO); + static_assert((uint64_t(1) << 63) > max_complete_fields, + "Cannot use highest bit as unused flag"); + static_assert((uint64_t(1) << 63) > max_bytes_from_incomplete_field, + "Cannot use highest bit as unused flag"); + index->search_info.set_ahi_enabled_fixed_mask( + ahi, + (~fields >> 63) & 1, /* fields != ULONGLONG_MAX */ + (~bytes >> 63) & 1, /* bytes != ULONGLONG_MAX */ + (right | (right >> 1)) & 1, /* right != TABLE_HINT_DEFAULT */ + uint8_t(fields), + uint16_t(bytes), + (right >> 1) & 1 /* right == TABLE_HINT_NO */ + ); + if (def_search_info == &index->search_info) + def_search_info= nullptr; } + if (def_search_info) + def_search_info->set_ahi_enabled_fixed_mask( + table_ahi, false, false, false, 0, 0, false); } #endif diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 781dec388e9f3..bfb1cf83c0668 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -53,8 +53,10 @@ struct ha_index_option_struct { uint adaptive_hash_index; /*!< DEFAULT, ON, OFF */ ulonglong complete_fields; - ulonglong bytes_from_incomplete_fields; - my_bool for_equal_hash_point_to_last_record; + ulonglong bytes_from_incomplete_field; + /** DEFAULT (0), YES (1), NO (2) + DEFAULT means no fixed recommendation for this AHI parameter. */ + uint for_equal_hash_point_to_last_record; }; /** The class defining a handle to an Innodb table */ diff --git a/storage/innobase/include/btr0sea.h b/storage/innobase/include/btr0sea.h index 0c33cbda240ab..f3a4b1a9d4d21 100644 --- a/storage/innobase/include/btr0sea.h +++ b/storage/innobase/include/btr0sea.h @@ -130,13 +130,13 @@ struct btr_sea /* Index is enabled if global ahi is enabled and index can be enabled. If enabled is set to 2 (IF_SPECIFIED), only enable indexes declared - with ahi enabled on (ahi_enabled == 2). - We don't have to check if index->search_info.ahi_enabled != 0 - as the test enabled <= ahi_enabled will not be true in this case. + with ahi enabled on (get_ahi_enabled() == 2). + We don't have to check if index->search_info.get_ahi_enabled() != 0 + as the test enabled <= get_ahi_enabled() will not be true in this case. */ const ulong enabled{get_enabled()}; return (unlikely(enabled != 0) && - enabled <= index->search_info.ahi_enabled); + enabled <= index->search_info.get_ahi_enabled()); } /* Same as above, but if index == 0 return 1. This to handle the case @@ -146,7 +146,7 @@ struct btr_sea { const ulong enabled{get_enabled()}; return (unlikely(enabled != 0) && - (!index || enabled <= index->search_info.ahi_enabled)); + (!index || enabled <= index->search_info.get_ahi_enabled())); } inline ulong get_enabled() const noexcept { return enabled; } diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index ed31d73259569..307913c0c38d3 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1070,6 +1070,65 @@ struct dict_index_t { static constexpr uint8_t HASH_ANALYSIS= 16; /** the number of calls to hash_analysis_useful() */ Atomic_relaxed hash_analysis{0}; + + /** Compound variable which contains: + - bits [0, 1]: adaptive_hash_index enabled/disabled preference (2 bits) + - 0b00 (0): force disabled + - 0b01 (1): no preference (set by default, use global setting) + - 0b10 (2): prefer enabled (if not globally disabled) + - 0b11 (3): reserved + - bits [2, 23]: fixed parameters in recommendations (22 bits) + - bits [2, 8]: complete_fields (fields, 7 bits) + - bits [9, 22]: bytes_from_incomplete_field (bytes, 14 bits) + - bit 23: ~for_equal_hash_point_to_last_record (left, 1 bit) + - bits [24, 26]: mask which indicates valid recommendations bits (3 bits) + - bit 24: fields valid + - bit 25: bytes valid + - bit 26: left valid + - bits [27, 31]: spare (5 bits) + @see buf_block_t::left_bytes_fields */ + Atomic_relaxed ahi_enabled_fixed_mask{0}; + + /* Size in bits for each field of ahi_enabled_fixed_mask */ + static constexpr uint32_t enabled_bits= 2; /* First */ + static constexpr uint32_t fields_bits= 7; + static constexpr uint32_t bytes_bits= 14; + static constexpr uint32_t left_bits= 1; + static constexpr uint32_t is_fields_set_bits= 1; + static constexpr uint32_t is_bytes_set_bits= 1; + static constexpr uint32_t is_left_set_bits= 1; /* Last */ + + /* Shifts for each field of ahi_enabled_fixed_mask (packed) */ + static constexpr uint32_t enabled_shift= 0; /* First */ + static constexpr uint32_t fields_shift= enabled_shift + enabled_bits; + static constexpr uint32_t bytes_shift= fields_shift + fields_bits; + static constexpr uint32_t left_shift= bytes_shift + bytes_bits; + static constexpr uint32_t is_fields_set_shift= + left_shift + left_bits; + static constexpr uint32_t is_bytes_set_shift= + is_fields_set_shift + is_fields_set_bits; + static constexpr uint32_t is_left_set_shift= + is_bytes_set_shift + is_bytes_set_bits; /* Last */ + + static_assert(is_left_set_shift + is_left_set_bits <= + sizeof(ahi_enabled_fixed_mask) * 8, + "ahi_enabled_fixed_mask does not fit in 32 bits"); + + /* Masks for each field of ahi_enabled_fixed_mask */ + static constexpr uint32_t enabled_mask= (1U << enabled_bits) - 1; + static constexpr uint32_t fields_mask= (1U << fields_bits) - 1; + static constexpr uint32_t bytes_mask= (1U << bytes_bits) - 1; + static constexpr uint32_t left_mask= (1U << left_bits) - 1; + static constexpr uint32_t is_fields_set_mask= + (1U << is_fields_set_bits) - 1; + static constexpr uint32_t is_bytes_set_mask= + (1U << is_bytes_set_bits) - 1; + static constexpr uint32_t is_left_set_mask= + (1U << is_left_set_bits) - 1; + + static_assert(left_mask == true && false < true, + "left_mask is unadequate for a bool"); + public: bool hash_analysis_useful() noexcept { @@ -1089,17 +1148,90 @@ struct dict_index_t { search, and the calculation itself is not always accurate! */ Atomic_relaxed last_hash_succ{false}; - /** If adaptive hash indexes are enabled for this index - Values 0 (not enabled), 1 (enabled if ahi is globally enabled), - 2 (adaptive_hash_index=on was set for the index) - */ - Atomic_relaxed ahi_enabled{0}; - /** recommended parameters; @see buf_block_t::left_bytes_fields */ Atomic_relaxed left_bytes_fields{buf_block_t::LEFT_SIDE | 1}; /** number of buf_block_t::index pointers to this index */ Atomic_counter ref_count{0}; + /* Maximum values for the ahi_enabled_fixed_mask fields */ + static constexpr uint8_t max_enabled= enabled_mask - 1; + static constexpr uint8_t max_fields= fields_mask; + static constexpr uint16_t max_bytes= bytes_mask; + + inline void set_ahi_enabled_fixed_mask( + const uint8_t enabled, + const bool is_fields_set, + const bool is_bytes_set, + const bool is_left_set, + const uint8_t fields, + const uint16_t bytes, + const bool left) noexcept + { + ut_ad(enabled <= max_enabled); + ut_ad(!is_fields_set || fields <= max_fields); + ut_ad(!is_bytes_set || bytes <= max_bytes); + /* Maybe overzealous, just to highlight the arithmetic used below */ + static_assert(uint32_t(true) == 1); + static_assert(uint32_t(false) == 0); + const uint32_t val= (uint32_t(enabled & enabled_mask) << enabled_shift) | + (uint32_t(fields & fields_mask) << fields_shift) | + (uint32_t(is_fields_set) << is_fields_set_shift) | + (uint32_t(bytes & bytes_mask) << bytes_shift) | + (uint32_t(is_bytes_set) << is_bytes_set_shift) | + (uint32_t(left) << left_shift) | + (uint32_t(is_left_set) << is_left_set_shift); + ahi_enabled_fixed_mask.store(val); + } + + inline uint8_t get_ahi_enabled(const uint32_t val) const noexcept + { + const uint8_t enabled= (val >> enabled_shift) & enabled_mask; + ut_ad(enabled <= max_enabled); + return enabled; + } + + inline uint8_t get_ahi_enabled() const noexcept + { + return get_ahi_enabled(ahi_enabled_fixed_mask.load()); + } + + inline uint32_t get_ahi_enabled_fixed_mask() const noexcept + { + return ahi_enabled_fixed_mask.load(); + } + + inline void get_ahi_fixed_mask( + const uint32_t val /* copy of ahi_enabled_fixed_mask */, + uint32_t& fixed, + uint32_t& mask) const noexcept + { + const uint32_t left= (val >> left_shift) & left_mask; + const uint32_t bytes= (val >> bytes_shift) & bytes_mask; + const uint32_t fields= (val >> fields_shift) & fields_mask; + const uint32_t is_left_set= + (val >> is_left_set_shift) & is_left_set_mask; + const uint32_t is_bytes_set= + (val >> is_bytes_set_shift) & is_bytes_set_mask; + const uint32_t is_fields_set= + (val >> is_fields_set_shift) & is_fields_set_mask; + ut_ad(!is_fields_set || fields <= fields_mask); + ut_ad(!is_bytes_set || bytes <= bytes_mask); + ut_ad(!is_left_set || left <= left_mask); + /* See buf_block_t::left_bytes_fields */ + static_assert(left_mask < (1U << 1)); + static_assert(bytes_mask < (1U << 15)); + static_assert(fields_mask < (1U << 16)); + fixed= fields | (bytes << 16) | (left << 31); + /* Just to highlight arithmetic used below */ + static_assert(is_fields_set_mask == 1); + static_assert(is_bytes_set_mask == 1); + static_assert(is_left_set_mask == 1); + static_assert((0 - uint32_t(1)) == 0xFFFFFFFF); + mask= ((0 - is_fields_set) & 0x0000FFFF) | + ((0 - is_bytes_set) & 0x7FFF0000) | + ((0 - is_left_set) & 0x80000000); + } + # ifdef UNIV_SEARCH_PERF_STAT /** number of successful hash searches */ size_t n_hash_succ{0}; @@ -2317,17 +2449,6 @@ struct dict_table_t { /*!< True if the table belongs to a system database (mysql, information_schema or performance_schema) */ -#ifdef BTR_CUR_HASH_ADAPT - uint8_t ahi_enabled; /*!< set to 0 if ahi is by default disabled - for this table, - 1 if should ahi should be enabled if global ahi - is enabled and 2 if it should be enabled if - global ahi == if_specified - Used to update index->ahi_enabled. - Only used in ha_innodb.cc to set ahi_enabled for - each index. - */ -#endif /* BTR_CUR_HASH_ADAPT */ dict_frm_t dict_frm_mismatch; /*!< !DICT_FRM_CONSISTENT==0 if data dictionary information and