一、from + size

ElasticSearch默认采用的分页方式是from + size的形式,在深度分页的情况下,这种使用方式的效率是非常低的,比如我们执行如下查询:

1

2

3

4

5

6

7

8

GET /student/student/_search

{

  "query":{

    "match_all": {}

  },

  "from":5000,

  "size":10

}

以上DSL语句执行后,ElasticSearch需要在各个分片上匹配排序并得到5010条数据,协调节点拿到这些数据再进行排序处理,然后结果集中取最后10条数据返回。

这样的深度分页的效率非常低,因为我们只需要查询10条数据,而ElasticSearch则需要对from + size条数据进行排序处理后返回。

其次:ElasticSearch为了性能,限制了分页的深度,目前ElasticSearch支持的最大的查询长度是max_result_window = 10000;也就是说我们不能分页到10000条数据以上。

例如:9990+10 = 10000,此时可以执行分页查询。

但是,当9991+10 > 10000,此时执行查询的返回为:

二、scroll(游标)

相对于from和size的分页来说,使用scroll可以模拟一个传统数据的游标,记录当前读取的文档信息位置。这个分页的用法,不是为了实时查询数据,而是为了一次性查询大量的数据(甚至是全部的数据)。

传统数据库游标:

游标(cursor)是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果。每个游标区都有一个名字,用户可以用SQL语句逐一从游标中获取记录,并赋给主变量,交由主语言进一步处理。就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。游标是一段私有的SQL工作区,也就是一段内存区域,用于暂时存放受SQL语句影响到的数据。通俗理解就是将受影响的数据暂时放到了一个内存区域的虚表中,而这个虚表就是游标。

ElasticSearch scroll:

使用scroll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据。scroll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的。每次发送scroll请求,我们还需要指定一个scroll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了。

一个滚屏搜索允许我们做一个初始阶段搜索并且持续批量从Elasticsearch里拉取结果直到没有结果剩下。

滚屏搜索会及时制作快照。这个快照不会包含任何在初始阶段搜索请求后对index做的修改。它通过将旧的数据文件保存在手边,所以可以保护index的样子看起来像搜索开始时的样子。这样将使得我们无法得到用户最近的更新行为

scroll的使用:

执行如下curl,每次请求两条。可以定制 scroll = 1m意味着该窗口过期时间为1分钟。

1

2

3

4

5

6

7

GET /student/student/_search?scroll=1m

{

  "query":{

    "match_all": {}

  },

  "size":2

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

{

  "_scroll_id" "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAR-lFlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEfphZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABH6cWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==",

  "took" 15,

  "timed_out" false,

  "_shards" : {

    "total" 3,

    "successful" 3,

    "skipped" 0,

    "failed" 0

  },

  "hits" : {

    "total" 4,

    "max_score" 1.0,

    "hits" : [

      {

        "_index" "student",

        "_type" "student",

        "_id" "2",

        "_score" 1.0,

        "_source" : {

          "id" 2,

          "name" "仙道彰",

          "age" 17,

          "gender" "男",

          "address" "神奈川县陵南高中"

        }

      },

      {

        "_index" "student",

        "_type" "student",

        "_id" "4",

        "_score" 1.0,

        "_source" : {

          "id" 4,

          "name" "赤木刚宪",

          "age" 20,

          "address" "东京大学",

          "gender" "男"

        }

      }

    ]

  }

}

可以看到在返回结果中,存在一个很重要的_scroll_id,在后面的请求中,都需要在带着这个_scroll_id去请求。如下:

1

2

3

4

5

GET /_search/scroll

{

  "scroll":"1m",

  "scroll_id":"DnF1ZXJ5VGhlbkZldGNoAwAAAAAAAR-lFlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEfphZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABH6cWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ=="

}

执行一次,得到结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

{

  "_scroll_id" "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAASI_FlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEiQRZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABIkAWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==",

  "took" 3,

  "timed_out" false,

  "_shards" : {

    "total" 3,

    "successful" 3,

    "skipped" 0,

    "failed" 0

  },

  "hits" : {

    "total" 4,

    "max_score" 1.0,

    "hits" : [

      {

        "_index" "student",

        "_type" "student",

        "_id" "3",

        "_score" 1.0,

        "_source" : {

          "id" 3,

          "name" "赤木晴子",

          "age" 17,

          "gender" "女",

          "address" "神奈川县湘北高中"

        }

      },

      {

        "_index" "student",

        "_type" "student",

        "_id" "1",

        "_score" 1.0,

        "_source" : {

          "id" 1,

          "name" "樱木花道",

          "age" 18,

          "address" "神奈川县湘北高中",

          "gender" "男"

        }

      }

    ]

  }

}

再执行一次,得到结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

{

  "_scroll_id" "DnF1ZXJ5VGhlbkZldGNoAwAAAAAAASI_FlBWekUzVGlUUjhXQ09KQ2dwLTA1eFEAAAAAAAEiQRZQVnpFM1RpVFI4V0NPSkNncC0wNXhRAAAAAAABIkAWUFZ6RTNUaVRSOFdDT0pDZ3AtMDV4UQ==",

  "took" 1,

  "timed_out" false,

  "_shards" : {

    "total" 3,

    "successful" 3,

    "skipped" 0,

    "failed" 0

  },

  "hits" : {

    "total" 4,

    "max_score" 1.0,

    "hits" : [ ]

  }

}

现在student这个索引中共有4条数据,id分别为 1, 2, 3, 4。当我们使用 scroll 查询第3次的时候,返回结果为kong。这时我们就知道已经结果集已经匹配完了。

三、search_after

from + size的分页方式虽然是最灵活的分页方式,但是当分页深度达到一定程度将会产生深度分页的问题。scroll能够解决深度分页的问题,但是其无法实现实时查询,即当scroll_id生成后无法查询到之后数据的变更,因为其底层原理是生成数据的快照。这时 search_after应运而生。其是在es-5.X之后才提供的。

search_after 是一种假分页方式,根据上一页的最后一条数据来确定下一页的位置,同时在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。为了找到每一页最后一条数据,每个文档必须有一个全局唯一值,官方推荐使用 _id 作为全局唯一值,但是只要能表示其唯一性就可以。

执行如下查询:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

GET /student/student/_search

{

  "query":{

    "match_all": {}

  },

  "size":2,

  "sort": [

    {

      "id": {

        "order""desc"

      }

    }

  ]

}

结果为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

{

  "took" 1,

  "timed_out" false,

  "_shards" : {

    "total" 3,

    "successful" 3,

    "skipped" 0,

    "failed" 0

  },

  "hits" : {

    "total" 4,

    "max_score" null,

    "hits" : [

      {

        "_index" "student",

        "_type" "student",

        "_id" "4",

        "_score" null,

        "_source" : {

          "id" 4,

          "name" "赤木刚宪",

          "age" 20,

          "address" "东京大学",

          "gender" "男"

        },

        "sort" : [

          4

        ]

      },

      {

        "_index" "student",

        "_type" "student",

        "_id" "3",

        "_score" null,

        "_source" : {

          "id" 3,

          "name" "赤木晴子",

          "age" 17,

          "gender" "女",

          "address" "神奈川县湘北高中"

        },

        "sort" : [

          3

        ]

      }

    ]

  }

}

可以看到结果集中的  "sort" : [3] ,将当前结果集中sort的值,作为下一次查询search_after的参数:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

GET /student/student/_search

{

  "query":{

    "match_all": {}

  },

  "size":2,

  "sort": [

    {

      "_id": {

        "order""desc"

      }

    }

  ],

  "search_after":[3]

}

得到结果为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

{

  "took" 1,

  "timed_out" false,

  "_shards" : {

    "total" 3,

    "successful" 3,

    "skipped" 0,

    "failed" 0

  },

  "hits" : {

    "total" 4,

    "max_score" null,

    "hits" : [

      {

        "_index" "student",

        "_type" "student",

        "_id" "4",

        "_score" null,

        "_source" : {

          "id" 4,

          "name" "赤木刚宪",

          "age" 20,

          "address" "东京大学",

          "gender" "男"

        },

        "sort" : [

          4

        ]

      },

      {

        "_index" "student",

        "_type" "student",

        "_id" "3",

        "_score" null,

        "_source" : {

          "id" 3,

          "name" "赤木晴子",

          "age" 17,

          "gender" "女",

          "address" "神奈川县湘北高中"

        },

        "sort" : [

          3

        ]

      }

    ]

  }

}

这样我们就使用search_after方式实现了分页查询。

四、三种分页方式的比较

分页方式 性能 优点 缺点 场景
from + size 灵活性好,实现简单 深度分页问题 数据量比较小,能容忍深度分页问题
scroll 解决了深度分页问题

无法反应数据的实时性(快照版本)

维护成本高,需要维护一个 scroll_id

海量数据的导出,需要查询海量结果集的数据

search_after

性能最好

不存在深度分页问题

能够反映数据的实时变更

实现复杂,需要有一个全局唯一的字段

连续分页的实现会比较复杂,因为每一次查询都需要上次查询的结果

海量数据的分页

Elasticsearch深入理解(九)——三种分页方式选取相关推荐

  1. oracle分页的三种方式,oracle 使用rownum的三种分页方式

    rownum是Oracle数据库中的一个特有关键字,返回的是一个数字代表记录的行号. 基础知识:rownum只能做 获取51到100的数据 三种分页的写法: 1.使用minus,原理就是查询出前100 ...

  2. Spring 依赖注入的理解及三种注入方式

    Spring 依赖注入概念和三种注入方式(理解及应用) 什么是注入 要了解Spring的三种注入方式首先前提是得先了解一下什么是注入,相信很多人对这个概念都是模糊不清的,网上的解释是这样的: 依赖注入 ...

  3. mybatis 中 Example 的使用 :条件查询、排序、分页(三种分页方式 : RowBounds、PageHelpler 、limit )

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. import tk.mybatis.mapper.entity.Example;import com ...

  4. oracle中rownum的三种分页方式

    三种分页的写法: 1.使用minus,原理就是查询出前100行的数据 减去 查询出前50行的数据 1 2 3 select * from DATA_TABLE_SQL where rownum< ...

  5. Elasticsearch 三种分页方式

    欢迎关注方志朋的博客,回复"666"获面试宝典 from + size 浅分页 "浅"分页可以理解为简单意义上的分页.它的原理很简单,就是查询前20条数据,然后 ...

  6. Mybatis的5种分页方式

    <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-/ ...

  7. Web框架之Django_07 进阶操作(MTV与MVC、多对多表三种创建方式、前后端传输数据编码格式contentType、ajax、自定义分页器)

    阅读目录 一.MVC与MTV 二.多对多表的创建 三.ajax,前后端传输编码格式contentType 四.批量插入数据与自定义分页器 摘要 MTV与MVC 多对多表三种创建方式 ajax ,前后端 ...

  8. oracle Hash Join及三种连接方式

    在Oracle中,确定连接操作类型是执行计划生成的重要方面.各种连接操作类型代表着不同的连接操作算法,不同的连接操作类型也适应于不同的数据量和数据分布情况. 无论是Nest Loop Join(嵌套循 ...

  9. JavaScript 闭包的详细分享(三种创建方式)(附小实例)

    JavaScript闭包的详细理解 一.原理:闭包函数--指有权访问私有函数里面的变量和对象还有方法等:通俗的讲就是突破私有函数的作用域,让函数外面能够使用函数里面的变量及方法. 1.第一种创建方式 ...

  10. 三种去重方式——HashSet、Redis去重、布隆过滤器(BloomFilter)

    三种去重方式 去重就有三种实现方式,那有什么不同呢? HashSet 使用java中的HashSet不能重复的特点去重.优点是容易理解.使用方便. 缺点:占用内存大,性能较低. Redis去重 使用R ...

最新文章

  1. ORB_SLAM2代码阅读(4)——LoopClosing线程
  2. 柱状图、堆叠柱状图、瀑布图有什么区别?怎样用Python绘制?(附代码)
  3. 下载SpringJar包
  4. screen投屏怎么用_Screen投屏没有声音如何解决?
  5. ehcache怎么删除缓存_解释SpringBoot之Ehcache 2.x缓存
  6. 从0到100——知乎架构变迁史
  7. replaceAll
  8. 《Exploring in UE4》多线程机制详解[原理分析]
  9. 看动画学算法之:队列queue
  10. ES6学习(箭头函数详解)
  11. eclipse在线安装ivy和ivyde
  12. 日常运维管理 常用命令(3)
  13. Oracle彻底卸载干净教程
  14. c 陷阱与缺陷 摘录
  15. 简单的C语言顺序结构例题介绍
  16. python语音识别库kaldi_Kaldi 使用 DFSMN 训练语音模型
  17. 正反馈、负反馈傻傻分不清?看这篇电路深度讲解
  18. 巧用模板和友联类型为vc++单元测试加一利器
  19. 基于Arcgis对流域数据的提取及计算
  20. 袁萌浅谈C919大飞机(二)

热门文章

  1. JS 截取视频某一帧图片 实现视频截图
  2. 科普 [分子力学总势能(或者LJ势能)能量最小化]
  3. 本地设置测试域名转向
  4. 知名互联网公司系统架构图
  5. VMware虚拟机net模式无法共享主机ip
  6. Tomcat中Session钝化与活化实现步骤
  7. FastAdmin 目录权限设置
  8. 数码管显示 0-9999计数器
  9. Qt解决连接https报错
  10. Unity项目捏脸解决方案BlendShape