自适应哈希索引数据结构
对B+树的数据查找,需要遍历从根节点到叶子节点之间的每一层的节点,因此B+树的树高过高时,会影响B+树的查找效率。
Innodb存储引擎根据查询模式,对活跃的数据页中的记录进行哈希索引,以实现快速查找,解决B+树树高问题。
为标识查询模式和热点页,需要在索引和数据页上有相应的数据结构来存放访问信息。
- 每个索引对象有一个btr_search_t对象来保持索引的访问模式和相关信息。
- 每个数据页对象有一个buf_block_t对象来保持数据页的访问模式和相关信息。
btr_search_t对象
btr_search_t对象来保持索引的访问模式以及相关AHI信息:
- hash_analysis:通过索引访问的次数,如果通过索引访问次数超过BTR_SEARCH_HASH_ANALYSIS(默认17)次数时,则开始对索引上的访问模式进行统计。
- n_hash_potential:按照连续查询模式成功次数,当成功次数超过BTR_SEARCH_BUILD_LIMIT(默认100)次数时,则开始对索引中记录创建HASH索引。
- n_fields:自适应哈希索引前缀列的数量
- n_bytes:自适应哈希索引的字节数(非完整列)
- left_side:访问数据方向,用于判断对重复列进行查找的方向(向左还是向右
struct btr_search_t{ ulint ref_count; /*!< Number of blocks in this index tree that have search index built i.e. block->index points to this index. Protected by search latch except when during initialization in btr_search_info_create(). */ /* @{ The following fields are not protected by any latch. Unfortunately, this means that they must be aligned to the machine word, i.e., they cannot be turned into bit-fields. */ buf_block_t* root_guess;/*!< the root page frame when it was last time fetched, or NULL */ ulint withdraw_clock; /*!< the withdraw clock value of the buffer pool when root_guess was stored */ ulint hash_analysis; /*!< when this exceeds BTR_SEARCH_HASH_ANALYSIS, the hash analysis starts; this is reset if no success noticed */ ibool last_hash_succ; /*!< TRUE if the last search would have succeeded, or did succeed, using the hash index; NOTE that the value here is not exact: it is not calculated for every search, and the calculation itself is not always accurate! */ ulint n_hash_potential; /*!< number of consecutive searches which would have succeeded, or did succeed, using the hash index; the range is 0 .. BTR_SEARCH_BUILD_LIMIT + 5 */ /* @} */ /*---------------------- @{ */ ulint n_fields; /*!< recommended prefix length for hash search: number of full fields */ ulint n_bytes; /*!< recommended prefix: number of bytes in an incomplete field @see BTR_PAGE_MAX_REC_SIZE */ ibool left_side; /*!< TRUE or FALSE, depending on whether the leftmost record of several records with the same prefix should be indexed in the hash index */ /*---------------------- @} */ #ifdef UNIV_SEARCH_PERF_STAT ulint n_hash_succ; /*!< number of successful hash searches thus far */ ulint n_hash_fail; /*!< number of failed hash searches */ ulint n_patt_succ; /*!< number of successful pattern searches thus far */ ulint n_searches; /*!< number of searches */ #endif /* UNIV_SEARCH_PERF_STAT */ #ifdef UNIV_DEBUG ulint magic_n; /*!< magic number @see BTR_SEARCH_MAGIC_N */ /** value of btr_search_t::magic_n, used in assertions */ # define BTR_SEARCH_MAGIC_N 1112765 #endif /* UNIV_DEBUG */ };
buf_block_t 对象
每个数据页有一个buf_block_t对象用来存放页的访问信息,判断该数据页是否需要对该页记录建立自适应哈希索引。
- n_hash_helps:用来控制是否创建哈希索引的计数器
- n_fields:自适应哈希索引前缀列的数量
- n_bytes:自适应哈希索引的字节数(非完整列)
/** The buffer control block structure */ struct buf_block_t{ /** @name General fields */ /* @{ */ buf_page_t page; /*!< page information; this must be the first field, so that buf_pool->page_hash can point to buf_page_t or buf_block_t */ byte* frame; /*!< pointer to buffer frame which is of size UNIV_PAGE_SIZE, and aligned to an address divisible by UNIV_PAGE_SIZE */ #ifndef UNIV_HOTBACKUP BPageLock lock; /*!< read-write lock of the buffer frame */ UT_LIST_NODE_T(buf_block_t) unzip_LRU; /*!< node of the decompressed LRU list; a block is in the unzip_LRU list if page.state == BUF_BLOCK_FILE_PAGE and page.zip.data != NULL */ #ifdef UNIV_DEBUG ibool in_unzip_LRU_list;/*!< TRUE if the page is in the decompressed LRU list; used in debugging */ ibool in_withdraw_list; #endif /* UNIV_DEBUG */ unsigned lock_hash_val:32;/*!< hashed value of the page address in the record lock hash table; protected by buf_block_t::lock (or buf_block_t::mutex, buf_pool->mutex in buf_page_get_gen(), buf_page_init_for_read() and buf_page_create()) */ /* @} */ /** @name Optimistic search field */ /* @{ */ ib_uint64_t modify_clock; /*!< this clock is incremented every time a pointer to a record on the page may become obsolete; this is used in the optimistic cursor positioning: if the modify clock has not changed, we know that the pointer is still valid; this field may be changed if the thread (1) owns the pool mutex and the page is not bufferfixed, or (2) the thread has an x-latch on the block */ /* @} */ /** @name Hash search fields (unprotected) NOTE that these fields are NOT protected by any semaphore! */ /* @{ */ ulint n_hash_helps; /*!< counter which controls building of a new hash index for the page */ volatile ulint n_bytes; /*!< recommended prefix length for hash search: number of bytes in an incomplete last field */ volatile ulint n_fields; /*!< recommended prefix length for hash search: number of full fields */ volatile bool left_side; /*!< true or false, depending on whether the leftmost record of several records with the same prefix should be indexed in the hash index */ /* @} */ /** @name Hash search fields These 5 fields may only be modified when: we are holding the appropriate x-latch in btr_search_latches[], and one of the following holds: (1) the block state is BUF_BLOCK_FILE_PAGE, and we are holding an s-latch or x-latch on buf_block_t::lock, or (2) buf_block_t::buf_fix_count == 0, or (3) the block state is BUF_BLOCK_REMOVE_HASH. An exception to this is when we init or create a page in the buffer pool in buf0buf.cc. Another exception for buf_pool_clear_hash_index() is that assigning block->index = NULL (and block->n_pointers = 0) is allowed whenever btr_search_own_all(RW_LOCK_X). Another exception is that ha_insert_for_fold_func() may decrement n_pointers without holding the appropriate latch in btr_search_latches[]. Thus, n_pointers must be protected by atomic memory access. This implies that the fields may be read without race condition whenever any of the following hold: - the btr_search_latches[] s-latch or x-latch is being held, or - the block state is not BUF_BLOCK_FILE_PAGE or BUF_BLOCK_REMOVE_HASH, and holding some latch prevents the state from changing to that. Some use of assert_block_ahi_empty() or assert_block_ahi_valid() is prone to race conditions while buf_pool_clear_hash_index() is executing (the adaptive hash index is being disabled). Such use is explicitly commented. */ /* @{ */ #if defined UNIV_AHI_DEBUG || defined UNIV_DEBUG ulint n_pointers; /*!< used in debugging: the number of pointers in the adaptive hash index pointing to this frame; protected by atomic memory access or btr_search_own_all(). */ # define assert_block_ahi_empty(block) ut_a(os_atomic_increment_ulint(&(block)->n_pointers, 0) == 0) # define assert_block_ahi_empty_on_init(block) do { UNIV_MEM_VALID(&(block)->n_pointers, sizeof (block)->n_pointers); assert_block_ahi_empty(block); } while (0) # define assert_block_ahi_valid(block) ut_a((block)->index || os_atomic_increment_ulint(&(block)->n_pointers, 0) == 0) #else /* UNIV_AHI_DEBUG || UNIV_DEBUG */ # define assert_block_ahi_empty(block) /* nothing */ # define assert_block_ahi_empty_on_init(block) /* nothing */ # define assert_block_ahi_valid(block) /* nothing */ #endif /* UNIV_AHI_DEBUG || UNIV_DEBUG */ unsigned curr_n_fields:10;/*!< prefix length for hash indexing: number of full fields */ unsigned curr_n_bytes:15;/*!< number of bytes in hash indexing */ unsigned curr_left_side:1;/*!< TRUE or FALSE in hash indexing */ dict_index_t* index; /*!< Index for which the adaptive hash index has been created, or NULL if the page does not exist in the index. Note that it does not guarantee that the index is complete, though: there may have been hash collisions, record deletions, etc. */ /* @} */ bool made_dirty_with_no_latch; /*!< true if block has been made dirty without acquiring X/SX latch as the block belongs to temporary tablespace and block is always accessed by a single thread. */ bool skip_flush_check; /*!< Skip check in buf_dblwr_check_block during bulk load, protected by lock.*/ # ifdef UNIV_DEBUG /** @name Debug fields */ /* @{ */ rw_lock_t debug_latch; /*!< in the debug version, each thread which bufferfixes the block acquires an s-latch here; so we can use the debug utilities in sync0rw */ /* @} */ # endif BPageMutex mutex; /*!< mutex protecting this block: state (also protected by the buffer pool mutex), io_fix, buf_fix_count, and accessed; we introduce this new mutex in InnoDB-5.1 to relieve contention on the buffer pool mutex */ #endif /* !UNIV_HOTBACKUP */ };
判断索引对象是否满足自适应哈希索引
在判断是否需要对索引对象创建自适应哈希索引前,需要先判断索引是否被访问17次,btr_search_t对象中hash_analysis统计索引使用次数
/** Updates the search info. */ UNIV_INLINE void btr_search_info_update( dict_index_t *index, /*!< in: index of the cursor */ btr_cur_t *cursor) /*!< in: cursor which was just positioned */ { ut_ad(!rw_lock_own(btr_get_search_latch(index), RW_LOCK_S)); ut_ad(!rw_lock_own(btr_get_search_latch(index), RW_LOCK_X)); if (dict_index_is_spatial(index) || !btr_search_enabled) { return; } btr_search_t *info; info = btr_search_get_info(index); info->hash_analysis++; if (info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS) { /* Do nothing */ return; } ut_ad(cursor->flag != BTR_CUR_HASH); btr_search_info_update_slow(info, cursor); }
BTR_SEARCH_HASH_ANALYSIS被硬编码在代码中:
/** After change in n_fields or n_bytes in info, this many rounds are waited before starting the hash analysis again: this is to save CPU time when there is no hope in building a hash index. */ #define BTR_SEARCH_HASH_ANALYSIS 17
判断数据页对象是否满足自适应哈希索引
当索引被访问17次后,则开始对索引上的数据页进行访问模式统计,当满足下列条件时则对相应的数据页创建自适应哈希索引:
- 索引上按照相同查询模式连续访问100次
- 数据页上按照相同查询模式访问超过"当前数据页记录数的1/16" 次
/** The global limit for consecutive potentially successful hash searches, before hash index building is started */ #define BTR_SEARCH_BUILD_LIMIT 100 /** If the number of records on the page divided by this parameter would have been successfully accessed using a hash index, the index is then built on the page, assuming the global limit has been reached */ #define BTR_SEARCH_PAGE_BUILD_LIMIT 16 /** Update the block search info on hash successes. NOTE that info and block->n_hash_helps, n_fields, n_bytes, left_side are NOT protected by any semaphore, to save CPU time! Do not assume the fields are consistent. @return true if building a (new) hash index on the block is recommended @param[in,out] info search info @param[in,out] block buffer block @param[in] cursor cursor */ static ibool btr_search_update_block_hash_info(btr_search_t *info, buf_block_t *block, const btr_cur_t *cursor) { ut_ad(!rw_lock_own(btr_get_search_latch(cursor->index), RW_LOCK_S)); ut_ad(!rw_lock_own(btr_get_search_latch(cursor->index), RW_LOCK_X)); ut_ad(rw_lock_own(&block->lock, RW_LOCK_S) || rw_lock_own(&block->lock, RW_LOCK_X)); info->last_hash_succ = FALSE; ut_a(buf_block_state_valid(block)); ut_ad(info->magic_n == BTR_SEARCH_MAGIC_N); if ((block->n_hash_helps > 0) && (info->n_hash_potential > 0) && (block->n_fields == info->n_fields) && (block->n_bytes == info->n_bytes) && (block->left_side == !!info->left_side)) { if ((block->index) && (block->curr_n_fields == info->n_fields) && (block->curr_n_bytes == info->n_bytes) && (block->curr_left_side == info->left_side)) { /* The search would presumably have succeeded using the hash index */ info->last_hash_succ = TRUE; } block->n_hash_helps++; } else { block->n_hash_helps = 1; block->n_fields = info->n_fields; block->n_bytes = info->n_bytes; block->left_side = info->left_side; } #ifdef UNIV_DEBUG if (cursor->index->table->does_not_fit_in_memory) { block->n_hash_helps = 0; } #endif /* UNIV_DEBUG */ if ((block->n_hash_helps > page_get_n_recs(block->frame) / BTR_SEARCH_PAGE_BUILD_LIMIT) && (info->n_hash_potential >= BTR_SEARCH_BUILD_LIMIT)) { if ((!block->index) || (block->n_hash_helps > 2 * page_get_n_recs(block->frame)) || (block->n_fields != block->curr_n_fields) || (block->n_bytes != block->curr_n_bytes) || (block->left_side != block->curr_left_side)) { /* Build a new hash index on the page */ return (TRUE); } } return (FALSE); }