Elasticsearch 入門指南
本篇將介紹 Elasticsearch 的核心概念與常用查詢語法,適合初次接觸的開發者。
Elasticsearch 是基於 Apache Lucene 的分散式搜尋引擎,主要應用於全文搜尋、日誌分析、即時數據分析等場景。
核心概念
與 SQL 的對比
| Elasticsearch | SQL | 說明 |
|---|---|---|
| Index | Database | 存放資料的地方 |
| Document | Row | 一筆 JSON 格式的資料 |
| Field | Column | 資料中的欄位 |
| Mapping | Schema | 定義欄位的資料類型 |
常用資料類型
| 類型 | 用途 | 範例 |
|---|---|---|
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 | ✗ |
must和filter都是「必須符合」,差別在於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 Saito | ✗ | filter 不符合: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 有什麼差異?
| Elasticsearch | MySQL | |
|---|---|---|
| 索引方式 | 倒排索引 | B+ Tree |
| 適合場景 | 全文搜尋、日誌分析 | 交易處理、關聯查詢 |
| 一致性 | 最終一致性 | 強一致性 |
為什麼 Elasticsearch 搜尋這麼快?
- 倒排索引:詞彙直接對應文件,不需逐筆掃描
- 分散式架構:查詢可以在多個節點並行執行
- 快取機制:Filter 查詢結果會被快取
Shard 和 Replica 是什麼?
- Shard:將資料水平切分,分散儲存與查詢負載
- Replica:Shard 的副本,提供高可用性與讀取負載均衡