原标题:巧用MySQL AHI加速神器,让你的InnoDB查询飞起来!

作者:张青林,腾讯云布道师、MySQL架构师,隶属腾讯TEG-基础架构部-CDB内核开发团队,专注于MySQL内核研发&相关架构工作,有着服务多个10W级QPS客户的数据库优化及稳定性维护经验。

腾讯云数据库团队:继承腾讯数据库团队十多年海量存储的内部数据库运营和运维经验,推出一系列高性能关系型、分布式、文档型和缓存类数据库产品,并提供高可用性、自动化运维和易维护的云数据库综合解决方案。

MySQL 定位用户记录的过程可以描述为:

打开索引 -> 根据索引键值逐层查找 B+ 树 branch 结点 -> 定位到叶子结点,将 cursor 定位到满足条件的 rec 上;

如果树高为 N,则需要读取索引树上的 N 个结点并进行比较,如果 buffer_pool 较小,则大量的操作都会在 pread 上,用户响应时间变长;

另外,MySQL 中 Server 层与 Engine 之间的是以 row 为单位进行交互的,engine 将记录返回给 server 层,server 层对 engine 的行数据进行相应的计算,然后缓存或发送至客户端,为了减少交互过程所需要的时间,MySQL 做了两个优化:

如果同一个查询语句连续取出了 MYSQL_FETCH_CACHE_THRESHOLD(4) 条记录,则会调用函数 row_sel_enqueue_cache_row_for_mysql 将 MYSQL_FETCH_CACHE_SIZE(8) 记录缓存至 prebuilt->fetch_cache 中,在随后的 prebuilt->n_fetch_cached 次交互中,都会从prebuilt->fetch_cache 中直接取数据返回到 server 层,那么问题来了,即使是用户只需要 4 条数据,Engine 层也会将 MYSQL_FETCH_CACHE_SIZE 条数据放入 fetch_cache 中,造成了不必要的缓存使用。另外,5.7 可以根据用户的设置来调整缓存用户记录的条数;

Engine 取出数据后,会将 cursor 的位置保存起来,当取下一条数据时,会尝试恢复 cursor 的位置,成功则并继续取下一条数据,否则会重新定位 cursor 的位置,从而通过保存 cursor 位置的方法可以减少 server 层 & engine 层交互的时间;

Server 层 & engine 层交互的过程如下,由于 server & engine 的 row format 不一样,那么 engine row format -> server row format 在读场景下的开销也是比较大的。

1

AHI 功能作用

由以上的分析可以看到 MySQL 一次定位 cursor 的过程即是从根结点到叶子结点的路径,时间复杂度为:height(index) + [CPU cost time],上述的两个优化过程无法省略定位 cursor 的中间结点,因此需要引入一种可以从 search info 定位到叶子结点的方法,从而省略根结点到叶子结点的路径上所消耗的时间,而这种方法即是自适应索引(Adaptive hash index, AHI)。查询语句使用 AHI 的时候有以下优点:

可以直接通过从查询条件直接定位到叶子结点,减少一次定位所需要的时间;

在 buffer pool 不足的情况下,可以只针对热点数据页建立缓存,从而避免数据页频繁的 LRU;

但是 AHI 并不总能提升性能,在多表Join & 模糊查询 & 查询条件经常变化的情况下,此时系统监控 AHI 使用的资源大于上述的好处时,不仅不能发挥 AHI 的优点,还会为系统带来额外的 CPU 消耗,此时需要将 AHI 关闭来避免不必要的系统资源浪费,关于 AHI 的适应场景可以参考:https://docs.oracle.com/cd/E17952_01/mysql-5.6-en/innodb-adaptive-hash.html

2

AHI 内存结构

AHI 会监控查询语句中的条件并进行分析(稍后会进行详细的介绍),当满足 AHI 缓存建立的条件后,会选择索引的若干前缀索引列对热点数据页组建 hash page 以记录 hash value -> page block 之间的对应关系,本小节主要对 AHI 的内存结构 & 内存来源进行相应的介绍,其内存结构如图:

上图是 AHI 的一个内存结构示意图,AHI 主要使用以下两种内存:

系统初始化分配的 hash_table 的内存,其中每一个 hash_table 的数组大小为:(buf_pool_get_curr_size() / sizeof(void*) / 64),根据机器位数的不同,数组大小不同, 32位机器为 buffer_pool大小的 1/256, 64 位机器为 buffer_pool 大小的 1/512, 此部分内存为系统内存(mem_area_alloc->malloc),主要用于构建 hash_table 结构;

当 AHI 对数据页面构造 AHI 缓存时,此时使用 buffer_pool 的 free 链接中的内存,即 buffer_pool 的内存,所以在页数据发生变化的时候,需要对 AHI 缓存进行相应的维护;

3

AHI 实现解析

AHI在查询过程中的作用范围

MySQL 中 Server & InnoDB 的交互中是以行为单位进行交互的,InnoDB逐行取数据的过程可以分为以下 6 个步骤:

如果发现其它线程需要对btr_search_latch上锁,则释放 btr_search_latch,然后执行 1; (5.6 & 5.7 在实现上不同)

尝试从 row_prebuilt_t->fetch_cache 中取数据库记录,有则直接返回,如果没有数据或者不可以使用 fetch cache, 则执行2;

在满足条件的情况下,使用 AHI 定位 cursor 位置并返回数据, 否则执行 3;

根据 direction 的值确认是否可以从 row_prebuilt_t中恢复 cursor 的位置,如果 direction = 0 或不可以从 row_prebuilt_t中恢复 cursor 的位置, 则调用 btr_pcur_open_at_index_side 打开索引,调用 btr_cur_search_to_nth_level,如果可以使用 AHI,则快速定位叶子结点,否则遍历 height(index) 个结点定位 cursor, 然后进入 4;如果可以从 row_prebuilt_t 恢复则执行 5;

根据查找的值在叶子结点中逐个匹配,查找满足条件的记录,返回数据,取下一条记录时执行 3,5;

移动 cursor 到下一条记录并返回数据。

AHI 则在第 [2, 3] 两个步骤中影响着定位叶子结点的过程,根据查询条件定位叶子节点的过程中发挥着 hash 的作用,AHI 的实现主要包括 AHI 初始化过程、构建条件、使用过程、维护过程、系统监控等部分,我们从源码的实现的角度上分析上述过程。

AHI初始化过程

AHI 作为 buffer_pool 的一部分,是建立查询条件与 REC 在内存中位置的一个 hash_table, 在系统启动的时候会随着 buffer_pool 的初始化而自动的建立相应的内存结构,其初始化过程为:

利用系统内存 (malloc) 创建全局变量 btr_search_sys 及其锁结构;

利用系统内存 (malloc) 建立 hash_table 内存结构,并初始化其成员变量,其中 hash_table 数组的大小取决于当前 buffer_pool 的 size 与 系统的机器位数,计算公式为:buf_pool_get_curr_size() / sizeof(void*) / 64,hash_table_t 的结构如下所示:

说明:

所有 buffer_pool instances 共享一个 AHI, 而不是每一个 buffer_pool instance 一个 AHI;

5.7.8 之前 AHI 只有一个全局的锁结构 btr_search_latch,当压力比较大的时候会出现性能瓶颈,5.7.8 对 AHI 进行了拆锁处理,详情可以参考函数: btr_get_search_table() & btr_search_sys_create();

AHI 的 btr_search_latch (bug#62018) & index lock 是MySQL中两个比较大的锁(详情可以参考:https://www.percona.com/blog/2010/02/25/index-lock-and-adaptive-search-next-two-biggest-innodb-problems/)。5.7 通过对 AHI 锁拆分 (5.7 commit id: ab17ab91) 以及引入不同的索引锁协议 (WL#6326) 解决了这两个问题。

AHI构建条件

AHI 是建立在 search info & REC 内存地址之间的映射信息,在系统接受访问之前并没有足够的信息来建立 AHI 的映射信息,所以需要搜集 SQL 语句在执行过程中的 search_info & block info 信息并判断是否可以为数据页建立 AHI 缓存,其中:

search info 对应 btr_search_t, 用于记录 index 中的 n_fields (前缀索引列数) & n_bytes(last column bytes) 信息,这些被用于计算 fold 值;

block info 用于记录计算 fold 的值所需要的 fields & bytes 之外,还记录了在此情况下使用 AHI 在此数据页上潜在成功的次数;

我们简单地对AHI统计信息的几个方面进行描述。

触发 AHI 索引统计的条件

SQL 语句在定位 cursor 的过程中会执行 btr_cur_search_to_nth_level 函数,当打开 AHI 的时候,在btr_cur_search_to_nth_level 返回之前会调用 btr_search_info_update 来更新相应的统计信息,如果当前的索引的 serch_info->hash_analysis < BTR_SEARCH_HASH_ANALYSIS (17),则对 search info & block info 不进行统计,否则则会调用 btr_search_info_update_slow 更新 search info & block info 信息,实现如下:

AHI中索引查询信息(index->search_info)的更新与自适应的过程

背景知识:

btr_cur_search_to_nth_level 中在定位 cursor 的过程中会在树的每一层调用 page_cur_search_with_match 来确定下一个 branch 结点或叶子结点,page_cur_search_with_match 函数会将查询过程中比较的前缀索引列数 & 最后一列匹配的字节数记录至 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match},目的是为了保存与 search tuple 在比较过程时的最小比较单元,详细的计算过程可以参考 page_cur_search_with_match 的实现代码。

首先判断当前 index 是否为 insert buffer tree,如果是 insert buffer,则不进行 AHI 等相关的操作;

其次,如果当前索引的 info->n_hash_potential = 0,则会按照推荐算法从 {cursor->up_match, cursor->up_bytes, cursor->low_bytes, cursor->low_match} 推荐出前缀索引列数 & 最后一列的字节数用于计算 AHI 中存储的键 {ha_node_t->fold} 的值。

当 info->n_hash_potential != 0 时,则会判断当前查询匹配模式 & index->search_info 中保存的匹配模式是否发生变化,如果没有发生变化,则会增加此模式下潜在利用 AHI 成功的次数 (info->n_hash_potential),否则需要重新推荐前缀索引列等相关信息,并清空 info->n_hash_potential 的值(info->n_hash_potential = 0),AHI 就是利用这种方法来实现自适应的,所以在打开 AHI 的系统中不建议经常变换查询条件,前缀索引等信息的计算过程如下:

由以上算法可以看出,选择{info->n_fields, info->n_bytes, info->left_side}的依据则是在不超过 unique index 列数的前提下,使其计算代价最小,而 index->info->left_side 的值则会决定存储同一数据页上相同前缀索引的最左记录还是最右记录。

数据页block信息的更新

数据页 block info 的更新主要包括数据页上的索引匹配模式、在已有索引匹配模式下成功的次数以及是否为该数据页建立 AHI 缓存信息的判断,其主要过程如下:

将 index->info->last_hash_succ 设置为 FALSE,此时其它线程无法使用该索引上 AHI 功能;

如果 index->search_info 的匹配格式 & 该数据页上保存的匹配模式相同时,则增加此 block 使用 AHI 成功的次数 block->n_hash_helps, 如果已经为该数据页建立 AHI 缓存,则设置 index->info->last_hash_succ = TRUE;

如果 index->search_info 的匹配格式 & 该数据页上保存的匹配模式不相同,则设置 block->n_hash_helps=1 且使用 index->search_info 对 block 上的索引匹配信息进行重新设置,详细过程可参考 btr_search_update_block_hash_info;

判断是否需要为数据页建立 AHI 缓存,在数据页 block 上使用 AHI 成功的次数大于此数据页上用户记录的 1/16 且当前前缀索引的条件下使用 AHI 成功的次数大于 100 时, 如果此数据页使用 AHI 潜在成功的次数大于 2 倍该数据页上的用户记录或者当前推荐的前缀索引信息发生了变化的时,则需要为数据页构造 AHI 缓存信息,详情可参考以下代码;

AHI构建过程(收集&判断&建立)

AHI 的构建过程指的是根据 index->search_info 构建查询条件 & 数据页的 hash 关系,其主要过程为:

1、收集 hash 信息。遍历该数据页上的所有用户记录,建立由前缀索引信息&物理记录之间的映射关系的数组{folds, recs},其中index->info->left_side用来判断在前缀索引列相同情况下如何保存物理页记录,从代码中可以得知:当left_side为TRUE时前缀索引列相同的记录只保存最左记录,当left_side为FALSE时前缀索引列相同的记录只保存最右记录,代码实现如下:

2、如果之前该数据页已经存在 AHI 缓存信息但前缀索引信息与当前的信息不一致,则释放之前缓存的 AHI 信息,如果释放超过了一个 page size,则将释放的数据页退还给 buffer_pool->free 链表;

3、调用 btr_search_check_free_space_in_heap 来确保 AHI 有足够的内存生成映射信息 ha_node_t {fold, data, next},该内存从 buffer_pool->free 链表获得,详情参考:buf_block_alloc(), fold 的值的计算可参考函数:rec_fold();

4、由于操作过程中释放了 btr_search_latch,需要再次检查 block 上的AHI信息是否发生了变化,如果发生变化则退出函数;

5、调用 ha_insert_for_fold 方法将之前收集的信息生成 ha_node_t, 并将其存放到 btr_search_sys->hash_table 的数组中,其中存放后的结构可以参考图 AHI memory structure;

for (i = 0; i < n_cached; i++) {

ha_insert_for_fold(table, folds[i], block, recs[i]);

}

AHI使用条件及定位叶子结点过程

在“AHI 在查询过程中的作用范围”一节中我们详细的介绍了 MySQL 中 Server 层 & engine 层中的交互方式以及 AHI 在整个过程中的位置 & 作用,下面着要看一下在 步骤 2、3 中 AHI 是如何工作的。

步骤 2 中,是使用 AHI 的一种 shortcut 查询方式,只有在满足很苛刻的条件后才能使用 AHI 的 shortcut 查询方式,这些苛刻条件包括:

当前索引是 cluster index;

当前查询是 unique search;

当前查询不包含 blob 类型的大字段;

记录长度不能大于 page_size/8;

不是使用 memcache 接口协议的查询;

事物开启且隔离级别大于 READ UNCOMMITTED;

简单 select 查询而非在 function & procedure;

在满足以上条件后才能使用 AHI 的 shortcut 查询方式定位叶子结点,5.7 中满足条件后的操作可以简单的描述为:

rw_lock_s_lock(btr_get_search_latch(index));

...

row_sel_try_search_shortcut_for_mysql()

...

rw_lock_s_lock(btr_get_search_latch(index));

步骤 3 中使用 AHI 快速定位叶子结点同样需要满足一些条件,具体可以参考代码:btr_cur_search_to_nth_level(),在此不再累述,我们着重分析一下使用 AHI 定位叶子节点的过程。

1、对 index 所在的 hash_table 上锁,使用查询条件中的 tuple 信息计算出键值 fold;

rw_lock_s_lock(btr_search_get_latch(index));

fold = dtuple_fold(tuple, cursor->n_fields, cursor->n_bytes, index_id);

2、在 hash_table 上进行查找 key = fold 的 ha_node_t;

3、释放锁资源并根据返回的记录定位叶子结点;

block = buf_block_align(rec);

rw_lock_s_unlock(btr_search_get_latch(index));

btr_cur_position(index, (rec_t*) rec, block, cursor);

4、定位到叶子结点后的过程和不使用 AHI 之后的过程类似,直接返回记录并记录 cursor 位置。

AHI维护&监控

MySQL 5.7 中有两个 AHI 相关的参数,分别为:

innodb_adaptive_hash_index, innodb_adaptive_hash_index_parts,其中 innodb_adaptive_hash_index 为动态调整的参数,用以控制是否打开 AHI 功能;innodb_adaptive_hash_index_parts 是只读参数,在实例运行期间是不能修改,用于调整 AHI 分区的个数(5.7.8 引入),减少锁冲突,详细介绍可以参考官方说明:innodb_adaptive_hash_index及innodb_adaptive_hash_index,本节主要介绍操作 AHI 的相关命令以及命令的内部实现过程。

1)打开AHI操作&内部实现

set global innodb_adaptive_hash_index=ON,此命令只是对全局变量进行设置,代码实现如下:

2)关闭AHI操作&内部实现

set global innodb_adaptive_hash_index= OFF,此命令用于关闭 AHI 功能,具体实现可参考 btr_search_disable(), 关闭流程说明:

设置 btr_search_enabled = FALSE,关闭 AHI 功能;

将数据字典中所有缓存的表对象的 ref_count 设置为0,只有 btr_search_info_get_ref_count(info, index) = 0 的情况下才能清除数据字典中的缓存对象,详情见 dict_table_can_be_evicted();

将所有数据页中的统计信息置空,具体实现可参考 buf_pool_clear_hash_index();

释放 AHI 所使用的 buffer_pool 的内存,btr_search_disable 具体实现如下:

3) AHI缓存信息的维护

AHI 维护的是 search info & REC 在物理内存地址的 hash 关系,当物理记录的位置或者所在 block 的地址发生变化时,AHI 也需要对其进行相应的维护,如新记录的的插入,表记录的的删除,数据页的分裂,drop table & alter table,LRU 换页等都需要对 AHI 进行相应的维护,详情可参考函数 btr_search_update_hash_ref() & btr_search_drop_page_hash_index() & buf_LRU_drop_page_hash_for_tablespace()的实现;

4) AHI信息的监控

AHI 默认情况下只对 adaptive_hash_searches (使用 AHI 方式查询的次数) & adaptive_hash_searches_btree (使用 bree 查询的次数,需要遍历 branch 结点) 进行监控,更详细的监控需要进行额外的设置,详细设置方法可参考 innodb_monitor_enable &module_adaptive_hash,打开 AHI 的监控方法、使用监控、重置监控的方法如下:

值得一提的是只有执行 set global innodbmonitor_reset_all='adaptive_hash%' & set global innodb_monitor_disable='adaptive_hash%' 才对状态进行重置,如果发现 adaptive_hash_searches << adaptive_hash_searches_btree 的时候,则应该关闭 AHI 以减少不必要的系统消耗。

相关专题:

精选专题(官网:dbaplus.cn)

◆近期热文 ◆

◆MVP专栏 ◆

丨丨丨丨

责任编辑:

mysql加速搜索_巧用MySQL AHI加速神器,让你的InnoDB查询飞起来!相关推荐

  1. mysql替换开头_如何在MySQL的字符串开头搜索和替换特定字符?

    为此,您可以使用INSERT().让我们首先创建一个表-mysql> create table DemoTable -> ( -> ZipCode varchar(200) -> ...

  2. 删除安装的mysql数据库文件_安装/删除MySQL数据库

    安装包我会放在本文的底部,如果需要自取 MySQL的数据存储目录为data,data目录通常在C:\Documents and Settings\All Users\Application Data\ ...

  3. mysql数据库算法_数据库:MySQL索引背后的数据结构及算法原理【转】

    原文:http://blog.codinglabs.org/articles/theory-of-mysql-index.html 摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话 ...

  4. sphinx mysql存储引擎_基于Sphinx+MySQL的千万级数据全文检索(搜索引擎)架构设计...

    Sphinx,单一索引最大可包含1亿条记录,在1千万条记录情况下的查询速度为0.x秒(毫秒级).Sphinx创建索引的速度为:创建100万条记录的索引只需3-4分钟,创建1000万条记录的索引可以在5 ...

  5. mysql 字典索引_【大白话mysql】你真的了解 mysql 索引吗?

    本文来源于公众号: 跬步匠心 什么是索引? 当我们使用汉语字典查找某个字时,我们会先通过拼音目录查到那个字所在的页码,然后直接翻到字典的那一页,找到我们要查的字,通过拼音目录查找比我们拿起字典从头一页 ...

  6. java mysql mac 安装_最新版MySQL在MacOS上的安装与使用!

    在 MacOS 上安装最新版的 MySQL 有三种方法:使用 Docker 安装: 使用 Homebrew 运行 brew install mysql 安装: 使用安装包安装. 我们本文将采用最常规的 ...

  7. mysql workbench企业_甲骨文发布MySQL Workbench 6.0版本

    甲骨文近日发布了最新的MySQL Workbench 6.0,该版本的用户界面得到了重新设计并拥有新功能,从而简化MySQL数据库的开发.设计和管理. MySQL Workbench是世界上最流行的开 ...

  8. mysql账号认证_浅谈MySQL用户账号认证方式

    为了有效控制数据库用户的访问权限,在MySQL数据库中创建了一个新用户,但使用刚创建的用户和密码却发现连接不了MySQL数据库,通过查看官网手册及<MySQL技术内幕>一书,才逐渐熟悉My ...

  9. mysql 闪电存储_详解MySQL中EXPLAIN解释命令

    explain显示了mysql如何使用索引来处理select语句以及连接表.可以帮助选择更好的索引和写出更优化的查询语句. 使用方法,在select语句前加上explain就可以了: 如: expla ...

  10. 远程连接mysql拒绝访问_远程连接 Mysql 失败的解决方法

    原标题:远程连接 Mysql 失败的解决方法 今天在虚拟机Ubuntu上折腾了一晚上mysql,然后试着用java连接,搞了很久都没成功,但是同学配好的Debian上却连接成功了,也就是说我的配置有问 ...

最新文章

  1. 澳洲计算机学,2020年澳洲计算机科学专业工作好找吗
  2. java整数转二进制字符串_在Java中将int转换为二进制字符串表示形式?
  3. 20155213 2016-2017-2 《Java程序设计》第七周学习总结
  4. redis缓存的安装和使用
  5. linux sendto 对方关闭后性能,Gateway的请问sendToUid是否有性能问题?
  6. 鸿蒙手机系统开发大会,鸿蒙OS+EMUI10,华为开发者大会的创新与看点
  7. GraphQL引入依赖maven仓库配置
  8. 微信小程序电商系统省市区选择器(二)
  9. P1433 吃奶酪(状压dp)
  10. 身于“乱世”,我们程序员应该如何打算?
  11. Javascript框架 - ExtJs - 类
  12. python 知乎关系图谱_5000行python代码+可视化60W数据,告诉你知乎用户不为人知的事...
  13. netty如何知道连接已经关闭,socket心跳,双工?异步?
  14. sqrt方法复杂度探讨
  15. 如何搭建一个公司网站?
  16. mysql字符集校对_MySQL字符集与校对
  17. UE4在场景中画线(蓝图与C++)的实现方式
  18. electron_笔记
  19. php自动生成网站地图txt,织梦网站地图生成插件+发布文章后自动生成地图
  20. mysql用天兔监测什么_天兔监控服务器和mysql,redis等监控

热门文章

  1. 地震观测仪器的历史和发展趋势(二)
  2. 上海康桥先进制造技术创业园项目远程预付费系统的应用-安科瑞耿敏花
  3. 用AI让逝去的亲人照片动起来后,数百万网友泪目:原来思念这么重
  4. 将.bat文件设置为Window系统开机自启动项
  5. 前端-微信、企业微信JS-SDK实时调试方法
  6. 股票自动买卖 java_股票如何实现程序化交易和自动交易?
  7. 360的困兽之斗——探讨奇虎Tecent商业模式
  8. codeforce 760 B Frodo and pillows 二分搜索
  9. 中台详解(上)-什么是中台
  10. Oracle OAF 学习小结(1)- 个性化详解