Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mysql-test/include/analyze-format.inc
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# - r_engine_stats depends on buffer pool state and whether old record versions
# were purged.

--replace_regex /("(r_[a-z_]*_time(_in_progress)?_ms|r_buffer_size|cost|r_partial_match_buffer_size)": )[^, \n]*/\1"REPLACED"/ /("r_engine_stats":) {[^}]*}/\1 REPLACED/
--replace_regex /("(r_[a-z_]*_time(_in_progress)?_ms|r_buffer_size|cost|r_partial_match_buffer_size)": )[^, \n]*/\1"REPLACED"/ /("r_engine_stats":) \{(?:[^{}]|\{[^}]*\})*\}/\1 REPLACED/
40 changes: 36 additions & 4 deletions mysql-test/main/rowid_filter_innodb,ahi.rdiff
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,53 @@
DROP DATABASE IF EXISTS dbt3_s001;
CREATE DATABASE dbt3_s001;
use dbt3_s001;
@@ -2059,7 +2059,7 @@
@@ -2061,7 +2061,11 @@
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": {
- "pages_accessed": 90
+ "pages_accessed": 51
+ "pages_accessed": 51,
+ "r_ahi_stats": {
+ "ahi_searches": 39,
+ "ahi_searches_btree": 3
+ }
},
"filtered": "REPLACED",
"r_total_filtered": 2.43902439,
@@ -2227,7 +2227,7 @@
@@ -2093,7 +2097,10 @@
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": {
- "pages_accessed": 4
+ "pages_accessed": 4,
+ "r_ahi_stats": {
+ "ahi_searches_btree": 1
+ }
},
"filtered": "REPLACED",
"r_total_filtered": 66.66666667,
@@ -2229,7 +2236,11 @@
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": {
- "pages_accessed": 90
+ "pages_accessed": 49
+ "pages_accessed": 49,
+ "r_ahi_stats": {
+ "ahi_searches": 41,
+ "ahi_searches_btree": 1
+ }
},
"filtered": "REPLACED",
"r_total_filtered": 2.43902439,
@@ -2261,7 +2272,10 @@
"r_table_time_ms": "REPLACED",
"r_other_time_ms": "REPLACED",
"r_engine_stats": {
- "pages_accessed": 4
+ "pages_accessed": 4,
+ "r_ahi_stats": {
+ "ahi_searches_btree": 1
+ }
},
"filtered": "REPLACED",
"r_total_filtered": 66.66666667,
45 changes: 45 additions & 0 deletions mysql-test/suite/innodb/include/check_ahi_status.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Include file to execute a query with optional repetition and display
# values of r_ahi_stats on ANALYZE FORMAT=JSON
#
# Parameters:
# $query - The query to execute
# $repeat - Number of times to repeat the query before analyzing
#
# This include file will:
# 1. Execute the query $repeat times to warm up AHI (with logging disabled)
# 2. Execute ANALYZE FORMAT=JSON on the query and display the values

--disable_query_log
--disable_result_log

# Repeat the query to warm up AHI
let $i = $repeat;
while ($i > 0)
{
eval $query;
dec $i;
}

--enable_result_log
--enable_query_log

# Execute ANALYZE FORMAT=JSON once and capture output
--echo ANALYZE FORMAT=JSON $query
let $out=`ANALYZE FORMAT=JSON $query`;

# Parse JSON and extract AHI variables
--disable_query_log
evalp set @js='$out';
set @ahi_searches = COALESCE(json_extract(@js,'$**.r_engine_stats.r_ahi_stats.ahi_searches'), 0);
set @ahi_searches_btree = COALESCE(json_extract(@js,'$**.r_engine_stats.r_ahi_stats.ahi_searches_btree'), 0);
set @ahi_rows_added = COALESCE(json_extract(@js,'$**.r_engine_stats.r_ahi_stats.ahi_rows_added'), 0);
set @ahi_pages_added = COALESCE(json_extract(@js,'$**.r_engine_stats.r_ahi_stats.ahi_pages_added'), 0);
set @r_rows = json_extract(@js,'$**.table.r_rows');

# Display AHI variables
select @ahi_searches as ahi_searches,
@ahi_searches_btree as ahi_searches_btree,
@ahi_rows_added as ahi_rows_added,
@ahi_pages_added as ahi_pages_added,
@r_rows as r_rows;
--enable_query_log
30 changes: 30 additions & 0 deletions mysql-test/suite/innodb/r/ahi_stats.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#
# MDEV 38305 : Expose adaptive hash index statistics in ANALYZE FORMAT=JSON
#
SET @start_global_value= @@global.innodb_adaptive_hash_index;
SET GLOBAL innodb_adaptive_hash_index= ON;
CREATE TABLE t1 (
id INT PRIMARY KEY,
col1 INT,
col2 INT,
col3 INT,
INDEX idx_1 (col1),
INDEX idx_2 (col2),
INDEX idx_3 (col3)
) ENGINE=InnoDB;
INSERT INTO t1 SELECT seq, seq % 20, seq % 5, seq % 10 FROM seq_0_to_999;
ANALYZE FORMAT=JSON SELECT * FROM t1 FORCE INDEX(idx_1) WHERE col1 = 5
ahi_searches ahi_searches_btree ahi_rows_added ahi_pages_added r_rows
0 [51] 0 0 [50]
ANALYZE FORMAT=JSON SELECT * FROM t1 FORCE INDEX(idx_2) WHERE col2 = 3
ahi_searches ahi_searches_btree ahi_rows_added ahi_pages_added r_rows
[129] [72] [797] [2] [200]
Comment thread
iMineLink marked this conversation as resolved.
ANALYZE FORMAT=JSON SELECT * FROM t1 FORCE INDEX(idx_3) WHERE col3 = 5
ahi_searches ahi_searches_btree ahi_rows_added ahi_pages_added r_rows
[101] 0 0 0 [100]
SET GLOBAL innodb_adaptive_hash_index = OFF;
ANALYZE FORMAT=JSON SELECT * FROM t1 FORCE INDEX(idx_2) WHERE col2 = 2
ahi_searches ahi_searches_btree ahi_rows_added ahi_pages_added r_rows
0 0 0 0 [200]
DROP TABLE t1;
SET GLOBAL innodb_adaptive_hash_index= @start_global_value;
4 changes: 2 additions & 2 deletions mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@ index_page_reorg_successful index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NU
index_page_discards index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages discarded
adaptive_hash_searches adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of successful searches using Adaptive Hash Index
adaptive_hash_searches_btree adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of searches using B-tree on an index search
adaptive_hash_pages_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages on which the Adaptive Hash Index is built
adaptive_hash_pages_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of index pages on which the Adaptive Hash Index is built
adaptive_hash_pages_removed adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of index pages whose corresponding Adaptive Hash Index entries were removed
adaptive_hash_rows_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows added
adaptive_hash_rows_added adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 status_counter Number of Adaptive Hash Index rows added
adaptive_hash_rows_removed adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows removed
adaptive_hash_rows_deleted_no_hash_entry adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of rows deleted that did not have corresponding Adaptive Hash Index entries
adaptive_hash_rows_updated adaptive_hash_index 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of Adaptive Hash Index rows updated
Expand Down
2 changes: 2 additions & 0 deletions mysql-test/suite/innodb/r/innodb_status_variables.result
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ AND variable_name NOT IN
'INNODB_MEM_ADAPTIVE_HASH',
'INNODB_BUFFERED_AIO_SUBMITTED','INNODB_BUFFER_POOL_PAGES_LATCHED');
variable_name
INNODB_ADAPTIVE_HASH_PAGES_ADDED
INNODB_ADAPTIVE_HASH_ROWS_ADDED
INNODB_ASYNC_READS_PENDING
INNODB_ASYNC_READS_TASKS_RUNNING
INNODB_ASYNC_READS_TOTAL_COUNT
Expand Down
78 changes: 78 additions & 0 deletions mysql-test/suite/innodb/t/ahi_stats.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
--source include/have_innodb.inc
--source include/have_innodb_16k.inc
--source include/have_sequence.inc

--echo #
--echo # MDEV 38305 : Expose adaptive hash index statistics in ANALYZE FORMAT=JSON
--echo #

# Test Plan:
# When queries use secondary indexes, InnoDB must lookup clustered index records
# using the primary key reference obtained from the secondary index. These
# clustered index lookups accumulate and trigger AHI building when a threshold
# is reached. This test verifies that AHI statistics are correctly reported in
# ANALYZE FORMAT=JSON output across different scenarios:
#
# Test 1: Access 50 rows (col1=5, 50 matches) - insufficient to build AHI
# Test 2: Access 200 rows (col2=3, 200 matches) - combined with Test 1 (250 total),
# this triggers AHI construction on the clustered index
# Test 3: Access with heavy warmup (100 repetitions) - AHI fully utilized
# Test 4: AHI disabled - verify statistics are zero regardless of access patterns

SET @start_global_value= @@global.innodb_adaptive_hash_index;
SET GLOBAL innodb_adaptive_hash_index= ON;

CREATE TABLE t1 (
id INT PRIMARY KEY,
col1 INT,
col2 INT,
col3 INT,
INDEX idx_1 (col1),
INDEX idx_2 (col2),
INDEX idx_3 (col3)
) ENGINE=InnoDB;

INSERT INTO t1 SELECT seq, seq % 20, seq % 5, seq % 10 FROM seq_0_to_999;

# Test 1: Insufficient accesses to build AHI
# Query accesses 50 rows (col1=5 matches 50 out of 1000 rows: 5,25,45,...,985).
# When using idx_1, InnoDB performs 50 clustered index lookups to retrieve the
# full rows. This is insufficient to trigger AHI construction, so AHI statistics
# should show minimal or no AHI activity.
let $query = SELECT * FROM t1 FORCE INDEX(idx_1) WHERE col1 = 5;
let $repeat = 0;
--source suite/innodb/include/check_ahi_status.inc

# Test 2: Accumulated accesses trigger AHI construction
# Query accesses 200 rows (col2=3 matches 200 out of 1000 rows: 3,8,13,18,...,998).
# Combined with Test 1's 50 clustered index lookups, the accumulated total of 250
# clustered index searches crosses the threshold to build AHI. This test verifies
# that AHI statistics reflect the construction and initial usage of the hash index.
let $query = SELECT * FROM t1 FORCE INDEX(idx_2) WHERE col2 = 3;
let $repeat = 0;
--source suite/innodb/include/check_ahi_status.inc

# Test 3: Verify AHI statistics with heavy warmup (AHI fully utilized)
# Query accesses 100 rows (col3=5 matches 100 out of 1000 rows: 5,15,25,...,995).
# With repeat=200, this executes the query 200 times before the ANALYZE, resulting
# in 20,000 clustered index lookups. This heavy access pattern ensures the AHI is
# fully built and actively serving lookups, allowing verification that AHI hit
# statistics are properly reported when the hash index is effectively utilized.
let $query = SELECT * FROM t1 FORCE INDEX(idx_3) WHERE col3 = 5;
let $repeat = 200;
--source suite/innodb/include/check_ahi_status.inc

# Test 4: Verify AHI statistics when AHI is disabled
# Query accesses 200 rows (col2=2 matches 200 out of 1000 rows: 2,7,12,...,997).
# With repeat=100, this would normally trigger heavy AHI usage (20,000 clustered
# index lookups). However, since innodb_adaptive_hash_index is turned OFF, the AHI
# is not used and all AHI statistics should be zero regardless of query repetition.
SET GLOBAL innodb_adaptive_hash_index = OFF;

let $query = SELECT * FROM t1 FORCE INDEX(idx_2) WHERE col2 = 2;
let $repeat = 100;
--source suite/innodb/include/check_ahi_status.inc

# Cleanup
DROP TABLE t1;
SET GLOBAL innodb_adaptive_hash_index= @start_global_value;
5 changes: 5 additions & 0 deletions sql/ha_handler_stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class ha_handler_stats

ulonglong undo_records_read;

ulonglong ahi_searches; /* Successful adaptive hash lookups */
ulonglong ahi_searches_btree; /* B-tree searches (AHI miss) */
ulonglong ahi_rows_added; /* Rows added to adaptive hash index */
ulonglong ahi_pages_added; /* Pages added to adaptive hash index */
Comment on lines +44 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new fields in ha_handler_stats should be initialized to zero to prevent reporting garbage values in ANALYZE FORMAT=JSON. Additionally, ensure that these fields are cleared in the ha_handler_stats::reset() method (and initialized in the constructor if applicable) so that statistics do not leak between different queries or table accesses within the same session.

Suggested change
ulonglong ahi_searches; /* Successful adaptive hash lookups */
ulonglong ahi_searches_btree; /* B-tree searches (AHI miss) */
ulonglong ahi_rows_added; /* Rows added to adaptive hash index */
ulonglong ahi_pages_added; /* Pages added to adaptive hash index */
ulonglong ahi_searches = 0; /* Successful adaptive hash lookups */
ulonglong ahi_searches_btree = 0; /* B-tree searches (AHI miss) */
ulonglong ahi_rows_added = 0; /* Rows added to adaptive hash index */
ulonglong ahi_pages_added = 0; /* Pages added to adaptive hash index */


/* Time spent in engine, in timer_tracker_frequency() units */
ulonglong engine_time;

Expand Down
14 changes: 14 additions & 0 deletions sql/sql_explain.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,20 @@ static void trace_engine_stats(handler *file, Json_writer *writer)
writer->add_member("pages_prefetch_read_count").add_ull(hs->pages_prefetched);
if (hs->undo_records_read)
writer->add_member("old_rows_read").add_ull(hs->undo_records_read);
if (unlikely(hs->ahi_searches | hs->ahi_searches_btree |
hs->ahi_rows_added | hs->ahi_pages_added))
{
writer->add_member("r_ahi_stats").start_object();
if (hs->ahi_searches)
writer->add_member("ahi_searches").add_ull(hs->ahi_searches);
if (hs->ahi_searches_btree)
writer->add_member("ahi_searches_btree").add_ull(hs->ahi_searches_btree);
if (hs->ahi_rows_added)
writer->add_member("ahi_rows_added").add_ull(hs->ahi_rows_added);
if (hs->ahi_pages_added)
writer->add_member("ahi_pages_added").add_ull(hs->ahi_pages_added);
writer->end_object();
}
writer->end_object();
}
}
Expand Down
8 changes: 4 additions & 4 deletions storage/innobase/btr/btr0btr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1888,7 +1888,7 @@ btr_root_raise_and_insert(
page_get_infimum_rec(root->page.frame));
}

btr_search_move_or_delete_hash_entries(new_block, root);
btr_search_move_or_delete_hash_entries(new_block, root, *mtr);
}

constexpr uint16_t max_trx_id = PAGE_HEADER + PAGE_MAX_TRX_ID;
Expand Down Expand Up @@ -3064,7 +3064,7 @@ btr_page_split_and_insert(
}

btr_search_move_or_delete_hash_entries(
new_block, block);
new_block, block, *mtr);

/* Delete the records from the source page. */

Expand Down Expand Up @@ -3112,7 +3112,7 @@ btr_page_split_and_insert(
}

btr_search_move_or_delete_hash_entries(
new_block, block);
new_block, block, *mtr);

/* Delete the records from the source page. */

Expand Down Expand Up @@ -3460,7 +3460,7 @@ btr_lift_page_up(
lock_prdt_rec_move(father_block, block->page.id());
} else {
btr_search_move_or_delete_hash_entries(
father_block, block);
father_block, block, *mtr);
}
}

Expand Down
Loading
Loading