Elasticsearch 删除数据

本文中,我们将学习几种删除数据的方法:

  • 删除单个文档或者一组文档。这样做的时候,Elasticsearch 只是将它们标记为删除,所以它们不会再出现于搜索结果中,
    稍后 Elasticsearch 通过异步的方式将它们彻底地从索引中移出。

  • 删除整个索引。这是删除多组文档的特例。但是不同点在于这样做的性能更好。
    主要的工作就是移除和那个索引相关的所有文件,几乎是瞬间就能完成。

  • 关闭索引。尽管这和删除无关,还是值得一提。关闭的索引不允许读取或者写入操作,数据也不会加载到内存。
    这和删除 Elasticsearch 数据类似,但是索引还是保留在磁盘上。它也很容易恢复,只要再次打开关闭的索引。

删除文档

有几种方式移除单个文档,这里讨论主要的几个。

  • 通过 ID 删除单个文档。如果只有一篇文档要删除,而且你知道它的 ID,这样做非常不错。

  • 在单个请求中删除多篇文档。如果有多篇文档需要删除,可以在一个批量请求中一次性删除它们,这样比每次只删除一篇文档更快。

  • 删除映射类型,包括其中的文档。这样的操作会高效地搜索并删除该类型中所索引的全部文档,也包括映射本身。

  • 删除匹配某个查询的所有文档。这和删除映射类型相似,内部运行一个查询,并识别需要删除的文档。只有在这里可以指定任何想要的查询,然后删除匹配的文档。

  1. 删除单个文档

为了删除单一的文档,需要向其 URL 发送 HTTP DELETE 请求。例如:

1
curl -XDELETE 'localhost:9200/online-shop/shirts/1'

也可以使用版本来管理删除操作的并发,就像索引和更新的并发控制一样。举个例子,假设某款衬衫销售一空,你想移除这篇文档,这样它就不会
出现在搜索结果中。但是当时你可能并不知道,新的采购到货了,而且库存数据也被更新了。为了避免这种情况,可以在 DELETE 请求中
加入版本 version 参数,就像索引和更新的操作那样。

尽管如此,删除的版本控制还是有个特殊情况。一旦删除了文档,它就不复存在了,于是一个更新操作很容易重新创建该文档,尽管这是不应该
发生的(因为更新的版本要比删除的版本更低)。由于外部版本可以用于不存在的文档上,使用外部版本时这个问题尤为突出。

为了防止这样的问题发生,Elasticsearch 将在一段时间内保留这篇文档的版本,如此它就能拒绝版本比删除操作更低的更新操作了。
默认情况下,这个时间是 60 秒,对于多数情况而应该足够了,但是你可以通过设置 elasticsearch.yml 文件中或者是每个索引配置中的
index.gc_deletes 来修改它。

  1. 删除映射类型和删除查询匹配的文档

你也可以删除整个映射类型,包括映射本身和其中索引的全部文档。要如此操作,需要向 DELETE 请求提供类型的 URL:

1
curl -XDELETE 'localhost:9200/online-shop/shirts'

删除类型时需要注意的是,类型名称只是文档中的另一个字段。索引中的所有文档,无论它们属于哪个类映射类型,都存放在同一个分片中。
当发送前面的命令时,Elasticsearch 只能查询属于哪个类型的文档,然后删除它们。当针对删除类型和删除完整索引两者的性能进行比较时,
这是很重要的细节。因为删除类型通常要耗费更长的时间和更多的资源。

以同样的方式,可以查询某个类型中所有的文档并删除它们,Elasticsearch 允许通过称为查询删除(delete by query)的 API 来指定自己的
查询,查找想要删除的文档。使用这个 API 和运行查询类似,除了 HTTP 请求变为 DELETE,而且 _search 的端点变为了 _query。

例如,为了从聚会索引 get-together 中移除所有匹配 “Elasticsearch” 的文档,可以运行这个命令:

1
curl -XDELETE 'localhost:9200/get-together/_query?q=elasticsearch'

和那些查询类似,可以通过查询特定的类型、多个类型、索引中的任何地方、多个索引甚至是整个索引,来运行一个删除操作。
在全部索引中查询时,通过查询的删除要特别小心。

删除索引

正如你所想,为了删除一个索引,需要发送一个 DELETE 请求到该索引的 URL:

1
curl -XDELETE 'localhost:9200/get-together'

通过提供以逗号分隔的列表,还可以删除多个索引。如果将索引名称改为 _all,甚至可以删除全部的索引/

提示:使用 curl -DELETE localhost:9200/_all 会删除所有的文档,听上去是不是很危险?可以设置 elasticsearch.yml 中的 action.destructive_requires_name: true 来预防这种情况的发生。这会使得 Elasticsearch 在删除的时候拒绝 _all 参数,以及索引名称中的通配符。

删除索引是很快的,因为它基本上就是移除了索引分片相关的文件。和删除单独的文档相比,删除文件系统中的文件会更快。这样操作的时候,文件只是被标记为已删除。在分段进行合并时,它们才会被移除。这里的合并是指将多个 Lucene 小分段组合为一个更大分段的过程。

分段与合并

一个分段是建立索引的时候所创建的一块 Lucene 索引(按照 Elasticsearch 的术语,也称作分片)。当你索引新的文档时,其内容不会添加到分段的尾部,而只会创建新的分段。由于删除操作只是将文档标记为待删除,所以分段中的数据也从来不会被移除。最终,更新文档意味着重新索引,数据就永远不会被修改。

当 Elasticsearch 在分片上进行查询的时候,Lucene 需要查询它的所有分段,合并结果,然后将其返回 – 就像查询同一个索引中多个分片的过程。就像分片那样,分段越多,搜索请求越慢。

你可能已经想到,日常的索引操作会产生很多这样的小分段。为了避免一个索引中存在过多的分段,Lucene 定期将分段进行合并。

合并文档意味着读取它们的内容(除了被删除的文档),然后利用组合的内容创建新的、更大的分段。这个过程需要资源,尤其是 CPU 和磁盘的 I/O。幸运的是,合并操作是异步运行的,Elasticsearch 也允许配置相关的若干选项。

关闭索引

除了删除索引,还可以选择关闭它们。如果关闭一个索引,就无法通过 Elasticsearch 来读取和写入其中的数据,直到再次打开它。当使用应用日志这样的流式数据时,此操作非常有用。你会在后面了解到,将流式数据以基于时间的索引方式来存储是非常棒的注意。例如,每天创建一个索引。

在现实世界中,最好永远地保存应用日志,以防要查看很久之前的信息。另一方面,在 Elasticsearch 中存放大量数据需要增加资源。对于这种使用案例,关闭旧的索引非常有意义。你可能并不需要那些数据,但是也不想删除它们。

为了关闭在线商店的索引,发送 HTTP POST 请求到该索引 URL 的 _close 端点:

1
curl -XPOST 'localhost:9200/online-shop/_close'

为了再次打开,要运行类似的命令,只是将端点换为 _open:

1
curl -XPOST 'localhost:9200/online-shop/_open'

一旦索引被关闭,它在 Elasticsearch 内存中唯一的痕迹是其元数据,如名字以及分片的位置。如果有足够的磁盘空间,而且也不确定是否需要在那个数据中再次搜索,关闭索引要比删除索引更好。关闭它们会让你非常安心,永远可以重新打开被关闭的索引,然后在其中再次搜索。

小结

  • 映射定义了文档中的字段,以及这些字段是如何被索引的。我们说 Elasticsearch 是无须模式(scheme)的,因为映射是自动扩展的,不过在实际生产中,需要经常控制哪些被索引,哪些被存储,以及如何存储。

  • 文档中的多数字段是核心类型,如字符串和数值。这些字段的索引方式对于 Elasticsearch 的表现以及搜索结果的相关性有着很大的影响。

  • 单一字段也可以包含多个字段或取值。我们了解了数组和多字段,它们让你在单一字段中拥有同一核型类型的多个实例。

  • 除了用于文档的字段,Elasticsearch 还提供了预定义的字段,如 _source 和 _all。配置这些字段将修改某些你并没有显式提供给文档的数据,但是对于性能和功能都有很大影响。例如,可以决定哪些字段需要在 _all 里索引。

  • 由于 Elasticsearch 在 Lucene 分段里存储数据,而分段一旦创建就不会修改,因此更新文档意味着检索现存的文档,将修改放入即将索引的新文档中,然后删除旧的索引。

  • 当 Lucene 分段异步合并时,就会移除待删除的文档。这也是为什么删除整个索引要比删除单个或多个文档要快 - 索引删除只是意味着移除磁盘上的文件,而且无须合并。

  • 在索引、更新和删除过程中,可以使用文档版本来管理并发问题。对于更新而言,如果因为并发问题而导致更新失败了,可以告诉 Elasticsearch 自动重试。