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 在读场景下的开销也是比较大的。
while (rc == NESTED_LOOP_OK && join->return_tab >= join_tab) { int error; if (in_first_read) { in_first_read= false; error= (*join_tab->read_first_record)(join_tab); } else error= info->read_record(info); /* load data from engine */ rc= evaluate_join_record(join, join_tab); /* computed by server */ }
AHI 功能作用
????由以上的分析可以看到 MySQL 一次定位 cursor 的过程即是从根结点到叶子结点的路径,时间复杂度为:height(index) + [CPU cost time],上述的两个优化过程无法省略定位 cursor 的中间结点,因此需要引入一种可以从 search info 定位到叶子结点的方法,从而省略根结点到叶子结点的路径上所消耗的时间,而这种方法即是 自适应索引(Adaptive hash index, AHI)。查询语句使用 AHI 的时候有以下优点: