MDEV-32286 ANALYZE displays a huge number of InnoDB secondary index pages_accessed#5362
MDEV-32286 ANALYZE displays a huge number of InnoDB secondary index pages_accessed#5362iMineLink wants to merge 2 commits into
Conversation
|
|
There was a problem hiding this comment.
Pull request overview
This PR addresses MDEV-32286 by reducing inflated pages_accessed reporting during non-covering secondary-index scans in InnoDB, introducing a fast-path that reuses the previously accessed clustered-index leaf page when access patterns show correlation.
Changes:
- Add a clustered-leaf “hint” fast-path (
btr_cur_t::try_leaf_hint()) to avoid repeated root-to-leaf descents on correlated clustered lookups. - Track and reset per-statement hint state in
row_prebuilt_t(remembered leaf page + consecutive miss streak with auto-disable). - Add an MTR test suite case for non-covering secondary index scans and update expected outputs affected by the new access pattern.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| storage/innobase/row/row0sel.cc | Attempts clustered-leaf hint before full clustered-index descent; remembers last clustered leaf page id. |
| storage/innobase/include/row0mysql.h | Adds per-statement state to row_prebuilt_t for clustered leaf hinting and miss streak tracking. |
| storage/innobase/include/btr0cur.h | Declares btr_cur_t::try_leaf_hint() API for direct hinted leaf probing. |
| storage/innobase/handler/ha_innodb.cc | Resets hint state in ha_innobase::reset() to scope hinting to a single statement. |
| storage/innobase/btr/btr0cur.cc | Implements try_leaf_hint() using non-blocking, no-I/O page acquisition and page-local search. |
| mysql-test/suite/innodb/t/non_covering_sec_idx_scan.test | Adds regression/repro test for pages_accessed behavior across correlated and decorrelated patterns. |
| mysql-test/suite/innodb/r/non_covering_sec_idx_scan.result | Captures expected output for the new test case. |
| mysql-test/main/rowid_filter_innodb.result | Updates expected pages_accessed values affected by the optimization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
…lation A full scan of t1 reports pages_accessed=23; a secondary-index scan touching 1,000 rows sharing one key reports 2003, the figure quoted in the ticket (selecting a column outside the secondary index forces a clustered-index lookup per row). t2 (3-level clustered index) and t3 (secondary index decorrelated from clustered key order) record the baselines 15165 and 20010 for the same shape. The .result records raw pages_accessed values, so a .opt pins every server option they depend on: page size (16k), row format, buffer pool size, and the adaptive hash index.
Row_sel_get_clust_rec_for_mysql::operator() re-descends the clustered B-tree from the root for every row of a non-covering secondary-index scan, although consecutive rows often share the same clustered leaf page; ANALYZE FORMAT=JSON exposes this as an inflated pages_accessed. Add btr_cur_t::try_leaf_hint(), which tries the leaf remembered in the new row_prebuilt_t::clust_leaf_hint_page_id before falling back to a full descent. The hint is a guess, never re-derived from a latched parent page, so the page is acquired with buf_page_try_get(): by the time it is tried it may precede the caller's already-latched secondary-index leaf in the B-tree latching order, where a blocking latch wait can deadlock (page latches have no deadlock detection), and BUF_GET would both trip the ignore_unfixed assertion on a freed-but-cached page and do a synchronous read for an evicted one. Freed pages are rejected explicitly: their stale contents would still pass the leaf/index-id validation. A tuple sorting after the last record of a non-rightmost leaf may have its PAGE_CUR_LE match on a later leaf, which cannot be resolved from the hinted page. The page-local search already proves this cheaply: its low_match falling short of n_fields_cmp with the cursor on the last user record means the tuple sorts strictly after every record on the page, so no separate boundary-record comparison is needed. Attempting the hint on every row regresses uncorrelated scans (each miss adds a real, pages_accessed-charged probe on top of the descent it falls back to), so the new row_prebuilt_t::clust_leaf_hint_miss_streak abandons the hint for the rest of the statement after CLUST_LEAF_HINT_MAX_MISSES (2) consecutive misses. The threshold must exceed 1 because a well-correlated scan misses once at every leaf-page boundary. The hint page id and streak are reset per statement in ha_innobase::reset(), matching autoinc_last_value. innodb.non_covering_sec_idx_scan: 2003 to 1046 (2-level clustered index), 15165 to 7309 (3-level, confirming internal levels are skipped too, not just the root), 20010 to 20012 (uncorrelated: only the two misses paid before giving up). rowid_filter_innodb: 84 to 86, the same bounded two-miss cost.
|
I reordered and adjusted the datatypes of the new On a side note, it could be possible to reorder the fields in the struct to optimize its layout. |
Added two commits, one for reproducing the issue cited in the ticket, and the second one with an optimization that adds a shortcut to directly fetch the clustered index leaf page with an hint (without full traversal) in case there's correlation during a non-covering secondary index scan.
The optimization self-disengages after 2 consecutive failures.
That's the price these kind of queries have to pay in terms of
pages_accessedeven when they don't benefit from this (maybe due to fully decorrelated secondary-to-clustered index access pattern).