在复杂条件搜索上,为什么MySQL只能被ES吊打?( 二 )


在复杂条件搜索上,为什么MySQL只能被ES吊打?

文章插图
一般来说,Term Index 都是全部缓存在内存中,查询时,先通过其快速定位到 Term Dictionary 对应的大致范围,然后再进行磁盘读取查找对应的 Term,这样就大大减少了磁盘 I/O 的次数 。
联合索引查询
了解了 ElasticSearch 的倒排索引后,我们再来看看其如何处理复杂的联合索引查询 。比如上述书籍例子中,我们需要查询评分等于2.2并且作者名称叫 Tom的书籍 。
理论上,我们只需要分别按照 Score 和 Author 字段的倒排索引进行查询,获取响应的 Posting List,再将其做交集合并即可 。
这里又要吐槽一下 MySQL,它是不支持这个合并操作的,它只能按照一个字段的索引进行查询,然后根据另外一个字段的条件做内存过滤 。顺便说一下,MySQL 的 join 功能也弱爆了,感兴趣的同学可以了解一下这篇文章
而 ElasticSearch 则支持使用跳表 Skip List和 Bitset 的方式将数据集进行合并 。
使用 Skip List 结构,同时遍历 Score 和 Author 查询出来的 Posting List,利用其 Skip List 结构,相互跳跃对比,得出合集 。
使用 Bitset 结构,对 Score 和 Author 查询出来的 Posting List 的值计算出各自的 Bitset,然后进行 AND 操作 。
跳表合并策略
ElasticSearch 在存储 Posting List 数据时,就保存了对应的多级跳表结构响应的数据,这也体现了其空间换时间的基本思想 。
这里先介绍一下跳表的基本概念,它其实是一种可以进行二分查找的有序链表 。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找 。首先在最高级索引上查找最后一个小于当前查找元素的位置,然后再跳到次高级索引继续查找,直到跳到最底层为止,通过这种方式,加快了查询的速度 。
比如,按照 Score 查出来的 Posting List 为[2,3,4,5,7,9,10,11],按照 Author 查出来的结果为 [3,8,9,12,13],则二者的跳表结构如下图所示 。
在复杂条件搜索上,为什么MySQL只能被ES吊打?

文章插图
具体合并过程则是先选最短的 posting list,也就是 Author 的结果集,从其最小的一个 id 开始,将其作为当前最大值 。然后依次剩余 posting list 中查找大于或等于该值的位置 。
比如上述结果集中,先去 Score 结果集中查找 3,找到后,就表明 3是二者的合集元素之一;然后再重新开启一轮,选取 Author 结果集中 3 的下一个值 8,去 Score 结果集查询 8,发现了大于等于 8 的最小的值是 9,所以不可能有共同的值 8,然后再去 Author 结果集查找 9,发现其大于等于 9 的最小值是 12,所以再去 Score 结果集中查找大于等于 12的值,发现并不存在;最终得出二者的合集就只有[3] 。
在查询过程中,每个 posting list 都可以根据当前 id 通过 skip list 快速跳过不符合的 id 值,加速整个合并取交集的过程 。
ElasticSearch 对于较长的 posting list 也会使用 Frame Of Reference 进行压缩编码,减少了磁盘占用,减少了索引尺寸 。有关具体存储结构的实现我们后续再进行细聊 。
Bitset 合并策略
ElasticSearch除了使用 skipList 来进行数据磁盘读取时的合并操作外,还会将一些查询条件对应的结果集 posting list 进行内存缓存,也就是所谓的 Filter Cache,为了后续再次复用 。
为了减少内存缓存所消耗的内存空间大小,ElasticSearch 没有使用单纯的数组和 bitset 来存储 posting list,而是使用要压缩效率更高的 Roaring Bitmap 。
我们可以先来讲一下单纯数组或 bitset 数据结构为什么并不使用 。比如如下一道较为常见的面试题目:
给定含有40亿个不重复的位于[0, 2^32 - 1]区间内的整数的集合,如何快速判定某个数是否在该集合内?
如果我们要使用 unsigned long 数组来存储它的话,也就需要消耗 40亿 * 32 位 = 160 Byte,大致是 16000 MB 。
如果要使用位图 Bitset 来存储的话,即某个数位于原集合内,就将它对应的位图内的比特置为1,否则保持为0 。这样只需要消耗 2 ^ 32 位 = 512 MB,这可只有原来的 3.2 % 左右 。
但是,Bitset 也有其缺陷,也就是稀疏存储的问题,比如上述集合并不是 40亿,而是只有2,3个,那么 Bitset 中只有少数几位是1,其他位都是 0,但是它仍然占用了 512 MB 。
而 RoaringBitmap 就是为了解决稀疏存储的问题 。下图就是 RoaringBitmap 的基本原理示意图 。
在复杂条件搜索上,为什么MySQL只能被ES吊打?

文章插图
首先,如上图所示,计算出32位无符号整数和 65536 的除数和余数 。其含义表示,将32位无符号整数按照高16位分桶,即最多可能有2^16=65536个桶,术语惩治为 container 。存储数据时,按照数据的高16位找到 container(找不到就会新建一个),再将低16位放入container中 。也就是说,一个 RoaringBitmap 就是很多container的集合 。


推荐阅读