Elasticsearch-Mapping 映射
Elasticsearch Mapping 映射,即 Schema 定义
Elasticsearch Guide [7.17] » Mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping.html
mapping 用来规定 index 中的字段数据类型,类似 metadata 元数据、schema 等概念。
动态 Mapping
Elasticsearch Guide [7.17] » Mapping » Dynamic mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic-mapping.html
不需要提前定义 mapping,甚至不需要提前创建 index,直接向一个 index 插入任意字段,ES 都会自动创建 index,并自动添加 field,这就叫 动态映射。
字段类型自动映射
Elasticsearch Guide [7.17] » Mapping » Dynamic mapping » Dynamic field mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic-field-mapping.html
JSON 字段类型的自动识别如下:
JSON数据类型 | “dynamic”:”true” | “dynamic”:”runtime” |
---|---|---|
null | 不添加字段 | 不添加字段 |
true 或 false | boolean | boolean |
double | float | double |
integer | long | long |
object | object | 不添加字段 |
array | 取决于第一个非空元素的类型 | 取决于第一个非空元素的类型 |
日期格式的string | date | date |
数字格式的string | float 或 long | double 或 long |
非日期且非数字格式的string | 带 .keyword 子字段的 text | keyword |
显式 Mapping
Elasticsearch Guide [7.17] » Mapping » Explicit mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/explicit-mapping.html
为什么需要自定义mapping?
虽然 elasticsearch 中已经有动态 mapping(Dynamic Mapping),而且新增字段默认也会添加新的 mapping,但是毕竟是机器,有时会推算的不对,比如地理位置信息,特殊格式化的日期类型等。这时,如果需要 es 提供排序、聚合等查询功能,就不能满足我们的需求。
通过手动设置 mapping,我们可以
指定哪个字段作为全文索引
指定哪个字段包含数字、日志、地理位置信息
指定日期的格式
指定字段的分词器(比如中文字段想使用 ik 分词器)
定义动态 mapping 的规则
显式自定义 mapping 和 动态 mapping 可以结合使用,例如对于想明确指定类型的字段使用自定义 mapping,其他字段使用动态 mapping
mapping 会把 JSON 文档文档映射成 Lucene 所需要的扁平格式
一个 mapping 属于一个索引的 type,每个文档都属于一个 Type,一个 type 有一个 mapping 定义
从 es 7.0 开始,不需要在 mapping 定义中指定 type 信息,因为默认每个索引只有一个 type 叫 _doc
创建index时指定mapping
创建 index 的同时可指定 mapping,例如
PUT /my-index-000001
{
"mappings":{
"properties":{
"firstName":{
"type":"text", //text类型全文搜索
"fields":{
"keyword":{
"type":"keyword", //keyword支持聚合查询
"ignore_above":256
}
}
},
"lastName":{
"type":"keyword",
"null_value":"NULL" //支持字段为null,只有keyword类型支持
},
"mobile":{
"type":"text",
"index":false //此字段不被索引
},
"address":{
"type":"text",
"index_options":"offsets" //控制倒排索引记录的内容。offsets最多,记录四个
}
}
}
}
添加字段到已有mapping中
可以使用 Update mapping API 向已有 index 的 mapping 中添加一个或多个字段,例如:
PUT /my-index-000001/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false
}
}
}
通过上面的调用,添加了一个 employee-id
字段,类型为 keyword
,但不被索引。
更新已有index的mapping
不能修改已有字段的数据类型,否则索引数据会失效,只能修改 字段属性
如果想修改索引的字段类型,可以创建一个新索引,然后将已有索引的数据重新索引(reindex)过去。
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html
Runtime fields 运行时字段(7.11+)
Elastic Docs ›Elasticsearch Guide [8.8] ›Mapping
https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html
运行时字段是 es 7.11+ 新增的,可以在创建 mapping 或 请求中定义,可以实现例如 存储年龄数值,但查询返回 老(60+)中(40-60)青(30-)这种动态映射,还有比如分别存储ip和port,但查询返回ip:port
运行时字段(runtime fields)是在查询时评估的字段。 运行时字段使你能够:
- 将字段添加到现有文档中,而无需重新索引数据
- 在不了解数据结构的情况下开始使用数据
- 覆盖查询时从索引字段返回的值
- 为特定用途定义字段,而无需修改基础架构
Elasticsearch:使用 Runtime fields 对索引字段进行覆盖处理以修复错误 - 7.11 发布
https://blog.csdn.net/UbuntuTouch/article/details/113795062
元数据字段
Elasticsearch Guide [7.17] » Mapping » Metadata fields
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-fields.html
_id 字段
Elasticsearch Guide [7.17] » Mapping » Metadata fields » _id field
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-id-field.html
每个文档都有唯一的一个 _id
字段,可以通过 GET /index/_doc/_id
来查询。_id
可在插入文档时指定,也可以由 ES 自动生成,_id
字段的类型无法在 mapping 中配置。
_routing 字段(Elasticsearch 分片策略/片键)
Elasticsearch Guide [7.17] » Mapping » Metadata fields » _routing field
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-routing-field.html
文档根据下面的公式被路由到指定的分片中:
routing_factor = num_routing_shards / num_primary_shards
shard_num = (hash(_routing) % num_routing_shards) / routing_factor
num_routing_shards
是索引的 index.number_of_routing_shards
配置值num_primary_shards
是索引的 index.number_of_shards
配置值
默认的 _routing
路由字段(片键)是 _id
,可在文档级别自定义路由字段,插入文档时指定路由字段后,搜索时也要指定路由字段。
_source
字段数据类型
Elasticsearch Guide [7.17] » Mapping » Field data types
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-types.html
text
https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html
text 类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许 es 来检索这些词语。text 类型不能用来排序和聚合。
keyword
https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html
Keyword 类型不需要进行分词,可以被用来检索过滤(term级查询)、排序和聚合。keyword 类型字段只能用本身来进行检索
keyword最大长度:32k字节(8191字符)
keyword 类型的最大支持的长度为 32766 字节(Lucene 的限制),如果存 UTF-8 中文字符的话大概是 32766 / 4 = 8191 个字符。text 对字符长度没有限制
超过后索引报错:
max_bytes_length_exceeded_exception: bytes can be at most 32766 in length; got xxx
type=illegal_argument_exception, reason=Document contains at least one immense term in field="content.keyword" (whose UTF8 encoding is longer than the max length 32766), all of which were skipped. Please correct the analyzer to not produce such terms. original message: bytes can be at most 32766 in length; got 63192
设置 ignore_above 后,超过给定长度后的数据将不被索引,无法通过 term 精确匹配检索返回结果。
This option is also useful for protecting against Lucene’s term byte-length limit of 32766.
The value for ignore_above is the character count, but Lucene counts bytes. If you use UTF-8 text with many non-ASCII characters, you may want to set the limit to 32766 / 4 = 8191 since UTF-8 characters may occupy at most 4 bytes.
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/ignore-above.html
例如 message 字段有个子字段 keyword,类型为 keyword,设置最大长度 8191,避免 message 过长超过 32766 字节后报错
根据经验,建议可能存储变长文本的 keyword 字段都加上 ignore_above 属性,避免存入字符串过长报错
PUT my_index
{
"mappings": {
"properties": {
"message": {
"type": "text",
"fields":{
"keyword":{
"type": "keyword",
"ignore_above": 8191
}
}
}
}
}
}
text和keyword的区别
text和keyword的区别
- text类型:支持分词、全文检索,不支持聚合、排序操作。适合大字段存储,如:文章详情、content字段等;
- keyword类型:支持精确匹配,支持聚合、排序操作。适合精准字段匹配,如:url、name、status、gender 等字段。
两者结合使用:text有个keyword类型的子字段
dynamic 动态映射中,es的类型自动推断会将 非数字非日期 的字符串 类型保存为 text 类型,同时有个 keyword 类型的子字段。
对于文字标题 title 这种较短的文本,有时候需要全文检索,有时候又需要精确匹配,可以参照 es 动态映射的类型定义方式:
title 本身是 text 类型,可以指定分析器 ik_max_word,同时有个 keyword 类型的子字段 keyword(字段名就叫 keyword) 可用于精确匹配:
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
date
https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html
Date 类型在 es 内部被转为 UTC 并存储为 long 型毫秒时间戳。
Date 类型的查询内部以 range 查询实现。
1、Date 类型的默认格式
如果不指定 Date 的 format 格式,使用默认 strict_date_optional_time
配置项配置的格式或毫秒时间戳,即:strict_date_optional_time||epoch_millis
strict_date_optional_time
是 ISO 日期时间格式,格式为 yyyy-MM-dd
,如果有时间的话必以 T
分割 yyyy-MM-dd'T'HH:mm:ss.SSSZ
Elastic Docs ›Elasticsearch Guide [8.6] ›Mapping ›Mapping parameters
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#strict-date-time
2、多日期格式
可指定多个日期格式
"mappings": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
其他类型
long, integer, short, byte, double, float
boolean
IPv4&IPv6
arrays 数组类型
https://www.elastic.co/guide/en/elasticsearch/reference/current/array.html
在 Elasticsearch 中,没有专门的数组数据类型。默认情况下,任何字段都可以包含零个或多个值,但是,数组中的所有值必须具有相同的数据类型
long 类型存储一个值是 long 类型,存储多个自然就成为 long 数组类型;
keyword 类型存储一个值是 keyword 类型,存储多个值就成为 keyword 数组类型。
object 对象类型
https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html
object 类型用于存储 JSON 对象,支持字段内嵌套子字段。
创建索引,user 字段类型是 object,内部包含 name, age, address 三个子字段,其中 address 也是个 object 字段,内含 street, city 两个子字段。
PUT /my_index
{
"mappings": {
"properties": {
"user": {
"type": "object",
"properties": {
"name": {"type": "text"},
"age": {"type": "integer"},
"address": {
"type": "object",
"properties": {
"street": {"type": "text"},
"city": {"type": "keyword"}
}
}
}
}
}
}
}
插入文档:
POST /my_index/_doc/1
{
"user": {
"name": "John Doe",
"age": 30,
"address": {"street": "123 Main St", "city": "New York"}
}
}
注意:在底层存储中,object 类型会被平铺为 user.name、user.address.city 等形式,这可能导致查询时无法区分同一数组内的不同对象(需用 nested 类型解决)
nested 嵌套文档
https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html
普通 object 对象在 es 中平铺存储导致丢失关联关系
普通 object 在es中存储时会打平,导致丢失关联关系。
例如创建 test_user 索引:
PUT /test_user
{
"mappings": {
"properties": {
"user_id": {zi
"type": "long"
},
"user_name": {
"type": "text"
},
"address": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text"
},
"address": {
"type": "text"
}
}
}
}
}
}
插入数据:
POST /test_user/_doc
{
"user_id": 1,
"user_name": "张三",
"address": [
{
"id": 1,
"name": "公司",
"address": "北京市海淀区"
},
{
"id": 2,
"name": "家",
"address": "北京市昌平区"
}
]
}
查询:
POST /test_user/_search
{
"query": {
"bool": {
"must": [
{ "match": { "address.id": 1 }},
{ "match": { "address.name":"家"}}
]
}
}
}
结果:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.287682,
"hits": [
{
"_index": "test_user",
"_type": "_doc",
"_id": "Bi-25osB0YdeJP5JAWyt",
"_score": 1.287682,
"_source": {
"user_id": 1,
"user_name": "张三",
"address": [
{
"id": 1,
"name": "公司",
"address": "北京市海淀区"
},
{
"id": 2,
"name": "家",
"address": "北京市昌平区"
}
]
}
}
]
}
}
address.id=1 && address.name=”家” 的数据并不存在,但可以检索到,因为 es 中存储是平铺的
{
"user_id": 1,
"user_name": "张三",
"address.id": [1,2],
"address.name": ["公司", "家"],
"address.address": ["北京市海淀区", "北京市昌平区"]
}
使用 netsted 类型保存一对多关联关系
创建索引
PUT /test_user_nested
{
"mappings": {
"properties": {
"user_id": {
"type": "long"
},
"user_name": {
"type": "text"
},
"address": {
"type": "nested",
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text"
},
"address": {
"type": "text"
}
}
}
}
}
}
添加文档
POST /test_user_nested/_doc
{
"user_id": 1,
"user_name": "张三",
"address": [
{
"id": 1,
"name": "公司",
"address": "北京市海淀区"
},
{
"id": 2,
"name": "家",
"address": "北京市昌平区"
}
]
}
嵌套查询
查询 address.id=1 && address.name=”家” 是查不到数据的,address.id=1 && address.name=”公司” 才能查到数据
POST /test_user_nested/_search
{
"query": {
"nested": {
"path": "address",
"query": {
"bool": {
"must": [
{
"match": {
"address.id": 1
}
},
{
"match": {
"address.name": "公司"
}
}
]
}
}
}
}
}
嵌套查询可以和普通查询一起组成联合查询:
{
"query": {
"bool": {
"must": [
{
"match": {
"user_id": 1
}
},
{
"nested": {
"path": "address",
"query": {
"bool": {
"must": [
{
"match": {
"address.id": 1
}
},
{
"match": {
"address.name": "公司"
}
}
]
}
}
}
}
]
}
}
}
嵌套子文档是独立存储的
只插入了一个文档,但 _cat 可以看到文档个数是3(用 /_count 查文档数还是1),这是因为 nested 子文档在 ES 内部其实也是独立存储的隐藏 lucene 文档,查询时 es 内部做了 join 处理,对外表现为一个文档
/_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
green open test_user_nested 2tNlE4t8SqiLKDtW8B6Isg 1 1 3 0 9.7kb 4.8kb
如果创建一个有 100 个地址的 user 文档,则内部一共是 101 个 Lucene 文档:1个父user文档,100个子address文档
嵌套文档性能问题与个数限制
index.mapping.nested_fields.limit 每个索引的最大嵌套字段数,默认50
index.mapping.nested_objects.limit 每个文档中的嵌套子文档最大个数,默认10000
join 父子文档
https://www.elastic.co/guide/en/elasticsearch/reference/current/parent-join.html
字段映射参数
dynamic 是否可动态添加字段
Elasticsearch Guide [7.17] » Mapping » Mapping parameters » dynamic
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic.html
dynamic
可以是整个 mapping 的属性,也可以是某个字段的属性,子字段会继承父字段或整个 mapping 的 dynamic 属性
dynamic
参数控制是否可自动添加字段,可取值如下,默认值是 true
true
自动添加新字段到 mappingruntime
新字段作为 运行时字段 被添加到 mapping,这些字段不会被索引,但查询时会在_source
字段中返回。false
新字段会被忽略。这些字段不被索引,不可搜索,不会被添加到 mapping,但数据依然会被存储在原始_source
中,_source
字段保存了文档的原始 JSON 数据,依然会在查询结果的_source
字段中返回。strict
如果检测到新字段,会抛出异常,文档无法插入,新字段必须显式添加到 mapping 中。
动态 dynamic 属性设置
更新整个索引的 dynamic 属性:
PUT dynamic_mapping_test/_mapping
{
"dynamic": false
}
enabled
index 是否被索引
index
控制当前字段是否被索引,默认为 true,如果设置成 false,该字段不可被搜索
index_options
index_options
控制倒排索引记录的内容
- docs 记录 doc id
- freqs 记录 doc id 和 term frequencies
- positions 记录 doc id/term frequencies/term position
- offsets 记录 doc id/term frequencies/term position/character offects
null_value 替换null值
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/null-value.html
1、空值不被索引,不可搜索
当字段值为 null、空数组 [] 或 null 值数组(如 [null])时,Elasticsearch 会将该字段视为没有值,不会在倒排索引中存储任何标记。这意味着:
- 无法通过 term 查询直接搜索 null(会抛出 illegal_argument_exception 错误);
- exist 查询无法匹配这些文档
- 空值字段的文档在聚合或统计时会被忽略。
2、通过 null_value
实现对 null 值的索引和搜索
在字段映射中定义 null_value
,Elasticsearch 会在存储时将 显式null值 替换为指定占位符,使其可被索引和搜索
注意:null_value
的类型需要和字段本身的类型相同,例如 long 型字段不可以设置字符串类型的 null_value
例如:
PUT /my_index
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword",
"null_value": "NULL" // 替换 null 为 "NULL"
}
}
}
}
PUT my_index/_doc/1
{
"status_code": null // 值被替换为 "NULL"
}
PUT my_index/_doc/2
{
"status_code": [] // 空数组不会被替换
}
设置 "null_value": "NULL"
后的行为特性:
- 插入文档时,若字段值为 null(即 显式null值),实际索引值为
"NULL"
- 插入空数组时,由于数组内不包含任何显式的 null,所以并不会被替换为
"NULL"
- 查询时使用
term: "NULL"
可匹配显式 null 值的文档 - 不影响原始数据,
_source
中仍显示原始 null 值
copy_to
Elasticsearch Guide [7.17] » Mapping » Mapping parameters » copy_to
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/copy-to.html
copy_to
可以将字段的值拷贝到另一个字段中,常用于将多个字段的值合并到同一个字段方便检索
将字段内容拷贝到目标字段,查询时可以用目标字段作为查询条件,但是不会出现 _source 中
例如,将 first_name 和 last_name 字段的内容拷贝到 full_name 中,然后可直接在 full_name 字段上搜索全名。
PUT my-index-000001
{
"mappings": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
fields 同一字段存储为多种类型
fields
多字段(multi-fields)
在字段下新增一个字段,可以自定义类型,使用不同的 analyzer
fields 的目的是为了使单个字段可以被多重方式索引和检索,例如可以用来实现以拼音方式搜索中文字段。
在动态 mapping 中,非日期且非数字格式的 string 字段会被自动映射为 text 类型,但是带有一个 字段名为 keyword,类型也是 keyword 的 fields 字段。
例如,下面 name 本身是 text 类型,但 name.keyword 是 keyword 类型
"name": {
"type":"text",
"fields":{
"keyword":{
"ignore_above":256,
"type":"keyword"
}
}
}
ignore_above 超长后忽略
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/ignore-above.html
超过 ignore_above 长度的字符串不会被索引和存储,不参与匹配和聚合查询,但依然会完整在 _source 中返回。
对于字符串数组类型,ignore_above 会作用在每个数组元素上。
默认值 2147483647
ignore_above 不会自动截断
注意:超过 ignore_above 长度后整个字段都不会被索引,没有自动截断功能,不是仅索引前 ignore_above 个字符,忽略后续字符
示例如下:
PUT /test_ignore_above
{
"mappings": {
"properties": {
"para": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 10
}
}
},
"title": {
"type": "keyword",
"ignore_above": 10
}
}
}
}
PUT /test_ignore_above/_doc/1
{
"title": "lesstitle",
"para": "lesspara"
}
PUT /test_ignore_above/_doc/2
{
"title": "longlongtitle",
"para": "longlongpara"
}
写入2个文档后,match_all 都能查到,但是:
{ “query”: { “exists”: { “field”: “title” } } } 结果只有文档1
{ “query”: { “exists”: { “field”: “para.keyword” } } } 结果也只有文档1
{ “query”: { “wildcard”: { “title”: “*title” } } } 模糊匹配结果只有文档1
{ “query”: { “term”: { “title”: “lesstitle” } } } 能查到文档1
{ “query”: { “term”: { “title”: “longlongtitle” } } } 无法查到文档2,返回 hits.total 个数为0
如果文档1和2都存在时,在 title 上脚本查询:
{
"query": {
"script": {
"script": {
"source": "doc['title'].value.contains(params.query)",
"lang": "painless",
"params": {
"query": "tit"
}
}
}
}
}
报错:
{ "type": "illegal_state_exception", "reason": "A document doesn't have a value for a field! Use doc[<field>].size()==0 to check if a document is missing a field!" }
因为文档2的title不可被检索匹配,删除文档2后不报错。
也可以改下脚本,兼容 title 不存在的文档:
{
"query": {
"script": {
"script": {
"source": "if (doc.containsKey('title') && doc['title'].size() > 0) {return doc['title'].value.contains(params.query);} else {return false;}",
"lang": "painless",
"params": {
"query": "tit"
}
}
}
}
}
但这样也无法查询到文档2
analyzer 索引分析器
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analyzer.html
analyzer 参数指定文档索引(插入)阶段的分析器。
用于索引阶段对字段内容进行分词处理,决定了文档如何被拆分为词项(terms)并存储到倒排索引中。例如,text 类型字段默认使用 standard 分析器,会将文本按空格、标点分割并转为小写
内置分析器
Built-in analyzer reference
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-analyzers.html
内置分析器:
- standard 默认分词器,按词切分,小写处理
- simple 按照非字母切分(符号被过滤),小写处理
- stop 小写处理,停用词过滤(the、a、is)
- whitespace 按照空格切分,不转小写
- keyword 不分词,直接将输入当作输出
- patter 正则表达式,默认 \W+(非字符分隔)
- language 提供了 30 多种常见语言的分词器(english、german)
- 中文分词 icu_analyzer、ik、thulac
search_analyzer 检索分析器
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-analyzer.html
search_analyzer 参数指定搜索阶段的分析器。
用于搜索阶段对查询词进行分词处理,确保查询词的分词逻辑与索引时的分词逻辑匹配。若未显式指定,默认使用同字段 analyzer 的配置。
analyzer 与 search_analyzer 用法最佳实践
典型用法:
索引时:使用细粒度分词(如 ik_max_word)尽可能拆解更多潜在关键词。
搜索时:使用粗粒度分词(如 ik_smart)提高搜索精准度。
PUT /my_index
{
"mappings": {
"properties": {
"content": {
"type": "text",
"analyzer": "ik_max_word", // 索引时分词
"search_analyzer": "ik_smart" // 搜索时分词
}
}
}
}
similarity 相似度算法
Elasticsearch 允许每个字段单独设置相似度算法。
字段的 similarity 属性用于定义该字段的相似度评分算法。
适用字段类型:主要针对 text 类型的全文检索字段
Elasticsearch 内置三种开箱即用的相似度算法,无需额外配置即可使用:
BM25
默认相关性算法,基于 Okapi BM25 模型,优化了词频饱和度和文档长度归一化,适合自然语言文本检索。classic
经典的 TF/IDF 算法,计算词频(TF)和逆文档频率(IDF)的乘积,支持字段长度归一化。boolean
无需全文相关性评分的字段,可以设置为 boolean,仅表示 term 是否匹配。
自定义相似度算法参数
Elasticsearch 支持通过 similarity 模块调整内置相似度算法的参数(如 BM25 的 k1 和 b):
1、在索引配置中定义一个定制化的相似度算法 custom_bm25,修改 BM25 算法的 k1 和 b 参数。
2、之后在 text 类型的字段上可配置 similarity 属性为 custom_bm25
PUT /my_index
{
"settings": {
"index": {
"similarity": {
"custom_bm25": {
"type": "BM25",
"k1": 1.2, // 控制词频饱和度
"b": 0.75 // 控制文档长度归一化强度
}
}
}
},
"mappings": {
"properties": {
"content": {
"type": "text",
"similarity": "custom_bm25" // 引用自定义算法
}
}
}
}
format 日期字段格式
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-date-format.html
如果不指定 Date 的 format 格式,使用默认 strict_date_optional_time
配置项配置的格式或毫秒时间戳,即:strict_date_optional_time||epoch_millis
指定日期字段格式:
PUT my-index-000001
{
"mappings": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
properties 嵌套字段的属性
object 或 nested 字段的子属性
doc_values 用于加速聚合/排序的正排索引
Elasticsearch Guide [7.17] » Mapping » Mapping parameters » doc_values
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/doc-values.html
倒排索引可以提供全文检索能力,但是无法提供对排序和数据聚合的支持。
聚合(aggregations)、排序(Sorting)、脚本(scripts access to field)等查询需要通过访问文档的字段值来进行,这是和使用倒排索引查询不同的一种数据访问模式。这类查询不使用倒排索引,无法直接根据 term 找到对应的文档,而是需要通过访问文档来找到他包含的 term 值。
Doc Values 是磁盘上的一种数据结构,在索引文档时创建,与 _source
独立,占用额外存储空间,同时开启 doc_values
和 _source
则会将该字段原始内容保存两份。
Doc Values 是一种类似 doc -> field value
的正排索引映射关系,可快速找到文档包含的 term,可使得排序和聚合查询更高效
默认情况下,ES几乎会为所有类型的字段存储doc_value,但是 text 或 text_annotated 等可分词字段不支持 doc values 。如果不需要对某个字段进行排序或者聚合,则可以关闭该字段的doc_value存储
除了 text
和 annotated_text
,全部字段类型都支持 Doc values。
倒排索引示例,方便查找某个 term 在哪些文档中,每个字段都有倒排索引,这里只示意 Field1 的:
| Field1 | Doc_1 | Doc_2 | Doc_3 |
| —— | —– | —– | —– |
| brown | X | X | |
| color | | X | X |
| dog | | | X |
Doc Values 正排索引示例,方便查找某个 Doc 中包含哪些 term:
| Doc | Field1 | Field2 |
| —– | ———— | ———– |
| Doc_1 | brown, color | meat |
| Doc_2 | brown | fruit, meat |
| Doc_3 | dog, color | meat |
之后的查询如果需要按 Field2 聚合,通过查 Doc Values 可以知道 Doc_1 和 Doc_3 的 Field2 字段值相同,可快速聚合。
支持 Doc values 的字段默认开启 doc_values 功能,如果确认某个字段不需要用来做排序、聚合、脚本查询,可以通过 "doc_values": false
关闭 doc_values 支持,以便节省磁盘空间。
注意:"doc_values": false
的字段不支持排序、聚合、脚本查询。
例如:
PUT my-index-000001
{
"mappings": {
"properties": {
"status_code": {
"type": "keyword"
},
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
Loading the fielddata on the _id field is deprecated
在 Java RestClient 中,如果我们使用 _id
字段进行排序或聚合操作,RestClient 会给出如下提示:
org.elasticsearch.client.RestClient : request [POST http://ip:9200/myindex/_search?typed_keys=true&max_concurrent_shard_requests=5&ignore_unavailable=false&expand_wildcards=open&allow_no_indices=true&ignore_throttled=true&search_type=query_then_fetch&batched_reduce_size=512&ccs_minimize_roundtrips=true] returned 1 warnings: [299 Elasticsearch-7.6.1-aa751e09be0a5072e8570670309b1f12348f023b "Loading the fielddata on the _id field is deprecated and will be removed in future versions. If you require sorting or aggregating on this field you should also include the id in the body of your documents, and map this field as a keyword field that has [doc_values] enabled"]
这个警告是由于你正在尝试在 _id 字段上加载 fielddata。在 Elasticsearch 中,_id 字段默认不存储 fielddata,因为这可能会占用大量内存。
Fielddata 是 Elasticsearch 中用于在内存中存储字段值以便进行聚合、排序和脚本操作的数据结构。
如果你需要对 _id 字段进行排序或聚合,建议的做法是在文档的主体中也包含这个 id,并将这个字段映射为启用了 doc_values 的 keyword 字段。这样,你可以对这个 keyword 字段进行排序或聚合,而不需要在 _id 字段上加载 fielddata。
例如 增加一个开启了 doc_values 的 my_id keyword 字段,插入数据时在业务代码中给赋值一个唯一id,后续用于排序。
{
"mappings": {
"properties": {
"my_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
illegal_argument_exception: Text fields are not optimised for operations
Java 中查询 es 报错:
Caused by: org.elasticsearch.ElasticsearchException: Elasticsearch exception [type=illegal_argument_exception, reason=Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [id] in order to load field data by uninverting the inverted index. Note that this can use significant memory.]
at org.elasticsearch.ElasticsearchException.innerFromXContent(ElasticsearchException.java:485)
at org.elasticsearch.ElasticsearchException.fromXContent(ElasticsearchException.java:396)
at org.elasticsearch.ElasticsearchException.innerFromXContent(ElasticsearchException.java:426)
原因:
java 中使用 id asc 排序分页查询 es 文档:
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.size(1000);
searchSourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));
SearchRequest searchRequest = new SearchRequest(index);
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
但查询的索引上 id 字段是个 text 类型。
因为没有预先在 mappings 中定义 id 为 keyword 类型,直接写入带 string id 的数据后导致 es 自动推断 id 类型为 text,text 类型的字段是不支持排序的,所以报错
{
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
解决:
创建索引时在 mappings 中定义 id 为 keyword 类型。
eager_global_ordinals 全局序号
Elasticsearch Guide [7.17] » Mapping » Mapping parameters » eager_global_ordinals
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/eager-global-ordinals.html
ES 使用 Doc Values 正排索引结构来支持聚合、排序、脚本查询等需要访问文档字段值的操作。
ES 使用全局序号代替真正的 term 值来压缩优化 Doc Values 的存储,可提高聚合查询的性能、节省存储 Doc Values 使用的磁盘空间、节省 fielddata 缓存使用的内存空间。
Global Ordinals 是 Shard 级别的,因此当一个 Shard 的 Segment 发生变动时就需要重新构建 Global Ordinals,比如有新数据写入导致产生新的Segment、Segment Merge等情况。当然,如果Segment没有变动,那么构建一次后就可以一直利用缓存了(适用于历史数据)。
默认情况下,Global Ordinals 是在收到聚合查询请求并且该查询会命中相关字段时构建,而构建动作是在查询最开始做的,即在Filter之前。在遇到某个字段的值种类很多时会变的非常慢,严重影响聚合查询速度。在追求查询的场景下很影响查询性能。可以使用 eager_global_ordinals,即在每次refresh以后即可更新字典,字典常驻内存,减少了查询的时候构建字典的耗时。
7.x 开始移除 Type
Removal of mapping types
https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/removal-of-types.html
index 中的 document 可以分组,这种分组就叫做 Type,比如 twitter 索引中可以有 user 类型的数据和 tweet 类型的数据。
ES 6.x 之前的版本,可在一个索引库下创建多个 type
ES 6.x 版只允许每个 Index 包含一个 Type,并预告 7.x 版将会彻底移除 Type
ES 7.x 开始,彻底废弃一个 index 下多个 type 支持,包括 api 层面
为什么 ES 要移除 Type?
一开始 es 发布时,声明 index 对应关系数据库中的 database,type 对应 table,document 对应 row 数据行。
但其实并不是这样,关系数据库中不同 table 间的同名字段是互相独立互不影响的,但 es 中同一个 index 下不同 type 间的同名字段是互相影响的,其实在 Lucene 内部是存储在同一字段中的。
还以 twitter 索引中有 user 和 tweet 两种 type 的数据为例,比如两类数据中都有 user_name 字段,则 Lucene 内部都使用 user_name 字段索引,所以两类数据中 user_name 字段的类型必须一致。
某些情况下我们可能想要不同 type 中的同名字段是不同类型,比如一个 type 中 deleted 是 date 类型,另一个 type 中是 boolean 类型,这种是实现不了的。
此外,在同一个 index 中存储不同类型的文档会导致数据稀疏,和 Lucene 的文档压缩能力冲突。
下一篇 Elasticsearch-优化
页面信息
location:
protocol
: host
: hostname
: origin
: pathname
: href
: document:
referrer
: navigator:
platform
: userAgent
: