5 分钟启一个 ES

单节点本地学习用。先确认 vm.max_map_count 足够大(ES 默认推荐 262144),Linux 上:

sudo sysctl -w vm.max_map_count=262144

docker-compose 启动 7.17(最后一个 7.x LTS):

services:
  es:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.10
    environment:
      - discovery.type=single-node
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    ulimits:
      memlock: { soft: -1, hard: -1 }

健康检查:

curl http://localhost:9200/_cluster/health

中文分词:IK

默认分词器对中文不友好(会把每个字拆开)。装 IK 插件:

docker exec -it es bash
./bin/elasticsearch-plugin install \
  https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.10/elasticsearch-analysis-ik-7.17.10.zip
exit
docker restart es

两个分词模式:

  • ik_max_word:最细粒度拆分(适合索引时)
  • ik_smart:粗粒度拆分(适合查询时)

Mapping 与字段类型

ES 7 起,type 概念被弱化(一个 index 只有一个 _doc)。建一个商品索引:

PUT /products
{
  "mappings": {
    "properties": {
      "title":       { "type": "text", "analyzer": "ik_max_word",
                       "fields": { "keyword": { "type": "keyword" } } },
      "brand":       { "type": "keyword" },
      "price":       { "type": "double" },
      "stock":       { "type": "integer" },
      "tags":        { "type": "keyword" },
      "created_at":  { "type": "date" }
    }
  }
}

要点:

  • text 字段会被分词,keyword 字段不会;常用双字段(title + title.keyword)兼顾全文检索与精确过滤。
  • Mapping 一旦建好,已存在字段的类型不可改。要改只能 reindex 到新 index。
  • 日期字段支持 date、date_nanos,默认 ISO-8601。

查询 DSL

基础查询

# 全文匹配(分词后匹配)
GET /products/_search
{
  "query": { "match": { "title": "运动 鞋" } }
}

# 精确匹配(不分词)
GET /products/_search
{
  "query": { "term": { "brand": "nike" } }
}

# 短语匹配(保持顺序、紧邻)
GET /products/_search
{
  "query": { "match_phrase": { "title": "篮球鞋" } }
}

复合查询

GET /products/_search
{
  "query": {
    "bool": {
      "must":   [ { "match": { "title": "跑鞋" } } ],
      "filter": [ { "range": { "price": { "gte": 100, "lte": 500 } } } ],
      "must_not": [ { "term": { "tags": "停产" } } ]
    }
  }
}
  • must:参与打分
  • filter:只过不过滤、不打分,更快
  • must_not:反向排除

高亮

GET /products/_search
{
  "query":  { "match": { "title": "运动鞋" } },
  "highlight": { "fields": { "title": {} } }
}

返回里每个 hit 的 highlight.title 会有 <em> 包裹的命中片段。

聚合

按品牌分桶求平均价格:

GET /products/_search
{
  "size": 0,
  "aggs": {
    "by_brand": {
      "terms": { "field": "brand", "size": 10 },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    }
  }
}
  • size: 0:不返回文档,只返回聚合结果
  • terms:分桶聚合,类似 SQL 的 GROUP BY
  • avg / sum / min / max / stats:指标聚合

关联建模

nested:一对多嵌套

一个用户有多个地址,地址里要按省/市分别过滤:

PUT /users
{
  "mappings": {
    "properties": {
      "login_name": { "type": "keyword" },
      "addresses": {
        "type": "nested",
        "properties": {
          "province": { "type": "keyword" },
          "city":     { "type": "keyword" }
        }
      }
    }
  }
}
GET /users/_search
{
  "query": {
    "nested": {
      "path": "addresses",
      "query": { "term": { "addresses.province": "上海" } }
    }
  }
}

join:父子关系

父子必须在同一 index(ES 限制):

PUT /ecom
{
  "mappings": {
    "properties": {
      "category_name": { "type": "text", "analyzer": "ik_max_word",
                         "fields": { "keyword": { "type": "keyword" } } },
      "join_field": {
        "type": "join",
        "relations": { "category": "product" }
      }
    }
  }
}

查询父下所有子:

GET /ecom/_search
{
  "query": {
    "has_child": {
      "type": "product",
      "query": { "range": { "price": { "gte": 5000, "lte": 10000 } } }
    }
  }
}

实战经验

  • 深分页别用 from + size 大数,改用 search_after 或滚动 API。
  • 默认 10000 条上限,超限会报 result_window_too_large,调大 index.max_result_window 是最后手段。
  • 建索引时一次性把 mapping 设对,改 mapping 代价大。
  • 日期字段排序慢,考虑 date + keyword 双字段做精确查找。

使用准则

  1. ES 7+ 起不再需要 type;新建索引直接用 _doc。
  2. 中文检索必装 IK;索引 ik_max_word,查询 ik_smart。
  3. 聚合前确认字段是 keyword 或数值类型——text 字段直接聚合会报错。
  4. nested 字段查询必须包在 nested query 里,否则退化为扁平数组匹配。
参考资料