From 3d546c5271c81c9070b5813e719ca3877bf443c8 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Wed, 1 Oct 2025 17:58:15 +0200 Subject: [PATCH 01/15] 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 00b834ab147da0c9037cb83673b348bde6b07f85 Mon Sep 17 00:00:00 2001 From: Sergei Golubchik Date: Mon, 6 Oct 2025 18:33:50 +0200 Subject: [PATCH 02/15] 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 09d8ed284b05f..d170705ad5d4a 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -3397,10 +3397,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); /* @@ -3621,7 +3621,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 d52fe8f7e7241d1f780f40eba39f4679b8a7a8f6 Mon Sep 17 00:00:00 2001 From: Monty Date: Wed, 25 Jun 2025 17:10:03 +0300 Subject: [PATCH 03/15] 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 | 16 ++ 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, 472 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 b9f1e8b68f60c..06ac058eb57a5 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 8da1c18feb421..ebaf466660f40 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 9d16645092244..279ef8305a28d 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5867,4 +5867,20 @@ int get_select_field_pos(Alter_info *alter_info, bool versioned); #ifndef DBUG_OFF String dbug_format_row(TABLE *table, const uchar *rec, bool print_names= true); #endif /* DBUG_OFF */ + +/* + 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 3c46f6e8ce3df..5b506ad802c27 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 eacde4912b89e..789665a45cbdf 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -666,10 +666,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. @@ -2967,6 +2981,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 } /*********************************************************************//** @@ -3668,10 +3696,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); @@ -4152,6 +4183,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 @@ -6026,10 +6058,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); @@ -6040,7 +6074,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()); @@ -11396,7 +11451,6 @@ create_table_info_t::check_table_options() return "PAGE_COMPRESSION_LEVEL"; } } - return NULL; } @@ -19315,10 +19369,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 0fa14f747c9f7..849d7cf26423d 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 655a6d96cedafc32ddaad3528d53fb5d3bc23667 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 04/15] 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 d1da93273f0205ca1d0aa63049c39a78ed1d820d 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 05/15] 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 7442b09bbc9ae6ea25dca14e595333b476ca36cf 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 06/15] 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 789665a45cbdf..d7f60a438f475 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2949,6 +2949,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, @@ -2959,42 +3002,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 + } } /*********************************************************************//** @@ -5883,7 +5923,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); @@ -6058,12 +6098,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); @@ -6074,28 +6112,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()); @@ -13226,7 +13243,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 && @@ -17475,7 +17492,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 cde7fb2ff057b44dc3b9d9432d77dd6446690911 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 07/15] 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 48b02d34f5a762169c81c444b8b5022512b15c03 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 9 Jan 2026 15:58:03 +0000 Subject: [PATCH 08/15] [skip ci] fixup: implement fixed AHI parameters Valid parameters are set for future recommendations in innodb_ahi_enable and used in btr_search_info_update_hash. TODO Allow fixing "0" as complete_fields (fields) and bytes_from_incomplete_fields (bytes) --- .../r/innodb_adaptive_hash_index_basic.result | 6 ++-- .../t/innodb_adaptive_hash_index_basic.test | 4 +-- storage/innobase/btr/btr0sea.cc | 10 ++++++- storage/innobase/handler/ha_innodb.cc | 28 +++++++++++++++++-- storage/innobase/handler/ha_innodb.h | 4 ++- storage/innobase/include/dict0mem.h | 8 ++++++ 6 files changed, 51 insertions(+), 9 deletions(-) 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..b9a162d2bedff 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_fields=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_fields`=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_fields=default for_equal_hash_point_to_last_record=default; show create table t1; Table Create Table t1 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..4e97135e39606 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,9 @@ 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_fields=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_fields=default for_equal_hash_point_to_last_record=default; show create table t1; drop table t1; diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc index 4dbb4bbcdb004..7037bab723f76 100644 --- a/storage/innobase/btr/btr0sea.cc +++ b/storage/innobase/btr/btr0sea.cc @@ -686,9 +686,15 @@ 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; + const auto& mask= info.ahi_fixed_left_bytes_fields_mask; + const auto& fixed= info.ahi_fixed_left_bytes_fields; + 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 */ + 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 +754,8 @@ 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 */ + 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 */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index d7f60a438f475..567bba0097b0a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -679,8 +679,8 @@ ha_create_table_option innodb_index_option_list[]= 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_ENUM("FOR_EQUAL_HASH_POINT_TO_LAST_RECORD", + for_equal_hash_point_to_last_record, "DEFAULT,YES,NO", 0), HA_IOPTION_END }; @@ -2985,7 +2985,31 @@ static void innodb_ahi_enable(dict_table_t *innodb_table, dict_index_t *index= dict_table_get_index_on_name(innodb_table, key.name.str); if (index) + { index->search_info.ahi_enabled= ahi; + const auto fields= key.option_struct->complete_fields; + const auto bytes= key.option_struct->bytes_from_incomplete_fields; + const auto left= + key.option_struct->for_equal_hash_point_to_last_record; + ut_ad(fields < 0xFFFF); + ut_ad(bytes < 0x8000); + ut_ad(left <= 2); + /* TODO Cannot force "0" as fields or bytes, as it's the + default value which represents "unset" property */ + const bool is_fields_set= fields != 0; + const bool is_bytes_set= bytes != 0; + const bool is_left_set= left != 0; + const uint32_t mask= + (is_fields_set ? 0x0000FFFF : 0) | + (is_bytes_set ? 0x7FFF0000 : 0) | + (is_left_set ? 0x80000000 : 0); + const uint32_t fixed= + static_cast(fields) | + (static_cast(bytes) << 16) | + (left == 1 ? 0x80000000 : 0); + index->search_info.ahi_fixed_left_bytes_fields_mask= mask; + index->search_info.ahi_fixed_left_bytes_fields= fixed; + } } } } diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 781dec388e9f3..81229ee4247c8 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -54,7 +54,9 @@ 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; + /** 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/dict0mem.h b/storage/innobase/include/dict0mem.h index 849d7cf26423d..8ab1972aef9f7 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1100,6 +1100,14 @@ struct dict_index_t { /** number of buf_block_t::index pointers to this index */ Atomic_counter ref_count{0}; + /** mask which indicates which bits are valid in + ahi_fixed_left_bytes_fields */ + uint32_t ahi_fixed_left_bytes_fields_mask{0}; + /** fixed parameters in recommendations, validity depends on + ahi_fixed_left_bytes_fields_mask; + @see buf_block_t::left_bytes_fields */ + uint32_t ahi_fixed_left_bytes_fields{0}; + # ifdef UNIV_SEARCH_PERF_STAT /** number of successful hash searches */ size_t n_hash_succ{0}; From 53d037ba03b6aa206e539e0591ac3bbf893f9ffd Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Tue, 13 Jan 2026 14:23:14 +0000 Subject: [PATCH 09/15] fixup: fix innodb.index_ahi_option,if_specified Should currently behave as innodb.index_ahi_option,no_ahi excluding the global variable value. --- .../innodb/r/index_ahi_option,if_specified.rdiff | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff 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..0d9a3159f1c15 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff @@ -0,0 +1,11 @@ +--- 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=if_specified; + # + # Scenario 1: complete_fields parameter with point lookups + # From 0eb076a06c90b0c34b1b85fc6a216dc4a337c81a Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Wed, 14 Jan 2026 11:47:02 +0000 Subject: [PATCH 10/15] Revert "fixup: fix innodb.index_ahi_option,if_specified" This reverts commit 53d037ba03b6aa206e539e0591ac3bbf893f9ffd. --- .../innodb/r/index_ahi_option,if_specified.rdiff | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff 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 deleted file mode 100644 index 0d9a3159f1c15..0000000000000 --- a/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff +++ /dev/null @@ -1,11 +0,0 @@ ---- 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=if_specified; - # - # Scenario 1: complete_fields parameter with point lookups - # From 61eaee311b79c502ef80fd3a5312c2a5970749aa Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Tue, 13 Jan 2026 14:23:14 +0000 Subject: [PATCH 11/15] fixup: fix per-index AHI parameters - Allow `complete_fields` and `bytes_from_incomplete_fields` to be set to 0 explicitly - Use three-valued logic for `adaptive_hash_index` to easily represent "default" state - Refactor AHI-related fields to use Atomic_relaxed for thread safety - Remove unused `ahi_enabled` field from `dict_table_t` - Ensure table is not rebuilt in `innodb.index_ahi_option` test to allow consistent `page_accessed` counts when table flags are changed for AHI usage detection - Add per-index `adaptive_hash_index` flags in `innodb.index_ahi_option` - Reset index AHI parameters in any case when applying options in `innodb_ahi_enable` TODO test internal usage of per-index `complete_fields`, `bytes_from_incomplete_fields` and `for_equal_hash_point_to_last_record` settings --- .../suite/innodb/r/index_ahi_option,ahi.rdiff | 20 +++- .../r/index_ahi_option,if_specified.rdiff | 38 ++++++ .../suite/innodb/r/index_ahi_option.result | 16 +-- .../suite/innodb/t/index_ahi_option.test | 16 ++- .../r/innodb_adaptive_hash_index_basic.result | 10 ++ .../t/innodb_adaptive_hash_index_basic.test | 2 + sql/handler.cc | 4 +- storage/innobase/handler/ha_innodb.cc | 108 +++++++++--------- storage/innobase/include/dict0mem.h | 22 +--- 9 files changed, 152 insertions(+), 84 deletions(-) create mode 100644 mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff 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..2325623d4f9f1 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff @@ -27,7 +27,16 @@ }, "filtered": 100, "r_total_filtered": 100, -@@ -129,7 +129,7 @@ +@@ -212,7 +212,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, +@@ -296,7 +296,7 @@ "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { @@ -36,3 +45,12 @@ }, "filtered": 100, "r_total_filtered": 100, +@@ -336,7 +336,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, 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..caefaec6a80d6 --- /dev/null +++ b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.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=if_specified; + # + # Scenario 1: complete_fields parameter with point lookups + # +@@ -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, +@@ -212,7 +212,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, +@@ -336,7 +336,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, diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result index cc35000d2be77..e622fde2b0eeb 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option.result +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -9,9 +9,9 @@ SET GLOBAL innodb_adaptive_hash_index=OFF; 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), @@ -141,7 +141,7 @@ ANALYZE ] } } -ALTER TABLE t1 adaptive_hash_index=OFF, FORCE; +ALTER TABLE t1 adaptive_hash_index=OFF; ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; ANALYZE @@ -172,7 +172,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 4 + "pages_accessed": 6 }, "filtered": 100, "r_total_filtered": 100, @@ -212,7 +212,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 6 + "pages_accessed": 4 }, "filtered": 100, "r_total_filtered": 100, @@ -296,7 +296,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 4 + "pages_accessed": 6 }, "filtered": 100, "r_total_filtered": 100, @@ -336,7 +336,7 @@ ANALYZE "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", "r_engine_stats": { - "pages_accessed": 6 + "pages_accessed": 4 }, "filtered": 100, "r_total_filtered": 100, diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 40b6a61ae1abc..0b986bda517ac 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -23,9 +23,9 @@ SET GLOBAL innodb_adaptive_hash_index=if_specified; 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; @@ -57,7 +57,9 @@ SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AN --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; + +ALTER TABLE t1 adaptive_hash_index=OFF; + --replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ ANALYZE FORMAT=JSON SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; @@ -67,7 +69,9 @@ SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AN --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) { @@ -79,7 +83,9 @@ while ($i) --enable_result_log --enable_query_log dec $i; -}--replace_regex /("(r_[a-z_]*_time_ms)": )[^, \n]*/\1"REPLACED"/ +} + +--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"/ 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 b9a162d2bedff..77701e9c8f44b 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 @@ -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_fields=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_fields`=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 4e97135e39606..2ac6c88a299b2 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 @@ -106,6 +106,8 @@ CREATE OR REPLACE TABLE t1 (a int primary key, b varchar(100), c int, index (c,b show create table t1; alter table t1 drop index c, add index (c,b) complete_fields=2 bytes_from_incomplete_fields=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_fields=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 ebaf466660f40..7aa815ab9d489 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/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 567bba0097b0a..452884c113b76 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -667,7 +667,7 @@ 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 }; @@ -675,12 +675,14 @@ ha_create_table_option innodb_table_option_list[]= 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), + table_hint_options, TABLE_HINT_DEFAULT), + HA_IOPTION_NUMBER("COMPLETE_FIELDS", complete_fields, ULONGLONG_MAX, 0, 32, + 1), HA_IOPTION_NUMBER("BYTES_FROM_INCOMPLETE_FIELDS", - bytes_from_incomplete_fields, 0, 1, 8192, 1), + bytes_from_incomplete_fields, ULONGLONG_MAX, 0, 8192, 1), HA_IOPTION_ENUM("FOR_EQUAL_HASH_POINT_TO_LAST_RECORD", - for_equal_hash_point_to_last_record, "DEFAULT,YES,NO", 0), + for_equal_hash_point_to_last_record, table_hint_options, + TABLE_HINT_DEFAULT), HA_IOPTION_END }; @@ -2958,60 +2960,62 @@ static void innodb_ahi_enable(dict_table_t *innodb_table, const ha_table_option_struct *option_struct, const TABLE *table) { + /* + ahi_enabled maps: + 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, 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. */ - UT_LIST_GET_FIRST(innodb_table->indexes)->search_info.ahi_enabled= + /* 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--; ) + for (auto i= table->s->keys; i--; ) + { + 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 ahi= index_ahi != 1 ? index_ahi: table_ahi; + index->search_info.ahi_enabled= ahi; + if (ahi == 0) { - 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 auto fields= key.option_struct->complete_fields; - const auto bytes= key.option_struct->bytes_from_incomplete_fields; - const auto left= - key.option_struct->for_equal_hash_point_to_last_record; - ut_ad(fields < 0xFFFF); - ut_ad(bytes < 0x8000); - ut_ad(left <= 2); - /* TODO Cannot force "0" as fields or bytes, as it's the - default value which represents "unset" property */ - const bool is_fields_set= fields != 0; - const bool is_bytes_set= bytes != 0; - const bool is_left_set= left != 0; - const uint32_t mask= - (is_fields_set ? 0x0000FFFF : 0) | - (is_bytes_set ? 0x7FFF0000 : 0) | - (is_left_set ? 0x80000000 : 0); - const uint32_t fixed= - static_cast(fields) | - (static_cast(bytes) << 16) | - (left == 1 ? 0x80000000 : 0); - index->search_info.ahi_fixed_left_bytes_fields_mask= mask; - index->search_info.ahi_fixed_left_bytes_fields= fixed; - } - } + index->search_info.ahi_fixed_left_bytes_fields_mask= 0; + index->search_info.ahi_fixed_left_bytes_fields= 0; + continue; } + const auto fields= key.option_struct->complete_fields; + const auto bytes= key.option_struct->bytes_from_incomplete_fields; + const auto left= + key.option_struct->for_equal_hash_point_to_last_record; + ut_ad(fields == ULONGLONG_MAX || fields < 0xFFFF); + ut_ad(bytes == ULONGLONG_MAX || bytes < 0x8000); + ut_ad(left >= TABLE_HINT_DEFAULT && left <= TABLE_HINT_NO); + const bool is_fields_set= fields != ULONGLONG_MAX; + const bool is_bytes_set= bytes != ULONGLONG_MAX; + const bool is_left_set= left != TABLE_HINT_DEFAULT; + const uint32_t mask= + (is_fields_set ? 0x0000FFFF : 0) | + (is_bytes_set ? 0x7FFF0000 : 0) | + (is_left_set ? 0x80000000 : 0); + const uint32_t fixed= + static_cast(fields) | + (static_cast(bytes) << 16) | + (left == TABLE_HINT_YES ? 0x80000000 : 0); + index->search_info.ahi_fixed_left_bytes_fields_mask= mask; + index->search_info.ahi_fixed_left_bytes_fields= fixed; } } #endif diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 8ab1972aef9f7..83cdcae1d6c53 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1089,9 +1089,10 @@ 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) + /** If adaptive hash indexes are enabled for this index: + - 0: force disabled + - 1: no preference (set by default, use global setting) + - 2: prefer enabled (if not globally disabled) */ Atomic_relaxed ahi_enabled{0}; @@ -1102,11 +1103,11 @@ struct dict_index_t { /** mask which indicates which bits are valid in ahi_fixed_left_bytes_fields */ - uint32_t ahi_fixed_left_bytes_fields_mask{0}; + Atomic_relaxed ahi_fixed_left_bytes_fields_mask{0}; /** fixed parameters in recommendations, validity depends on ahi_fixed_left_bytes_fields_mask; @see buf_block_t::left_bytes_fields */ - uint32_t ahi_fixed_left_bytes_fields{0}; + Atomic_relaxed ahi_fixed_left_bytes_fields{0}; # ifdef UNIV_SEARCH_PERF_STAT /** number of successful hash searches */ @@ -2325,17 +2326,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 From 059330f2e3392b4b6392251a105bd7dab4590e1e Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Wed, 14 Jan 2026 14:43:42 +0000 Subject: [PATCH 12/15] fixup: fix "if_specified" test and options - increase warm-up loop iterations from 120 to 140 in `innodb.index_ahi_option` test to allow `idx_1` picking up AHI when globally enabled in the `if_specified` combination - fix handling of default `complete_fields` and `bytes_from_incomplete_fields` values in `innodb_ahi_enable` - touchup to `btr_search_info_update_hash` to avoid reloading atomic variable multiple times --- .../suite/innodb/r/index_ahi_option,if_specified.rdiff | 9 +++++++++ mysql-test/suite/innodb/t/index_ahi_option.test | 4 ++-- storage/innobase/btr/btr0sea.cc | 4 ++-- storage/innobase/handler/ha_innodb.cc | 10 +++++----- 4 files changed, 18 insertions(+), 9 deletions(-) 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 index caefaec6a80d6..e33f348b8595e 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff @@ -27,6 +27,15 @@ }, "filtered": 100, "r_total_filtered": 100, +@@ -296,7 +296,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, @@ -336,7 +336,7 @@ "r_table_time_ms": "REPLACED", "r_other_time_ms": "REPLACED", diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 0b986bda517ac..290d5dfb873d4 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -35,7 +35,7 @@ INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), (50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), (50009, 50, 9, 4); -let $i = 120; +let $i = 140; while ($i) { --disable_query_log @@ -72,7 +72,7 @@ SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AN ALTER TABLE t1 adaptive_hash_index='ON'; -let $i = 120; +let $i = 140; while ($i) { --disable_query_log diff --git a/storage/innobase/btr/btr0sea.cc b/storage/innobase/btr/btr0sea.cc index 7037bab723f76..c0c58037f370d 100644 --- a/storage/innobase/btr/btr0sea.cc +++ b/storage/innobase/btr/btr0sea.cc @@ -686,8 +686,8 @@ 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; - const auto& mask= info.ahi_fixed_left_bytes_fields_mask; - const auto& fixed= info.ahi_fixed_left_bytes_fields; + const uint32_t mask= info.ahi_fixed_left_bytes_fields_mask.load(); + const uint32_t fixed= info.ahi_fixed_left_bytes_fields.load(); if (!n_hash_potential) { diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 452884c113b76..759615cb1c55a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -2996,9 +2996,9 @@ static void innodb_ahi_enable(dict_table_t *innodb_table, index->search_info.ahi_fixed_left_bytes_fields= 0; continue; } - const auto fields= key.option_struct->complete_fields; - const auto bytes= key.option_struct->bytes_from_incomplete_fields; - const auto left= + const uint64_t fields= key.option_struct->complete_fields; + const uint64_t bytes= key.option_struct->bytes_from_incomplete_fields; + const uint32_t left= key.option_struct->for_equal_hash_point_to_last_record; ut_ad(fields == ULONGLONG_MAX || fields < 0xFFFF); ut_ad(bytes == ULONGLONG_MAX || bytes < 0x8000); @@ -3011,8 +3011,8 @@ static void innodb_ahi_enable(dict_table_t *innodb_table, (is_bytes_set ? 0x7FFF0000 : 0) | (is_left_set ? 0x80000000 : 0); const uint32_t fixed= - static_cast(fields) | - (static_cast(bytes) << 16) | + (is_fields_set ? static_cast(fields) : 0) | + (is_bytes_set ? (static_cast(bytes) << 16) : 0) | (left == TABLE_HINT_YES ? 0x80000000 : 0); index->search_info.ahi_fixed_left_bytes_fields_mask= mask; index->search_info.ahi_fixed_left_bytes_fields= fixed; From 7e385db2008ab5831c978f8ce7ad945fb161d5e2 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Thu, 15 Jan 2026 16:49:18 +0000 Subject: [PATCH 13/15] 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 b08de5185ebd4..6aa7a2fea0b32 100644 --- a/sql/create_options.cc +++ b/sql/create_options.cc @@ -517,8 +517,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 49ae994cff952e807287ac6eed19d08619e1cec0 Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 16 Jan 2026 10:11:40 +0000 Subject: [PATCH 14/15] fixup: fix options parsing avoid reading after the end of the current string. --- sql/create_options.cc | 5 ++++- sql/create_options.h | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/sql/create_options.cc b/sql/create_options.cc index 6aa7a2fea0b32..2cea91171ce56 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; } } diff --git a/sql/create_options.h b/sql/create_options.h index 732225c243e0c..21b661abe2c38 100644 --- a/sql/create_options.h +++ b/sql/create_options.h @@ -54,7 +54,9 @@ class engine_option_value: public Sql_alloc for (end=str; *end && *end != ','; end++) /* no-op */; if (streq(Lex_cstring(str, end))) return num; - str= end + 1; + str= end; + if (*str == ',') + ++str; } return UINT_MAX; } From 2f4baed973e2b4fd3fae25f41514d7eeb0eb1fbc Mon Sep 17 00:00:00 2001 From: Alessandro Vetere Date: Fri, 16 Jan 2026 17:54:27 +0000 Subject: [PATCH 15/15] fixup: try making index_ahi_option more robust use innodb_metrics instead of analyze format=json to detect ahi usage. --- .../suite/innodb/r/index_ahi_option,ahi.rdiff | 71 ++-- .../r/index_ahi_option,if_specified.rdiff | 62 ++- .../suite/innodb/r/index_ahi_option.result | 397 ++---------------- .../suite/innodb/t/index_ahi_option.test | 231 ++++++++-- 4 files changed, 281 insertions(+), 480 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 2325623d4f9f1..a287e871c065f 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,ahi.rdiff @@ -9,48 +9,29 @@ # # 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, -@@ -212,7 +212,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, -@@ -296,7 +296,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, -@@ -336,7 +336,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, +@@ -30,20 +30,20 @@ + SET GLOBAL innodb_monitor_enable = module_adaptive_hash; + # Warming up AHI + # Warmed up AHI +-# No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_1) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index=OFF; + # Warming up AHI + # Warmed up AHI + # No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index='ON'; + # Warming up AHI + # Warmed up AHI +-# No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_1) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + DROP TABLE t1; + SET @@global.innodb_adaptive_hash_index = @start_global_value; 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 index e33f348b8595e..9e1a5ec3f3a55 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff +++ b/mysql-test/suite/innodb/r/index_ahi_option,if_specified.rdiff @@ -1,5 +1,5 @@ --- index_ahi_option.result -+++ index_ahi_option,ahi.result ++++ index_ahi_option,if_specified.result @@ -2,7 +2,7 @@ # Test InnoDB index-level adaptive_hash_index options # @@ -9,39 +9,27 @@ # # Scenario 1: complete_fields parameter with point lookups # -@@ -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, -@@ -212,7 +212,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, -@@ -296,7 +296,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, -@@ -336,7 +336,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, +@@ -31,19 +31,19 @@ + # Warming up AHI + # Warmed up AHI + # No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index=OFF; + # Warming up AHI + # Warmed up AHI + # No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + ALTER TABLE t1 adaptive_hash_index='ON'; + # Warming up AHI + # Warmed up AHI +-# No AHI used in SELECT (idx_1) +-# No AHI used in SELECT (idx_2) ++# Used AHI in SELECT (idx_1) ++# Used AHI in SELECT (idx_2) + # No AHI used in SELECT (idx_3) + DROP TABLE t1; + SET @@global.innodb_adaptive_hash_index = @start_global_value; diff --git a/mysql-test/suite/innodb/r/index_ahi_option.result b/mysql-test/suite/innodb/r/index_ahi_option.result index e622fde2b0eeb..3730298191b9f 100644 --- a/mysql-test/suite/innodb/r/index_ahi_option.result +++ b/mysql-test/suite/innodb/r/index_ahi_option.result @@ -18,376 +18,35 @@ 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 - } - } - ] - } -} +# +# 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. +# +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +# Warming up AHI +# Warmed up AHI +# No AHI used in SELECT (idx_1) +# No AHI used in SELECT (idx_2) +# No AHI used in SELECT (idx_3) ALTER TABLE t1 adaptive_hash_index=OFF; -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 - } - } - ] - } -} +# Warming up AHI +# Warmed up AHI +# No AHI used in SELECT (idx_1) +# No AHI used in SELECT (idx_2) +# No AHI used in SELECT (idx_3) 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": 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 - } - } - ] - } -} +# Warming up AHI +# Warmed up AHI +# No AHI used in SELECT (idx_1) +# No AHI used in SELECT (idx_2) +# No AHI used in SELECT (idx_3) 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; diff --git a/mysql-test/suite/innodb/t/index_ahi_option.test b/mysql-test/suite/innodb/t/index_ahi_option.test index 290d5dfb873d4..5b86c2a250d9c 100644 --- a/mysql-test/suite/innodb/t/index_ahi_option.test +++ b/mysql-test/suite/innodb/t/index_ahi_option.test @@ -35,7 +35,22 @@ INSERT INTO t1 VALUES (50001, 50, 1, 1), (50002, 50, 2, 2), (50006, 50, 6, 1), (50007, 50, 7, 2), (50008, 50, 8, 3), (50009, 50, 9, 4); -let $i = 140; +--echo # +--echo # AHI usage in searches is verified by measuring an increase in +--echo # the "count" of "adaptive_hash_searches" InnoDB metric. +--echo # An alternative strategy would use "ANALYZE FORMAT=JSON" and +--echo # check the "pages_accessed" entry: lower values w.r.t. the +--echo # base case would imply that AHI was used for the search. +--echo # Unfortunately, that is not really stable. +--echo # Hopefully using "innodb_metrics" is more stable. +--echo # + +SET GLOBAL innodb_monitor_enable = module_adaptive_hash; +let $warm_up_rounds= 200; +let $query_rounds= 10; + +--echo # Warming up AHI +let $i = $warm_up_rounds; while ($i) { --disable_query_log @@ -47,32 +62,139 @@ while ($i) --enable_query_log dec $i; } +--echo # Warmed up AHI + +let $val0 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_1) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_1) +} +let $val0 = $val1; + +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_2) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_2) +} +let $val0 = $val1; ---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; +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_3) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_3) +} +let $val0 = $val1; ALTER TABLE t1 adaptive_hash_index=OFF; ---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; +--echo # Warming up AHI +let $i = $warm_up_rounds; +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 # Warmed up AHI + +let $val0 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_1) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_1) +} +let $val0 = $val1; + +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_2) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_2) +} +let $val0 = $val1; + +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_3) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_3) +} +let $val0 = $val1; ALTER TABLE t1 adaptive_hash_index='ON'; -let $i = 140; +--echo # Warming up AHI +let $i = 200; while ($i) { --disable_query_log @@ -84,16 +206,67 @@ while ($i) --enable_query_log dec $i; } +--echo # Warmed up AHI ---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; +let $val0 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_1) WHERE col1 = 50; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_1) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_1) +} +let $val0 = $val1; + +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_2) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_2) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_2) +} +let $val0 = $val1; + +let $k = $query_rounds; +--disable_query_log +--disable_result_log +while ($k) { + SELECT COUNT(*) FROM t1 FORCE INDEX(idx_3) WHERE col1 = 50 AND col2 BETWEEN 0 AND 9 AND col3 BETWEEN 0 AND 9; + dec $k; +} +--enable_result_log +--enable_query_log +let $val1 = query_get_value(SELECT count FROM information_schema.innodb_metrics WHERE name = 'adaptive_hash_searches', count, 1); +if ($val1 > $val0) { + --echo # Used AHI in SELECT (idx_3) +} +if ($val1 <= $val0) { + --echo # No AHI used in SELECT (idx_3) +} +let $val0 = $val1; 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