ElasticSearch - index mapping(5.x以上)

古古

2018/06/07


  • Elasticsearch支持的基本類型

    • 字符串 : text, keyword
      • text : 存儲數據的時候,會自動分詞,並生成索引
      • keyword : 存儲數據的時候,不會分詞,而是直接整個詞拿去建索引
    • 整數 : byte, short, integer, long
    • 浮點數 : float, double
    • 布爾型 : boolean
    • 日期 : date
  • 自定義 mapping

    • index : 設置此字段能不能被查詢,就是決定要不要將這個字段放進倒排索引裡

      • 若 index 設置為 true(默認是 true),則表示這個這個字段會被放進倒排索引裡,如果是 text 就是分詞過後放進索引,如果是 keyword、integer…就直接整段放進索引裡
      • 若 index 設置為 false,則表示這個字段不放進倒排索引裡,因此不能查詢這個字段(因為他不存在於倒排索引裡)
        • 通常這種被設成 false 的字段,可以想像成是屬於一種附屬的字段,就是不能被 match、term 查詢,但是當該文檔被其他搜索條件搜出來時,他可以附帶的一起被找出來,因為他們同屬於同一個文檔
    • analyzer : 主要用在 text 類型的字段上,就是設定要使用哪種分詞器來建立索引

      • 可以使用內建的分詞器,或是使用自定義的分詞器
    • 可以使用 /_analyze 測試分析器具體會將句子分詞成什麼樣子,它能幫助我們理解 Elasticsearch 索引內部發生了什麼

      GET _analyze
      {
          "analyzer": "standard",
          "text": "Text to analyze"
      }
      
    • 具體實例

      • mapping

        PUT mytest
        {
            "mappings": {
                "doc": {
                    "properties": {
                        "name": {
                            "type": "keyword",
                            "index": false
                        },
                        "uid": {
                            "type": "integer"
                        },
                        "nickname": {
                            "type": "text",
                            "analyzer": "standard"
                        }
                    }
                }
            }
        }
        
      • 搜索 uid 時,name 會一起被找出來

        GET mytest/_search
        {
            "query": {
                "term": {
                    "uid": 1
                }
            }
        }
        
        {
            "uid": 1,
            "name": "1-hello",
            "nickname": "1-nickname"
        }
        
      • 搜索 name,會報 error

        GET mytest/_search
        {
            "query": {
                "term": {
                    "name": "1-hello"
                }
            }
        }
        
        "error": {
            "type": "illegal_argument_exception",
            "reason": "Cannot search on field [name] since it is not indexed."
        }
        
  • 更新 mapping

    • 當首次創建一個 index 的時候,可以指定類型的 mapping,但假設後來想要增加一個新的映射字段,可以使用 /_mapping 把新的字段加進 mapping 映射裡

      • 可以增加一個新的映射,但是不能修改存在的映射,原因是因為這個映射可能有文檔去用,如果改了映射的類型,可能會導致 index 的數據出錯,因此只能新加字段進去,不能修改
    • 具體實例

      • 在 mytest 映射中的 doc 類型增加一個新的名爲 tag 的 keyword

        PUT mytest/_mapping/doc
        {
            "properties": {
                "tag": {
                    "type": "keyword"
                }
            }
        }
        
    • 在 ES 中更新 mapping(新增一個字段)會產生的問題

      • 雖然 ES 支持更新映射在 mapping 中新增字段,但是這樣會造成一個問題,就是舊的文檔並不會自動更新產生新的字段

        • 假設舊文檔有 name、nickname 這兩個字段,並且已經插入了兩筆數據

          { "name": "Jackson", "nickname": "jack" }
          { "name": "Amy", "nickname": "a" }
          
        • 此時插入一個新的字段 sex,並且插入一筆新的數據

          { "name": "NewYork", "nickname": "new", "sex": 1 }
          
        • 執行 "match_all": {} 查詢時,結果如下

          • 由於 Jackson 和 Amy 並沒有 sex 這個字段,所以雖然執行 match_all 搜索時能被搜出來,但是搜索 "term": { "sex": 1 } 的話,就搜索不出來

            { "name": "Jackson", "nickname": "jack" }
            { "name": "Amy", "nickname": "a" }
            { "name": "NewYork", "nickname": "new", "sex": 1 }
            
          • 我們希望的狀況是,增加一個新字段,舊文檔應該也要設置一個預設值,像是 sex=0,而不是整個字段消失,如此在查詢時邏輯才不會有問題

      • 解決舊文檔沒有新字段這個問題主要有3種方法

        • 新建一個 index,使用 scroll + bulk insert 將數據從舊的索引批量插入到新索引中

          • 可以在 java 裡自定義舊文檔預設值的邏輯
        • 新建一個 index,使用 ES 提供的 reindex API 將數據從舊的索引 reindex 到新索引

          • 需要使用 ES 的腳本 script,在 reindex 的請求裡定義舊文檔預設值的邏輯

            POST _reindex
            {
                "source": {
                    "index": "mytest"
                },
                "dest": {
                    "index": "mytest2"
                },
                "script": {
                    "source": "if (ctx._source.new == null) {ctx._source.new = params.new}",
                    "params": {
                        "new": "good"
                    },
                    "lang": "painless"
                }
            }
            
        • 使用 update_by_query 更新所有舊文檔,給他們加上新的字段