ElasticSearch 索引 VS MySQL 索引
前言
这段时间在维护产品的搜索功能,每次在管理台看到 elasticsearch
这么高效的查询效率我都很好奇他是如何做到的。
这甚至比在我本地使用 MySQL
通过主键的查询速度还快。
为此我搜索了相关资料:
这类问题网上很多答案,大概意思呢如下:
- ES 是基于
Lucene
的全文检索引擎,它会对数据进行分词后保存索引,擅长管理大量的索引数据,相对于MySQL
来说不擅长经常更新数据及关联查询。
说的不是很透彻,没有解析相关的原理;不过既然反复提到了索引,那我们就从索引的角度来对比下两者的差异。
MySQL 索引
先从 MySQL
说起,索引这个词想必大家也是烂熟于心,通常存在于一些查询的场景,是典型的空间换时间的案例。
以下内容以 Innodb 引擎为例。
常见的数据结构
假设由我们自己来设计 MySQL
的索引,大概会有哪些选择呢?
散列表
首先我们应当想到的是散列表,这是一个非常常见且高效的查询、写入的数据结构,对应到 Java
中就是 HashMap
这个数据结构应该不需要过多介绍了,它的写入效率很高O(1)
,比如我们要查询 id=3
的数据时,需要将 3 进行哈希运算,然后再这个数组中找到对应的位置即可。
但如果我们想查询 1≤id≤6
这样的区间数据时,散列表就不能很好的满足了,由于它是无序的,所以得将所有数据遍历一遍才能知道哪些数据属于这个区间。
有序数组
有序数组的查询效率也很高,当我们要查询 id=4
的数据时,只需要通过二分查找也能高效定位到数据O(logn)
。
同时由于数据也是有序的,所以自然也能支持区间查询;这么看来有序数组适合用做索引咯?
自然是不行,它有另一个重大问题;假设我们插入了 id=2.5
的数据,就得同时将后续的所有数据都移动一位,这个写入效率就会变得非常低。
平衡二叉树
既然有序数组的写入效率不高,那我们就来看看写入效率高的,很容易就能想到二叉树;这里我们以平衡二叉树为例:
由于平衡二叉树的特性:
左节点小于父节点、右节点大于父节点。
所以假设我们要查询 id=11
的数据,只需要查询 10—>12—>11
便能最终找到数据,时间复杂度为O(logn)
,同理写入数据时也为O(logn)
。
但依然不能很好的支持区间范围查找,假设我们要查询5≤id≤20
的数据时,需要先查询10节点的左子树再查询10节点的右子树最终才能查询到所有数据。
导致这样的查询效率并不高。
跳表
跳表可能不像上边提到的散列表、有序数组、二叉树那样日常见的比较多,但其实 Redis
中的 sort set
就采用了跳表实现。
这里我们简单介绍下跳表实现的数据结构有何优势。
我们都知道即便是对一个有序链表进行查询效率也不高,由于它不能使用数组下标进行二分查找,所以时间复杂度是o(n)
但我们也可以巧妙的优化链表来变相的实现二分查找,如下图:
我们可以为最底层的数据提取出一级索引、二级索引,根据数据量的不同,我们可以提取出 N 级索引。
当我们查询时便可以利用这里的索引变相的实现了二分查找。
假设现在要查询 id=13
的数据,只需要遍历 1—>7—>10—>13
四个节点便可以查询到数据,当数越多时,效率提升会更明显。
同时区间查询也是支持,和刚才的查询单个节点类似,只需要查询到起始节点,然后依次往后遍历(链表有序)到目标节点便能将整个范围的数据查询出来。
同时由于我们在索引上不会存储真正的数据,只是存放一个指针,相对于最底层存放数据的链表来说占用的空间便可以忽略不计了。
平衡二叉树的优化
但其实 MySQL
中的 Innodb
并没有采用跳表,而是使用的一个叫做 B+
树的数据结构。
这个数据结构不像是二叉树那样大学老师当做基础数据结构经常讲到,由于这类数据结构都是在实际工程中根据需求场景在基础数据结构中演化而来。
比如这里的 B+
树就可以认为是由平衡二叉树演化而来。
刚才我们提到二叉树的区间查询效率不高,针对这一点便可进行优化:
在原有二叉树的基础上优化后:所有的非叶子都不存放数据,只是作为叶子节点的索引,数据全部都存放在叶子节点。
这样所有叶子节点的数据都是有序存放的,便能很好的支持区间查询。
只需要先通过查询到起始节点的位置,然后在叶子节点中依次往后遍历即可。
当数据量巨大时,很明显索引文件是不能存放于内存中,虽然速度很快但消耗的资源也不小;所以 MySQL
会将索引文件直接存放于磁盘中。
这点和后文提到 elasticsearch 的索引略有不同。
由于索引存放于磁盘中,所以我们要尽可能的减少与磁盘的 IO(磁盘 IO 的效率与内存不在一个数量级)
通过上图可以看出,我们要查询一条数据至少得进行 4 次IO,很明显这个 IO 次数是与树的高度密切相关的,树的高度越低 IO 次数就会越少,同时性能也会越好。
那怎样才能降低树的高度呢?
我们可以尝试把二叉树变为三叉树,这样树的高度就会下降很多,这样查询数据时的 IO 次数自然也会降低,同时查询效率也会提高许多。
这其实就是 B+ 树的由来。
使用索引的一些建议
其实通过上图对 B+树
的理解,也能优化日常工作的一些小细节;比如为什么需要最好是有序递增的?
假设我们写入的主键数据是无序的,那么有可能后写入数据的 id 小于之前写入的,这样在维护 B+树
索引时便有可能需要移动已经写好数据。
如果是按照递增写入数据时则不会有这个考虑,每次只需要依次写入即可。
所以我们才会要求数据库主键尽量是趋势递增的,不考虑分表的情况时最合理的就是自增主键。
整体来看思路和跳表类似,只是针对使用场景做了相关的调整(比如数据全部存储于叶子节点)。
ES 索引
MySQL
聊完了,现在来看看 Elasticsearch
是如何来使用索引的。
正排索引
在 ES 中采用的是一种名叫倒排索引
的数据结构;在正式讲倒排索引之前先来聊聊和他相反的正排索引
。
以上图为例,我们可以通过 doc_id
查询到具体对象的方式称为使用正排索引
,其实也能理解为一种散列表。
本质是通过 key 来查找 value。
比如通过 doc_id=4
便能很快查询到 name=jetty wang,age=20
这条数据。
倒排索引
那如果反过来我想查询 name
中包含了 li
的数据有哪些?这样如何高效查询呢?
仅仅通过上文提到的正排索引显然起不到什么作用,只能依次将所有数据遍历后判断名称中是否包含 li
;这样效率十分低下。
但如果我们重新构建一个索引结构:
当要查询 name
中包含 li
的数据时,只需要通过这个索引结构查询到 Posting List
中所包含的数据,再通过映射的方式查询到最终的数据。
这个索引结构其实就是倒排索引
。
Term Dictionary
但如何高效的在这个索引结构中查询到 li
呢,结合我们之前的经验,只要我们将 Term
有序排列,便可以使用二叉树搜索树的数据结构在o(logn)
下查询到数据。
将一个文本拆分成一个一个独立Term
的过程其实就是我们常说的分词。
而将所有 Term
合并在一起就是一个 Term Dictionary
,也可以叫做单词词典。
- 英文的分词相对简单,只需要通过空格、标点符号将文本分隔便能拆词,中文则相对复杂,但也有许多开源工具做支持(由于不是本文重点,对分词感兴趣的可以自行搜索)。
当我们的文本量巨大时,分词后的 Term
也会很多,这样一个倒排索引的数据结构如果存放于内存那肯定是不够存的,但如果像 MySQL
那样存放于磁盘,效率也没那么高。
Term Index
所以我们可以选择一个折中的方法,既然无法将整个 Term Dictionary
放入内存中,那我们可以为Term Dictionary
创建一个索引然后放入内存中。
这样便可以高效的查询Term Dictionary
,最后再通过Term Dictionary
查询到 Posting List
。
相对于 MySQL
中的 B+树
来说也会减少了几次磁盘IO
。
这个 Term Index
我们可以使用这样的 Trie树
也就是我们常说的字典树
来存放。
更多关于字典树的内容请查看这里。
如果我们是以 j
开头的 Term
进行搜索,首先第一步就是通过在内存中的 Term Index
查询出以 j
打头的 Term
在 Term Dictionary
字典文件中的哪个位置(这个位置可以是一个文件指针,可能是一个区间范围)。
紧接着在将这个位置区间中的所有 Term
取出,由于已经排好序,便可通过二分查找快速定位到具体位置;这样便可查询出 Posting List
。
最终通过 Posting List
中的位置信息便可在原始文件中将目标数据检索出来。
更多优化
当然 ElasticSearch
还做了许多针对性的优化,当我们对两个字段进行检索时,就可以利用 bitmap
进行优化。
比如现在需要查询 name=li and age=18
的数据,这时我们需要通过这两个字段将各自的结果 Posting List
取出。
最简单的方法是分别遍历两个集合,取出重复的数据,但这个明显效率低下。
这时我们便可使用 bitmap
的方式进行存储(还节省存储空间),同时利用先天的位与
****计算便可得出结果。
[1, 3, 5]
⇒ 10101
[1, 2, 4, 5]
⇒ 11011
这样两个二进制数组求与便可得出结果:
10001
⇒ [1, 5]
最终反解出 Posting List
为[1, 5]
,这样的效率自然是要高上许多。
同样的查询需求在 MySQL
中并没有特殊优化,只是先将数据量小的数据筛选出来之后再筛选第二个字段,效率自然也就没有 ES
高。
当然在最新版的 ES
中也会对 Posting List
进行压缩,具体压缩规则可以查看官方文档,这里就不具体介绍了。
总结
最后我们来总结一下:
通过以上内容可以看出再复杂的产品最终都是基础数据结构组成,只是会对不同应用场景针对性的优化,所以打好数据结构与算法的基础后再看某个新的技术或中间件时才能快速上手,甚至自己就能知道优化方向。
最后画个饼,后续我会尝试按照 ES
倒排索引的思路做一个单机版的搜索引擎,只有自己写一遍才能加深理解。
更好的阅读体验请访问此处:https://www.notion.so/ElasticSearch-VS-MySQL-54bddcc092c64c26b2127f1fb9772a23
你的点赞与分享是对我最大的支持
ElasticSearch 索引 VS MySQL 索引的更多相关文章
- mysql索引总结----mysql 索引类型以及创建
文章归属:http://feiyan.info/16.html,我想自己去写了,但是发现此君总结的非常详细.直接搬过来了 关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基 ...
- ***mysql索引总结----mysql索引类型以及创建
文章归属:http://feiyan.info/16.html,我想自己去写了,但是发现此君总结的非常详细.直接搬过来了 关于MySQL索引的好处,如果正确合理设计并且使用索引的MySQL是一辆兰博基 ...
- 【※索引】mysql索引常用命令
索引分单列索引和组合索引.单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引.组合索引,即一个索包含多个列. MySQL索引类型包括: (1)普通索引 这是最基本的索引,它没 ...
- 【mysql】索引原理-MySQL索引原理以及查询优化
转载:https://www.cnblogs.com/bypp/p/7755307.html 一.介绍 1.什么是索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性 ...
- B-tree B+tree适合文件系统索引和MySQL索引
B-树 B-树,这里的 B 表示 balance( 平衡的意思),B-树是一种多路自平衡的搜索树 它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点.下图是 B-树的简化图. B-树 ...
- 【索引】MySQL索引
一.索引的定义及作用 1. 二.索引的创建及删除 1.1查看表的索引 show index from tblname; 1.2.创建索引 1.22创建普通索引 ALTER TABLE `table_n ...
- 一文读懂 MySQL 索引
1 索引简介 1.1 什么是 MySQL 的索引 官方定义:索引是帮助 MySQL 高效获取数据的数据结构 从上面定义中我们可以分析出索引本质是一个数据结构,他的作用是帮助我们高效获取数据,在正式介绍 ...
- MySQL 索引背后的数据结构及算法原理
本文转载自http://blog.jobbole.com/24006/ 摘要本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引 ...
- mysql索引类型和索引方法
索引类型 mysql索引类型normal,unique,full text的区别是什么? normal:表示普通索引 unique:表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号 ...
随机推荐
- elementui table fixed错位
目前在官方没有解决这个bug前如果有人碰到类似问题.建议大家监听table数据,每次数据得到或改变的时候去重新渲染一下 /* 监听table数据对象 */ watch: { tableDa ...
- Ubuntu 20.04 手动安装 sublime_text 并建立搜索栏图标(解决 Ubuntu 20.04 桌面图标无法双击打开问题)
下载sublime_text_3离线程序包 wget https://download.sublimetext.com/sublime_text_3_build_3211_x64.tar.bz2 #x ...
- Spring security OAuth2.0认证授权学习第二天(基础概念-授权的数据模型)
如何进行授权即如何对用户访问资源进行控制,首先需要学习授权相关的数据模型. 授权可简单理解为Who对What(which)进行How操作,包括如下: Who,即主体(Subject),主体一般是指用户 ...
- IEDA使用Tomcat后控制台中文出现乱码
如下图所示,Intellij IDEA显示中文为乱码, 根据Intellij IDEA控制台输出,Tomcat Log出现乱码,因此可以将问题定位到Tomcat上,具体解决方法: 第一步:打开Tom ...
- JavaWeb实现图片上传功能
首先导入文件上传的jar包 然后在Spring-servlet.xml文件中设置上传文件解析器 <!--上传文件解析器--> <bean id="multipartReso ...
- MySQL 8 安装教程(个人版)+创建用户
Mysql 8的安装教程 解压到指定目录如:D:\WinInstall\mysql-8.0.19-winx64这时候你需要在根目录下创建两个文件,分别是data文件夹和my.ini文件,然后使用编辑器 ...
- 2020重新出发,JAVA高级,JVM种设计模式
Java的23种设计模式全面解析 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路.它不是语法规定,而是一套用来提高代码可复用性.可维护性.可读性.稳健 ...
- 实践案例丨基于 Raft 协议的分布式数据库系统应用
摘要:简单介绍Raft协议的原理.以及存储节点(Pinetree)如何应用 Raft实现复制的一些工程实践经验. 1.引言 在华为分布式数据库的工程实践过程中,我们实现了一个计算存储分离. 底层存储基 ...
- Linux 获取屏幕分辨率与窗口行列数(c/c++)
获取当前分辨率 #include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<s ...
- [深入理解JVM虚拟机]第2章-Java内存区域与内存溢出异常
2.0引-Java内存区域中,栈内存和堆内存分别装什么,为什么? 栈:解决程序的运行问题,即程序如何执行,或者说如何处理数据. 堆:解决的是数据存储的问题,即数据怎么放,放在哪儿. 参考链接https ...