• 关于

    索引搜索什么意思

    的搜索结果

问题

网站在搜索引擎的排名过程

luxiaodi818 2019-12-01 21:09:44 4199 浏览量 回答数 0

回答

简介 ES是一个基于RESTful web接口并且构建在Apache Lucene之上的开源分布式搜索引擎。 同时ES还是一个分布式文档数据库,其中每个字段均可被索引,而且每个字段的数据均可被搜索,能够横向扩展至数以百计的服务器存储以及处理PB级的数据。 可以在极短的时间内存储、搜索和分析大量的数据。通常作为具有复杂搜索场景情况下的核心发动机。 ES就是为高可用和可扩展而生的。一方面可以通过升级硬件来完成系统扩展,称为垂直或向上扩展(Vertical Scale/Scaling Up)。 另一方面,增加更多的服务器来完成系统扩展,称为水平扩展或者向外扩展(Horizontal Scale/Scaling Out)。尽管ES能够利用更强劲的硬件,但是垂直扩展毕竟还是有它的极限。真正的可扩展性来自于水平扩展,通过向集群中添加更多的节点来分担负载,增加可靠性。ES天生就是分布式的,它知道如何管理多个节点来完成扩展和实现高可用性。意味应用不需要做任何的改动。 Gateway,代表ES索引的持久化存储方式。在Gateway中,ES默认先把索引存储在内存中,然后当内存满的时候,再持久化到Gateway里。当ES集群关闭或重启的时候,它就会从Gateway里去读取索引数据。比如LocalFileSystem和HDFS、AS3等。 DistributedLucene Directory,它是Lucene里的一些列索引文件组成的目录。它负责管理这些索引文件。包括数据的读取、写入,以及索引的添加和合并等。 River,代表是数据源。是以插件的形式存在于ES中。  Mapping,映射的意思,非常类似于静态语言中的数据类型。比如我们声明一个int类型的变量,那以后这个变量只能存储int类型的数据。比如我们声明一个double类型的mapping字段,则只能存储double类型的数据。 Mapping不仅是告诉ES,哪个字段是哪种类型。还能告诉ES如何来索引数据,以及数据是否被索引到等。 Search Moudle,搜索模块,支持搜索的一些常用操作 Index Moudle,索引模块,支持索引的一些常用操作 Disvcovery,主要是负责集群的master节点发现。比如某个节点突然离开或进来的情况,进行一个分片重新分片等。这里有个发现机制。 发现机制默认的实现方式是单播和多播的形式,即Zen,同时也支持点对点的实现。另外一种是以插件的形式,即EC2。 Scripting,即脚本语言。包括很多,这里不多赘述。如mvel、js、python等。    Transport,代表ES内部节点,代表跟集群的客户端交互。包括 Thrift、Memcached、Http等协议 RESTful Style API,通过RESTful方式来实现API编程。 3rd plugins,代表第三方插件。 Java(Netty),是开发框架。 JMX,是监控。 使用案例 1、将ES作为网站的主要后端系统 比如现在搭建一个博客系统,对于博客帖子的数据可以直接在ES上存储,并且使用ES来进行检索,统计。ES提供了持久化的存储、统计和很多其他数据存储的特性。 注意:但是像其他的NOSQL数据存储一样,ES是不支持事务的,如果要事务机制,还是考虑使用其他的数据库做真实库。 2、将ES添加到现有系统 有些时候不需要ES提供所有数据的存储功能,只是想在一个数据存储的基础之上使用ES。比如已经有一个复杂的系统在运行,但是现在想加一个搜索的功能,就可以使用该方案。 3、将ES作为现有解决方案的后端部分 因为ES是开源的系统,提供了直接的HTTP接口,并且现在有一个大型的生态系统在支持他。比如现在我们想部署大规模的日志框架、用于存储、搜索和分析海量的事件,考虑到现有的工具可以写入和读取ES,可以不需要进行任何开发,配置这些工具就可以去运作。 设计结构 1、逻辑设计 文档 文档是可以被索引的信息的基本单位,它包含几个重要的属性: 是自我包含的。一篇文档同时包含字段和他们的取值。 是层次型的。文档中还可以包含新的文档,一个字段的取值可以是简单的,例如location字段的取值可以是字符串,还可以包含其他字段和取值,比如可以同时包含城市和街道地址。 拥有灵活的结构。文档不依赖于预先定义的模式。也就是说并非所有的文档都需要拥有相同的字段,并不受限于同一个模式 {   "name":"meeting",   "location":"office",   "organizer":"yanping" } {   "name":"meeting",   "location":{     "name":"sheshouzuo",        "date":"2019-6-28"   },   "memebers":["leio","shiyi"] } 类型 类型是文档的逻辑容器,类似于表格是行的容器。在不同的类型中,最好放入不同的结构的文档。 字段 ES中,每个文档,其实是以json形式存储的。而一个文档可以被视为多个字段的集合。 映射 每个类型中字段的定义称为映射。例如,name字段映射为String。 索引 索引是映射类型的容器一个ES的索引非常像关系型世界中的数据库,是独立的大量文档集合。   关系型数据库与ES的结构上的对比 2、物理设计 节点 一个节点是一个ES的实例,在服务器上启动ES之后,就拥有了一个节点,如果在另一个服务器上启动ES,这就是另一个节点。甚至可以在一台服务器上启动多个ES进程,在一台服务器上拥有多个节点。多个节点可以加入同一个集群。 当ElasticSearch的节点启动后,它会利用多播(multicast)(或者单播,如果用户更改了配置)寻找集群中的其它节点,并与之建立连接。这个过程如下图所示: 节点主要有3种类型,第一种类型是client_node,主要是起到请求分发的作用,类似路由。第二种类型是master_node,是主的节点,所有的新增,删除,数据分片都是由主节点操作(elasticsearch底层是没有更新数据操作的,上层对外提供的更新实际上是删除了再新增),当然也能承担搜索操作。第三种类型是date_node,该类型的节点只能做搜索操作,具体会分配到哪个date_node,就是由client_node决定,而data_node的数据都是从master_node同步过来的 分片 一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。   为了解决这个问题,ES提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。 分片之所以重要,主要有两方面的原因:   1、允许你水平分割/扩展你的内容容量 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量 至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由ES管理的,对于作为用户的你来说,这些都是透明的。   2、在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,ES允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。 复制之所以重要,主要有两方面的原因: (1)在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。 (2)扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行 总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制数量,但是不能改变分片的数量。   默认情况下,ES中的每个索引被分片5个主分片和1个复制,这意味着,如果你的集群中至少有两个节点,你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。一个索引的多个分片可以存放在集群中的一台主机上,也可以存放在多台主机上,这取决于你的集群机器数量。主分片和复制分片的具体位置是由ES内在的策略所决定的。 3、插件HEAD elasticsearch-head是一个界面化的集群操作和管理工具 ● node:即一个 Elasticsearch 的运行实例,使用多播或单播方式发现 cluster 并加入。 ● cluster:包含一个或多个拥有相同集群名称的 node,其中包含一个master node。 ● index:类比关系型数据库里的DB,是一个逻辑命名空间。 ● alias:可以给 index 添加零个或多个alias,通过 alias 使用index 和根据index name 访问index一样,但是,alias给我们提供了一种切换index的能力,比如重建了index,取名● customer_online_v2,这时,有了alias,我要访问新 index,只需要把 alias 添加到新 index 即可,并把alias从旧的 index 删除。不用修改代码。 ● type:类比关系数据库里的Table。其中,一个index可以定义多个type,但一般使用习惯仅配一个type。 ● mapping:类比关系型数据库中的 schema 概念,mapping 定义了 index 中的 type。mapping 可以显示的定义,也可以在 document 被索引时自动生成,如果有新的 field,Elasticsearch 会自动推测出 field 的type并加到mapping中。 ● document:类比关系数据库里的一行记录(record),document 是 Elasticsearch 里的一个 JSON 对象,包括零个或多个field。 ● field:类比关系数据库里的field,每个field 都有自己的字段类型。 ● shard:是一个Lucene 实例。Elasticsearch 基于 Lucene,shard 是一个 Lucene 实例,被 Elasticsearch 自动管理。之前提到,index 是一个逻辑命名空间,shard 是具体的物理概念,建索引、查询等都是具体的shard在工作。shard 包括primary shard 和 replica shard,写数据时,先写到primary shard,然后,同步到replica shard,查询时,primary 和 replica 充当相同的作用。replica shard 可以有多份,也可以没有,replica shard的存在有两个作用,一是容灾,如果primary shard 挂了,数据也不会丢失,集群仍然能正常工作;二是提高性能,因为replica 和 primary shard 都能处理查询。另外,如上图右侧红框所示,shard数和replica数都可以设置,但是,shard 数只能在建立index 时设置,后期不能更改,但是,replica 数可以随时更改。但是,由于 Elasticsearch 很友好的封装了这部分,在使用Elasticsearch 的过程中,我们一般仅需要关注 index 即可,不需关注shard。   shard、node、cluster 在物理上构成了 Elasticsearch 集群,field、type、index 在逻辑上构成一个index的基本概念,在使用 Elasticsearch 过程中,我们一般关注到逻辑概念就好,就像我们在使用MySQL 时,我们一般就关注DB Name、Table和schema即可,而不会关注DBA维护了几个MySQL实例、master 和 slave 等怎么部署的一样。 ES中的索引原理 (1)传统的关系型数据库 二叉树查找效率是logN,同时插入新的节点不必移动全部节点,所以用树型结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,再结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这样的数据结构做索引 (2)ES 采用倒排索引 那么,倒排索引是个什么样子呢? 首先,来搞清楚几个概念,为此,举个例子: 假设有个user索引,它有四个字段:分别是name,gender,age,address。画出来的话,大概是下面这个样子,跟关系型数据库一样 Term(单词):一段文本经过分析器分析以后就会输出一串单词,这一个一个的就叫做Term Term Dictionary(单词字典):顾名思义,它里面维护的是Term,可以理解为Term的集合 Term Index(单词索引):为了更快的找到某个单词,我们为单词建立索引 Posting List(倒排列表):倒排列表记录了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。(PS:实际的倒排列表中并不只是存了文档ID这么简单,还有一些其它的信息,比如:词频(Term出现的次数)、偏移量(offset)等,可以想象成是Python中的元组,或者Java中的对象) (PS:如果类比现代汉语词典的话,那么Term就相当于词语,Term Dictionary相当于汉语词典本身,Term Index相当于词典的目录索引) 我们知道,每个文档都有一个ID,如果插入的时候没有指定的话,Elasticsearch会自动生成一个,因此ID字段就不多说了 上面的例子,Elasticsearch建立的索引大致如下: name字段: age字段: gender字段: address字段: Elasticsearch分别为每个字段都建立了一个倒排索引。比如,在上面“张三”、“北京市”、22 这些都是Term,而[1,3]就是Posting List。Posting list就是一个数组,存储了所有符合某个Term的文档ID。 只要知道文档ID,就能快速找到文档。可是,要怎样通过我们给定的关键词快速找到这个Term呢? 当然是建索引了,为Terms建立索引,最好的就是B-Tree索引(MySQL就是B树索引最好的例子)。 我们查找Term的过程跟在MyISAM中记录ID的过程大致是一样的 MyISAM中,索引和数据是分开,通过索引可以找到记录的地址,进而可以找到这条记录 在倒排索引中,通过Term索引可以找到Term在Term Dictionary中的位置,进而找到Posting List,有了倒排列表就可以根据ID找到文档了 (PS:可以这样理解,类比MyISAM的话,Term Index相当于索引文件,Term Dictionary相当于数据文件) (PS:其实,前面我们分了三步,我们可以把Term Index和Term Dictionary看成一步,就是找Term。因此,可以这样理解倒排索引:通过单词找到对应的倒排列表,根据倒排列表中的倒排项进而可以找到文档记录) 为了更进一步理解,用两张图来具现化这一过程: (至于里面涉及的更加高深的数据压缩技巧,以及多个field联合查询利用跳表的数据结构快速做运算来查询,这些大家有兴趣可以自己去了解)

问问小秘 2020-04-29 15:40:48 0 浏览量 回答数 0

问题

ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?【Java问答学堂】28期

剑曼红尘 2020-05-28 09:45:28 15 浏览量 回答数 1

阿里云试用中心,为您提供0门槛上云实践机会!

0元试用32+款产品,最高免费12个月!拨打95187-1,咨询专业上云建议!

回答

面试官心理分析 这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 5~10s,坑爹了。第一次搜索的时候,是 5~10s,后面反而就快了,可能就几百毫秒。 你就很懵,每个用户第一次访问都会比较慢,比较卡么?所以你要是没玩儿过 es,或者就是自己玩玩儿 demo,被问到这个问题容易懵逼,显示出你对 es 确实玩儿的不怎么样? 面试题剖析 说实话,es 性能优化是没有什么银弹的,啥意思呢?就是不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。 性能优化的杀手锏——filesystem cache 你往 es 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 filesystem cache 里面去。 es 的搜索引擎严重依赖于底层的 filesystem cache,你如果给 filesystem cache 更多的内存,尽量让内存可以容纳所有的 idx segment file 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。 性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 filesystem cache,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。 这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是 64 * 3 = 192G。每台机器给 es jvm heap 是 32G,那么剩下来留给 filesystem cache 的就是每台机器才 32G,总共集群里给 filesystem cache 的就是 32 * 3 = 96G 内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 1T 的磁盘容量,es 数据量是 1T,那么每台机器的数据量是 300G。这样性能好吗? filesystem cache 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。 归根结底,你要让 es 性能要好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半。 根据我们自己的生产环境实践经验,最佳的情况下,是仅仅在 es 中就存少量的数据,就是你要用来搜索的那些索引,如果内存留给 filesystem cache 的是 100G,那么你就将索引数据控制在 100G 以内,这样的话,你的数据几乎全部走内存来搜索,性能非常之高,一般可以在 1 秒以内。 比如说你现在有一行数据。id,name,age .... 30 个字段。但是你现在搜索,只需要根据 id,name,age 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说 90% 的数据是不用来搜索的,结果硬是占据了 es 机器上的 filesystem cache 的空间,单条数据的数据量越大,就会导致 filesystem cahce 能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的少数几个字段就可以了,比如说就写入 es id,name,age 三个字段,然后你可以把其他的字段数据存在 mysql/hbase 里,我们一般是建议用 es + hbase 这么一个架构。 hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id,然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据,给查出来,再返回给前端。 写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放 es,会每次查询都是 5~10s,现在可能性能就会很高,每次查询就是 50ms。 数据预热 假如说,哪怕是你就按照上述的方案去做了,es 集群中每个机器写入的数据量还是超过了 filesystem cache 一倍,比如说你写入一台机器 60G 数据,结果 filesystem cache 就 30G,还是有 30G 数据留在了磁盘上。 其实可以做数据预热。 举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 filesystem cache 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。 或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 filesystem cache 里去。 对于那些你觉得比较热的、经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据每隔一段时间,就提前访问一下,让数据进入 filesystem cache 里面去。这样下次别人访问的时候,性能一定会好很多。 冷热分离 es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在 filesystem os cache 里,别让冷数据给冲刷掉。 你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个 shard。3 台机器放热数据 index,另外 3 台机器放冷数据 index。然后这样的话,你大量的时间是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 filesystem cache 里面了,就可以确保热数据的访问性能是很高的。但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。 document 模型设计 对于 MySQL,我们经常有一些复杂的关联查询。在 es 里该怎么玩儿,es 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。 最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 es 中。搜索的时候,就不需要利用 es 的搜索语法来完成 join 之类的关联搜索了。 document 模型设计是非常重要的,很多操作,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。es 能支持的操作就那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。 分页性能优化 es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到一个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。 分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你必须得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。 我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。 有什么解决方案吗? 不允许深度分页(默认深度分页性能很差) 跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差。 类似于 app 里的推荐商品不断下拉出来一页一页的 类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 scroll api,关于如何使用,自行上网搜索。 scroll 会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。 但是,唯一的一点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,然后去第 120 页,然后又回到第 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。 初始化时必须指定 scroll 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。 除了用 scroll api,你也可以用 search_after 来做,search_after 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。 往期回顾: 【Java问答学堂】1期 为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景? 【Java问答学堂】2期 如何保证消息队列的高可用? 【Java问答学堂】3期 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? 【Java问答学堂】4期 如何保证消息的可靠性传输?(如何处理消息丢失的问题?) 【Java问答学堂】5期 如何保证消息的顺序性? 【Java问答学堂】6期 如何解决消息队列的延时以及过期失效问题? 【Java问答学堂】7期 如果让你写一个消息队列,该如何进行架构设计? 【Java问答学堂】8期 es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)? 【Java问答学堂】9期 es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?

剑曼红尘 2020-04-28 14:17:05 0 浏览量 回答数 0

问题

【Java问答学堂】10期 es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?

剑曼红尘 2020-04-28 14:16:56 0 浏览量 回答数 1

回答

*1、查询SQL尽量不要使用select ,而是select具体字段。 反例子: select * from employee; 正例子: select id,name from employee; 理由: 只取需要的字段,节省资源、减少网络开销。select * 进行查询时,很可能就不会使用到覆盖索引了,就会造成回表查询。 2、如果知道查询结果只有一条或者只要最大/最小一条记录,建议用limit 1 假设现在有employee员工表,要找出一个名字叫jay的人. CREATE TABLE `employee` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, `date` datetime DEFAULT NULL, `sex` int(1) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 反例: select id,name from employee where name='jay' 正例 select id,name from employee where name='jay' limit 1; 理由: 加上limit 1后,只要找到了对应的一条记录,就不会继续向下扫描了,效率将会大大提高。当然,如果name是唯一索引的话,是不必要加上limit 1了,因为limit的存在主要就是为了防止全表扫描,从而提高性能,如果一个语句本身可以预知不用全表扫描,有没有limit ,性能的差别并不大。 3、应尽量避免在where子句中使用or来连接条件 新建一个user表,它有一个普通索引userId,表结构如下: CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userId` int(11) NOT NULL, `age` int(11) NOT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_userId` (`userId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 假设现在需要查询userid为1或者年龄为18岁的用户,很容易有以下sql 反例: select * from user where userid=1 or age =18 正例: //使用union all select * from user where userid=1 union all select * from user where age = 18 //或者分开两条sql写: select * from user where userid=1 select * from user where age = 18 理由: 使用or可能会使索引失效,从而全表扫描。 对于or+没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,它还得全表扫描,也就是需要三步过程: 全表扫描+索引扫描+合并 如果它一开始就走全表扫描,直接一遍扫描就完事。 mysql是有优化器的,处于效率与成本考虑,遇到or条件,索引可能失效,看起来也合情合理。 4、优化limit分页 我们日常做分页需求时,一般会用 limit 实现,但是当偏移量特别大的时候,查询效率就变得低下。 反例: select id,name,age from employee limit 10000,10 正例: //方案一 :返回上次查询的最大记录(偏移量) select id,name from employee where id>10000 limit 10. //方案二:order by + 索引 select id,name from employee order by id limit 10000,10 //方案三:在业务允许的情况下限制页数: 理由: 当偏移量最大的时候,查询效率就会越低,因为Mysql并非是跳过偏移量直接去取后面的数据,而是先把偏移量+要取的条数,然后再把前面偏移量这一段的数据抛弃掉再返回的。 如果使用优化方案一,返回上次最大查询记录(偏移量),这样可以跳过偏移量,效率提升不少。 方案二使用order by+索引,也是可以提高查询效率的。 方案三的话,建议跟业务讨论,有没有必要查这么后的分页啦。因为绝大多数用户都不会往后翻太多页。 5、优化你的like语句 日常开发中,如果用到模糊关键字查询,很容易想到like,但是like很可能让你的索引失效。 反例: select userId,name from user where userId like '%123'; 正例: select userId,name from user where userId like '123%'; 理由: 把%放前面,并不走索引,如下: 把% 放关键字后面,还是会走索引的。如下: 6、使用where条件限定要查询的数据,避免返回多余的行 假设业务场景是这样:查询某个用户是否是会员。曾经看过老的实现代码是这样。。。 反例: List<Long> userIds = sqlMap.queryList("select userId from user where isVip=1"); boolean isVip = userIds.contains(userId); 正例: Long userId = sqlMap.queryObject("select userId from user where userId='userId' and isVip='1' ") boolean isVip = userId!=null; 理由: 需要什么数据,就去查什么数据,避免返回不必要的数据,节省开销。 7、尽量避免在索引列上使用mysql的内置函数 业务需求:查询最近七天内登陆过的用户(假设loginTime加了索引) 反例: select userId,loginTime from loginuser where Date_ADD(loginTime,Interval 7 DAY) >=now(); 正例: explain select userId,loginTime from loginuser where loginTime >= Date_ADD(NOW(),INTERVAL - 7 DAY); 理由: 索引列上使用mysql的内置函数,索引失效 8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致系统放弃使用索引而进行全表扫 反例: select * from user where age-1 =10; 正例: select * from user where age =11; 理由: 9、Inner join 、left join、right join,优先使用Inner join,如果是left join,左边表结果尽量小 Inner join 内连接,在两张表进行连接查询时,只保留两张表中完全匹配的结果集 left join 在两张表进行连接查询时,会返回左表所有的行,即使在右表中没有匹配的记录。 right join 在两张表进行连接查询时,会返回右表所有的行,即使在左表中没有匹配的记录。 都满足SQL需求的前提下,推荐优先使用Inner join(内连接),如果要使用left join,左边表数据结果尽量小,如果有条件的尽量放到左边处理。 反例: select * from tab1 t1 left join tab2 t2 on t1.size = t2.size where t1.id>2; 正例: select * from (select * from tab1 where id >2) t1 left join tab2 t2 on t1.size = t2.size; 理由: 如果inner join是等值连接,或许返回的行数比较少,所以性能相对会好一点。 同理,使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少。 10、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 反例: select age,name from user where age <>18; 正例: //可以考虑分开两条sql写 select age,name from user where age <18; select age,name from user where age >18; 理由: 使用!=和<>很可能会让索引失效 11、使用联合索引时,注意索引列的顺序,一般遵循最左匹配原则。 表结构:(有一个联合索引idx_userid_age,userId在前,age在后) CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userId` int(11) NOT NULL, `age` int(11) DEFAULT NULL, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`), KEY `idx_userid_age` (`userId`,`age`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; 反例: select * from user where age = 10; 正例: //符合最左匹配原则 select * from user where userid=10 and age =10; //符合最左匹配原则 select * from user where userid =10; 理由: 当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。 联合索引不满足最左原则,索引一般会失效,但是这个还跟Mysql优化器有关的。 12、对查询进行优化,应考虑在 where 及 order by 涉及的列上建立索引,尽量避免全表扫描。 反例: select * from user where address ='深圳' order by age ; 正例: 添加索引 alter table user add index idx_address_age (address,age) 13、如果插入数据过多,考虑批量插入。 反例: for(User u :list){ INSERT into user(name,age) values(#name#,#age#) } 正例: //一次500批量插入,分批进行 insert into user(name,age) values <foreach collection="list" item="item" index="index" separator=","> (#{item.name},#{item.age}) </foreach> 理由: 批量插入性能好,更加省时间 打个比喻:假如你需要搬一万块砖到楼顶,你有一个电梯,电梯一次可以放适量的砖(最多放500),你可以选择一次运送一块砖,也可以一次运送500,你觉得哪个时间消耗大? 14、在适当的时候,使用覆盖索引。 覆盖索引能够使得你的SQL语句不需要回表,仅仅访问索引就能够得到所有需要的数据,大大提高了查询效率。 反例: // like模糊查询,不走索引了 select * from user where userid like '%123%' 正例: //id为主键,那么为普通索引,即覆盖索引登场了。 select id,name from user where userid like '%123%'; 15、慎用distinct关键字 distinct 关键字一般用来过滤重复记录,以返回不重复的记录。在查询一个字段或者很少字段的情况下使用时,给查询带来优化效果。但是在字段很多的时候使用,却会大大降低查询效率。 反例: SELECT DISTINCT * from user; 正例: select DISTINCT name from user; 理由: 带distinct的语句cpu时间和占用时间都高于不带distinct的语句。因为当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据,然而这个比较,过滤的过程会占用系统资源,cpu时间。 16、删除冗余和重复索引 反例: KEY `idx_userId` (`userId`) KEY `idx_userId_age` (`userId`,`age`) 正例: //删除userId索引,因为组合索引(A,B)相当于创建了(A)和(A,B)索引 KEY `idx_userId_age` (`userId`,`age`) 理由: 重复的索引需要维护,并且优化器在优化查询的时候也需要逐个地进行考虑,这会影响性能的。 17、如果数据量较大,优化你的修改/删除语句。 避免同时修改或删除过多数据,因为会造成cpu利用率过高,从而影响别人对数据库的访问。 反例: //一次删除10万或者100万+? delete from user where id <100000; //或者采用单一循环操作,效率低,时间漫长 for(User user:list){ delete from user; } 正例: //分批进行删除,如每次500 delete user where id<500 delete product where id>=500 and id<1000; 理由: 一次性删除太多数据,可能会有lock wait timeout exceed的错误,所以建议分批操作。 18、where子句中考虑使用默认值代替null。 反例: select * from user where age is not null; 正例: //设置0为默认值 select * from user where age>0; 理由: 并不是说使用了is null 或者 is not null 就会不走索引了,这个跟mysql版本以及查询成本都有关。 如果mysql优化器发现,走索引比不走索引成本还要高,肯定会放弃索引,这些条件!=,>is null,is not null经常被认为让索引失效,其实是因为一般情况下,查询的成本高,优化器自动放弃的。 如果把null值,换成默认值,很多时候让走索引成为可能,同时,表达意思会相对清晰一点。 19、不要有超过5个以上的表连接 连表越多,编译的时间和开销也就越大。 把连接表拆开成较小的几个执行,可读性更高。 如果一定需要连接很多表才能得到数据,那么意味着糟糕的设计了。 20、exist & in的合理利用 假设表A表示某企业的员工表,表B表示部门表,查询所有部门的所有员工,很容易有以下SQL: select * from A where deptId in (select deptId from B); 这样写等价于: 先查询部门表B select deptId from B 再由部门deptId,查询A的员工 select * from A where A.deptId = B.deptId 可以抽象成这样的一个循环: List<> resultSet ; for(int i=0;i<B.length;i++) { for(int j=0;j<A.length;j++) { if(A[i].id==B[j].id) { resultSet.add(A[i]); break; } } } 显然,除了使用in,我们也可以用exists实现一样的查询功能,如下: select * from A where exists (select 1 from B where A.deptId = B.deptId); 因为exists查询的理解就是,先执行主查询,获得数据后,再放到子查询中做条件验证,根据验证结果(true或者false),来决定主查询的数据结果是否得意保留。 那么,这样写就等价于: select * from A,先从A表做循环 select * from B where A.deptId = B.deptId,再从B表做循环. 同理,可以抽象成这样一个循环: List<> resultSet ; for(int i=0;i<A.length;i++) { for(int j=0;j<B.length;j++) { if(A[i].deptId==B[j].deptId) { resultSet.add(A[i]); break; } } } 数据库最费劲的就是跟程序链接释放。假设链接了两次,每次做上百万次的数据集查询,查完就走,这样就只做了两次;相反建立了上百万次链接,申请链接释放反复重复,这样系统就受不了了。即mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优。 因此,我们要选择最外层循环小的,也就是,如果B的数据量小于A,适合使用in,如果B的数据量大于A,即适合选择exist。 21、尽量用 union all 替换 union 如果检索结果中不会有重复的记录,推荐union all 替换 union。 反例: select * from user where userid=1 union select * from user where age = 10 正例: select * from user where userid=1 union all select * from user where age = 10 理由: 如果使用union,不管检索结果有没有重复,都会尝试进行合并,然后在输出最终结果前进行排序。如果已知检索结果没有重复记录,使用union all 代替union,这样会提高效率。 22、索引不宜太多,一般5个以内。 索引并不是越多越好,索引虽然提高了查询的效率,但是也降低了插入和更新的效率。 insert或update时有可能会重建索引,所以建索引需要慎重考虑,视具体情况来定。 一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否没有存在的必要。 23、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型 反例: king_id` varchar(20) NOT NULL COMMENT '守护者Id' 正例: `king_id` int(11) NOT NULL COMMENT '守护者Id'` 理由: 相对于数字型字段,字符型会降低查询和连接的性能,并会增加存储开销。 24、索引不适合建在有大量重复数据的字段上,如性别这类型数据库字段。 因为SQL优化器是根据表中数据量来进行查询优化的,如果索引列有大量重复数据,Mysql查询优化器推算发现不走索引的成本更低,很可能就放弃索引了。 25、尽量避免向客户端返回过多数据量。 假设业务需求是,用户请求查看自己最近一年观看过的直播数据。 反例: //一次性查询所有数据回来 select * from LivingInfo where watchId =useId and watchTime >= Date_sub(now(),Interval 1 Y) 正例: //分页查询 select * from LivingInfo where watchId =useId and watchTime>= Date_sub(now(),Interval 1 Y) limit offset,pageSize //如果是前端分页,可以先查询前两百条记录,因为一般用户应该也不会往下翻太多页, select * from LivingInfo where watchId =useId and watchTime>= Date_sub(now(),Interval 1 Y) limit 200 ; 26、当在SQL语句中连接多个表时,请使用表的别名,并把别名前缀于每一列上,这样语义更加清晰。 反例: select * from A inner join B on A.deptId = B.deptId; 正例: select memeber.name,deptment.deptName from A member inner join B deptment on member.deptId = deptment.deptId; 27、尽可能使用varchar/nvarchar 代替 char/nchar。 反例: `deptName` char(100) DEFAULT NULL COMMENT '部门名称' 正例: `deptName` varchar(100) DEFAULT NULL COMMENT '部门名称' 理由: 因为首先变长字段存储空间小,可以节省存储空间。 其次对于查询来说,在一个相对较小的字段内搜索,效率更高。 28、为了提高group by 语句的效率,可以在执行到该语句前,把不需要的记录过滤掉。 反例: select job,avg(salary) from employee group by job having job ='president' or job = 'managent' 正例: select job,avg(salary) from employee where job ='president' or job = 'managent' group by job; 29、如何字段类型是字符串,where时一定用引号括起来,否则索引失效 反例: select * from user where userid =123; 正例: select * from user where userid ='123'; 理由: 为什么第一条语句未加单引号就不走索引了呢? 这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较。 30、使用explain 分析你SQL的计划 日常开发写SQL的时候,尽量养成一个习惯吧。用explain分析一下你写的SQL,尤其是走不走索引这一块。 explain select * from user where userid =10086 or age =18;

剑曼红尘 2020-04-21 14:01:32 0 浏览量 回答数 0

问题

荆门开诊断证明-scc

游客5k2abgdj3m2ti 2019-12-01 22:09:00 1 浏览量 回答数 0

问题

【精品问答】Java技术1000问(1)

问问小秘 2019-12-01 21:57:43 39926 浏览量 回答数 17

问题

【今日算法】4月24日-如何寻找最长回文子串

游客ih62co2qqq5ww 2020-04-24 22:54:34 0 浏览量 回答数 0

问题

经典动态规划:高楼扔鸡蛋(进阶篇) 6月3日【今日算法】

游客ih62co2qqq5ww 2020-06-03 15:10:38 7 浏览量 回答数 1

问题

【精品问答】python技术1000问(2)

问问小秘 2019-12-01 22:03:02 3129 浏览量 回答数 1
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 企业建站模板