Blog
Elasticsearch
Elasticsearch 入門指南

Elasticsearch 入門指南

本篇將介紹 Elasticsearch 的核心概念與常用查詢語法,適合初次接觸的開發者。

Elasticsearch 是基於 Apache Lucene 的分散式搜尋引擎,主要應用於全文搜尋、日誌分析、即時數據分析等場景。

核心概念

與 SQL 的對比

ElasticsearchSQL說明
IndexDatabase存放資料的地方
DocumentRow一筆 JSON 格式的資料
FieldColumn資料中的欄位
MappingSchema定義欄位的資料類型

常用資料類型

類型用途範例
text全文搜尋,會分詞商品描述、文章內容
keyword精確匹配,不分詞狀態碼、標籤、ID
integer整數庫存數量
date日期時間建立時間
geo_point地理座標店家位置

倒排索引

這是 Elasticsearch 快速搜尋的關鍵。

傳統資料庫Elasticsearch
搜尋方式逐筆掃描每個文件查詢倒排索引
100 萬筆資料比對 100 萬次直接查表取得結果

範例

假設有三筆資料

文件1: "鼎泰豐小籠包"
文件2: "添好運港式點心"
文件3: "一蘭拉麵"

建立的倒排索引

"鼎泰豐" → [文件1]
"小籠包" → [文件1]
"添好運" → [文件2]
"一蘭"   → [文件3]
"拉麵"   → [文件3]

搜尋「小籠包」時,直接查表得到「文件1」,不需要掃描所有資料。

Query DSL

Query DSL (Domain Specific Language) 是 Elasticsearch 的查詢語言,使用 JSON 格式定義查詢條件。以下用「餐廳搜尋」情境來介紹各種查詢。

範例資料

假設 restaurants index 中有以下 5 筆文件:

[
  { "id": 1, "name": "鼎泰豐小籠包", "country": "台灣", "price": 450, "stars": 1, "status": "open" },
  { "id": 2, "name": "添好運點心", "country": "台灣", "price": 200, "stars": 1, "status": "open" },
  { "id": 3, "name": "小籠包專賣店", "country": "台灣", "price": 800, "stars": 2, "status": "closed" },
  { "id": 4, "name": "Sushi Saito", "country": "日本", "price": 1500, "stars": 3, "status": "open" },
  { "id": 5, "name": "一蘭拉麵", "country": "日本", "price": 350, "stars": 0, "status": "open" }
]

Match Query

全文搜尋,會分詞,適用於 text 欄位。搜尋名稱包含「小籠包」的餐廳:

GET restaurants/_search
{
  "query": {
    "match": { "name": "小籠包" }
  }
}

搜尋結果:

{
  "hits": {
    "total": { "value": 2 },
    "hits": [
      { "_id": "1", "_source": { "name": "鼎泰豐小籠包", "country": "台灣", "price": 450 } },
      { "_id": "3", "_source": { "name": "小籠包專賣店", "country": "台灣", "price": 800 } }
    ]
  }
}
結果餐廳原因
鼎泰豐小籠包名稱包含「小籠包」
小籠包專賣店名稱包含「小籠包」
添好運點心、Sushi Saito、一蘭拉麵名稱不含「小籠包」

Term Query

精確匹配,不分詞,適用於 keyword 欄位。搜尋台灣的餐廳:

GET restaurants/_search
{
  "query": {
    "term": { "country": "台灣" }
  }
}

搜尋結果:

{
  "hits": {
    "total": { "value": 3 },
    "hits": [
      { "_id": "1", "_source": { "name": "鼎泰豐小籠包", "country": "台灣" } },
      { "_id": "2", "_source": { "name": "添好運點心", "country": "台灣" } },
      { "_id": "3", "_source": { "name": "小籠包專賣店", "country": "台灣" } }
    ]
  }
}
結果餐廳原因
鼎泰豐小籠包、添好運點心、小籠包專賣店country = 台灣
Sushi Saito、一蘭拉麵country = 日本

Range Query

範圍查詢,支援 gt(大於)、gte(大於等於)、lt(小於)、lte(小於等於)。搜尋價格 500 以下的餐廳:

GET restaurants/_search
{
  "query": {
    "range": {
      "price": { "lte": 500 }
    }
  }
}

搜尋結果:

{
  "hits": {
    "total": { "value": 3 },
    "hits": [
      { "_id": "1", "_source": { "name": "鼎泰豐小籠包", "price": 450 } },
      { "_id": "2", "_source": { "name": "添好運點心", "price": 200 } },
      { "_id": "5", "_source": { "name": "一蘭拉麵", "price": 350 } }
    ]
  }
}
結果餐廳原因
鼎泰豐小籠包、添好運點心、一蘭拉麵price ≤ 500
小籠包專賣店 (800)、Sushi Saito (1500)price > 500

Bool Query

組合多個查詢條件,是實務上最常用的查詢方式。Elasticsearch 會計算每筆資料與查詢的「相關程度」(_score),分數越高代表越相關,結果預設按分數排序。

類型說明計算分數
must必須符合,類似 SQL 的 AND
filter必須符合,但不計算分數,效能較好
should至少符合一個,類似 SQL 的 OR
must_not必須不符合,類似 SQL 的 NOT

mustfilter 都是「必須符合」,差別在於 filter 不計算分數,因此效能更好且結果會被快取。實務上,全文搜尋用 must,精確條件用 filter

must - 搜尋名稱包含「小籠包」的餐廳:

GET restaurants/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "小籠包" } }
      ]
    }
  }
}
// 結果:2 筆
{ "name": "鼎泰豐小籠包" },
{ "name": "小籠包專賣店" }

filter - 搜尋台灣的餐廳(不計算分數,效能較好):

GET restaurants/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "country": "台灣" } }
      ]
    }
  }
}
// 結果:3 筆
{ "name": "鼎泰豐小籠包", "country": "台灣" },
{ "name": "添好運點心", "country": "台灣" },
{ "name": "小籠包專賣店", "country": "台灣" }

should - 搜尋一星或二星的餐廳:

GET restaurants/_search
{
  "query": {
    "bool": {
      "should": [
        { "term": { "stars": 1 } },
        { "term": { "stars": 2 } }
      ],
      "minimum_should_match": 1
    }
  }
}
// 結果:3 筆
{ "name": "鼎泰豐小籠包", "stars": 1 },
{ "name": "添好運點心", "stars": 1 },
{ "name": "小籠包專賣店", "stars": 2 }

must_not - 搜尋非日本的餐廳:

GET restaurants/_search
{
  "query": {
    "bool": {
      "must_not": [
        { "term": { "country": "日本" } }
      ]
    }
  }
}
// 結果:3 筆
{ "name": "鼎泰豐小籠包", "country": "台灣" },
{ "name": "添好運點心", "country": "台灣" },
{ "name": "小籠包專賣店", "country": "台灣" }

組合範例 1 - 台灣 + 價格 500 以下:

GET restaurants/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "country": "台灣" } },
        { "range": { "price": { "lte": 500 } } }
      ]
    }
  }
}
// 結果:2 筆
{ "name": "鼎泰豐小籠包", "country": "台灣", "price": 450 },
{ "name": "添好運點心", "country": "台灣", "price": 200 }
// ✗ 小籠包專賣店:price = 800 > 500

組合範例 2 - 名稱含「小籠包」+ 排除已歇業:

GET restaurants/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "小籠包" } }
      ],
      "must_not": [
        { "term": { "status": "closed" } }
      ]
    }
  }
}
// 結果:1 筆
{ "name": "鼎泰豐小籠包", "status": "open" }
// ✗ 小籠包專賣店:status = closed

組合範例 3 - 台灣 + 營業中 + 一星或二星 + 名稱含「小籠包」+ 價格 500 以下:

GET restaurants/_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "name": "小籠包" } }
      ],
      "filter": [
        { "term": { "country": "台灣" } },
        { "range": { "price": { "lte": 500 } } }
      ],
      "must_not": [
        { "term": { "status": "closed" } }
      ],
      "should": [
        { "term": { "stars": 1 } },
        { "term": { "stars": 2 } }
      ],
      "minimum_should_match": 1
    }
  }
}
// 結果:1 筆
{ "name": "鼎泰豐小籠包", "country": "台灣", "price": 450, "stars": 1, "status": "open" }
餐廳結果原因
鼎泰豐小籠包符合所有條件
添好運點心must 不符合:名稱不含「小籠包」
小籠包專賣店must_not 排除:status = closed
Sushi Saitofilter 不符合:country ≠ 台灣
一蘭拉麵filter 不符合:country ≠ 台灣

from / size 用於分頁,但 from + size 不能超過 10,000,深度分頁需使用 search_after

聚合分析

類似 SQL 的 GROUP BY,用於統計分析。size: 0 表示不回傳文件,只回傳聚合結果。

統計各國家的餐廳數量(等同 SELECT country, COUNT(*) FROM restaurants GROUP BY country):

GET restaurants/_search
{
  "size": 0,
  "aggs": {
    "by_country": {
      "terms": { "field": "country" }
    }
  }
}
// 結果
{
  "aggregations": {
    "by_country": {
      "buckets": [
        { "key": "台灣", "doc_count": 3 },
        { "key": "日本", "doc_count": 2 }
      ]
    }
  }
}

統計各國家的平均價格

GET restaurants/_search
{
  "size": 0,
  "aggs": {
    "by_country": {
      "terms": { "field": "country" },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    }
  }
}
// 結果
{
  "aggregations": {
    "by_country": {
      "buckets": [
        { "key": "台灣", "doc_count": 3, "avg_price": { "value": 483.33 } },
        { "key": "日本", "doc_count": 2, "avg_price": { "value": 925 } }
      ]
    }
  }
}

統計各國家的星等分佈(巢狀聚合):

GET restaurants/_search
{
  "size": 0,
  "aggs": {
    "by_country": {
      "terms": { "field": "country" },
      "aggs": {
        "by_stars": { "terms": { "field": "stars" } }
      }
    }
  }
}
// 結果
{
  "aggregations": {
    "by_country": {
      "buckets": [
        {
          "key": "台灣",
          "doc_count": 3,
          "by_stars": {
            "buckets": [
              { "key": 1, "doc_count": 2 },
              { "key": 2, "doc_count": 1 }
            ]
          }
        },
        {
          "key": "日本",
          "doc_count": 2,
          "by_stars": {
            "buckets": [
              { "key": 0, "doc_count": 1 },
              { "key": 3, "doc_count": 1 }
            ]
          }
        }
      ]
    }
  }
}

常見問題

Elasticsearch 和 MySQL 有什麼差異?

ElasticsearchMySQL
索引方式倒排索引B+ Tree
適合場景全文搜尋、日誌分析交易處理、關聯查詢
一致性最終一致性強一致性

為什麼 Elasticsearch 搜尋這麼快?

  1. 倒排索引:詞彙直接對應文件,不需逐筆掃描
  2. 分散式架構:查詢可以在多個節點並行執行
  3. 快取機制:Filter 查詢結果會被快取

Shard 和 Replica 是什麼?

  1. Shard:將資料水平切分,分散儲存與查詢負載
  2. Replica:Shard 的副本,提供高可用性與讀取負載均衡
© 2024 - 2026 Chia-Yu Su All rights reserved.