# 查询优化

原文链接 :[https://www.elastic.co/guide/en/elasticsearch/reference/5.3/tune-for-search-speed.html](https://www.elastic.co/guide/en/elasticsearch/reference/5.3/tune-for-search-speed.html)

译文链接 : [查询优化](/pages/viewpage.action?pageId=9405156)

贡献者 : @李坚，[ApacheCN](/display/~apachecn)，[Apache中文网](/display/~apachechina)

## 使用文件系统缓存

**Elasticsearch** 快速搜索严重依赖于文件系统缓存。一般来说,应该确保至少有一半可用内存的文件系统缓存用于 **elasticsearch** 缓存热门索引数据。

## 使用更快的硬件

如果索引受限于 _**I/O**_ ，应该增加文件系统缓存的内存或者购买更快的驱动设备。目前最快的是 _**SSD**_ 硬盘，比普通硬盘快很多。通常数据存储在本地，避免使用 **_NFS & SMB_** 等远程文件系统。还要小心虚拟化存储，像亚马逊的 _弹性块存储_（_Elastic Block Storage_）。**Elasticsearch** 在虚拟存储上也有比较好的性能，具有搜索快，安装便捷的特性；然而相对于本地专用存储，他就要慢的多了。如果你在 _**EBS**_上使用 _index ，_一定要使用 _IOPS_ 否则操作会很快挂掉。

如果搜索受限于 **_CPU_ **，更换更快的 _**CPUs**_。

## 文档建模

为了尽可能的节省搜索时间，应该为 _Documents_ 创建模版。特别是要避开联合查询，嵌套查询也会慢好几倍，子查询将会慢好几百倍。同一个问题的结果不加入非规范化 _documents_ 加速效果是可以预期的。

## 预索引数据

利用索引查询数据是最优的方式。例如，如果所有的文档都有 _price_ 字段，并且大多数查询都在一个固定的范围列表中运行范围聚合，那么可以通过将 _index_ 预索引到 _index_ 和使用 _terms_ 聚合来更快地实现聚合。

例如，像下面这样：

```
PUT index/type/1
{
  "designation": "spoon",
  "price": 13
}
```

像这样的查询：

```
GET index/_search
{
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 10 },
          { "from": 10, "to": 100 },
          { "from": 100 }
        ]
      }
    }
  }
}
```

文档在索引的时候要使用 **_price_range_** ，应该被映射为关键词：

```
PUT index
{
  "mappings": {
    "type": {
      "properties": {
        "price_range": {
          "type": "keyword"
        }
      }
    }
  }
}

PUT index/type/1
{
  "designation": "spoon",
  "price": 13,
  "price_range": "10-100"
}
```

然后这个请求就直接聚合新字段，而不是在 _price_ 字段运行范围查询：

```
GET index/_search
{
  "aggs": {
    "price_ranges": {
      "terms": {
        "field": "price_range"
      }
    }
  }
}
```

## 映射

一些数据是数字并不意味着它应该被映射为数值型字段。通畅情况下，字段存储标识符如 ISBN（国际标准图书编号）或者其他数据库任何数字标识的记录，可能受益于映射为 _关键词_ 而不是 _整数（integer）_ 或者 _长整数（long）_。

## 避免使用脚本

通常避免使用脚本，如果他们是绝对必要的，你应该更喜欢  _**painless**_ 和 _**expressions**_ 引擎。

## 根据日期查询

_now_ 使用在 _date field_（日期字段）的查询通常不可缓存，因为正在匹配的范围始终会更改。 然而，使用特定范围日期查询，在用户体验方面更佳，并且查询时能够更好地利用高速缓存。

例如下面的查询：

```
PUT index/type/1
{
  "my_date": "2016-05-11T16:30:55.328Z"
}

GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h",
            "lte": "now"
          }
        }
      }
    }
  }
}
```

可以用以下查询替换：

```
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "my_date": {
            "gte": "now-1h/m",
            "lte": "now/m"
          }
        }
      }
    }
  }
}
```

在这种情况下，我们四舍五入到分钟，所以如果当前时间是16:31:29，范围查询将匹配 _my_date_ 字段的值在 15:31:00 到 16:31:59 之间的所有内容。 如果有几个用户在同一分钟内运行包含此范围的查询，查询缓存可以帮助您加快速度。 用于舍入的间隔越长，查询缓存可以帮助越多，但要注意的是，太长时间的舍入也可能严重影响查询性能。

**NOTE：** 将查询内容分为一大部分可以缓存和一小部分不可以缓存；这样能够有效的利用缓存来查询，如下所示：

```
GET index/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "bool": {
          "should": [
            {
              "range": {
                "my_date": {
                  "gte": "now-1h",
                  "lte": "now-1h/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gt": "now-1h/m",
                  "lt": "now/m"
                }
              }
            },
            {
              "range": {
                "my_date": {
                  "gte": "now/m",
                  "lte": "now"
                }
              }
            }
          ]
        }
      }
    }
  }
}
```

## 预加载 global ordinals

_**global ordinals** _是用于在 _keywords_ 字段上运行 _terms_ 聚合的数据结构。它们将会被加载到内存中，因为  _elasticsearch_ 不知道哪个字段将会用于 _terms_ 聚合，哪些字段不会被用到。像下面一样在配置映射刷新的时候让 _elasticsearch_ 去加载 _global ordinals_：

```
PUT index
{
  "mappings": {
    "type": {
      "properties": {
        "foo": {
          "type": "keyword",
          "eager_global_ordinals": true
        }
      }
    }
  }
}
```

## 预加载文件系统缓存

如果运行 _elasticsearch_ 的机器重启了，那么文件系统缓存将会被清空。为了能查询的更快一些，机器启动后 _elasticsearch_ 需要花费一些时间将 index 的热区域加载到内存中。可以通过设置 **_index.store.preload_**来告诉 _elasticsearch_ 哪些文件扩展名的文件将会被加载到内存中。

 **WARNING：**如果加载了太多的索引或者文件到文件系统缓存中并且超出了文件系统缓存的大小，将会使搜索变得很慢，请**谨慎使用**此功能。