前面讲到,无论是关系型数据库还是非关系型数据库,乃至elasticsearch这种事实上承担着一定储存作用的搜索引擎,数据类型都是非常重要而基础的概念。但elasticsearch与其它承担着数据存储的技术有着比较大的区别之一就是映射,和倒排索引。
映射是定义如何存储和编制文档及其包含的字段的过程。例如,使用映射来定义:
- 哪些字符串字段应被视为全文字段。
- 哪些字段包含数字,日期或地理位置。
- 文档中所有字段的值是否应该编入catch-all _all字段。
- 日期值的格式。
- 自定义规则来控制动态添加字段的映射。
很显然,对于一个搜索引擎来说,光有数据结构是远远不够的。映射是elasticsearch区别与数据库的重要特征之一。
映射可以看作是建表的过程。但与数据库不同的是,映射可以为一个字段建立不同的映射以满足不同的场景,可以对数据进行清洗,容错,建立倒排索引。
倒排索引
有两篇文章:
1 我是程序员
2 我热爱写程序
先分词
1 【我】【是】【程序】【程序员】
2【我】【热爱】【写】【程序】
前面文章对关键字,经倒排后变成关键字对文章
关键字 | 文章号 |
我 | 1,2 |
是 | 1 |
程序 | 1,2 |
程序员 | 1 |
热爱 | 2 |
写 | 2 |
为了快速定位和节省存储大小,还需要加上关键字出现频率和位置。
关键字 | 文章号(频率) | 位置 |
我 | 1(1) | 1 |
2(1) | 1 | |
是 | 1(1) | 2 |
程序 | 1(1) | 3 |
2(1) | 4 | |
程序员 | 1(1) | 4 |
热爱 | 1(1) | 1 |
写 | 1(1) | 3 |
映射的类型
元数据 Meta-fields
用于定制文档的相关元数据。元字段的示例包括文档的_index,_type,_id和_source字段。
_index
|
索引标识 |
_uid
|
由_type和_id组成的复合字段。 |
_type
|
文档的映射类型。一个索引当中可以具有多个type。查询的时候指定索引,再指定type. |
_id
|
文档ID。 |
|
文档正文。是用户保存的真正的数据。 |
|
文档正文的大小。 |
|
_all字段是把其它字段拼接在一起的超级字段,所有的字段用空格分开,_all字段会被解析和索引,但是不存储。当你只想返回包含某个关键字的文档但是不明确地搜某个字段的时候就需要使用_all字段。 |
|
字段用来存储文档中的所有非空字段的名字,这个字段常用于exists查询。 |
|
用于创建两个映射类型之间的父子关系。 |
|
将文档路由到特定分片的自定义路由值。 |
_all
它将所有其他字段的值连接成一个大字符串,使用空格作为分隔符,然后对其进行分析和索引,但不存储。这意味着它可以被搜索,但不能被检索。
这段话的意思是,它是一个逻辑上的概念。它没有这个具体的健以及值。所以没有具体的存储。它没有一人常驻的物理存储。相当于对全部字段进行搜索。
PUT my_index/user/1 { "first_name": "John", "last_name": "Smith", "date_of_birth": "1970-10-24" } GET my_index/_search { "query": { "match": { "_all": "john smith 1970" } } } // ["john"
,"smith"
,"1970"
,"10"
,"24"
]
_field_names
字段索引包含除null之外的任何值的文档中的每个字段的名称。该字段由存在的查询使用来查找特定字段具有或不具有任何非空值的文档。
PUT my_index/my_type/1 { "title": "This is a document" } PUT my_index/my_type/2?refresh=true { "title": "This is another document", "body": "This document has a body" } GET my_index/_search { "query": { "terms": { "_field_names": [ "title" ] } } } //返回两条数据,因为两条数据都包含titile字段
_routing
使用以下公式决定该文档路由到哪个shard
shard_num = hash(_routing) % num_primary_shards
用户定义字段
每个映射类型包含与该类型相关的字段或属性的列表。用户类型可能包含title,name和age字段,而blogpost类型可能包含title,body,user_id和已创建的字段。同一索引中不同映射类型中具有相同名称的字段必须具有相同的映射。
映射参数 Mapping parameters
analyzer
normalizer
boost
coerce
copy_to
doc_values
dynamic
enabled
fielddata
format
ignore_above
ignore_malformed
include_in_all
index_options
index
fields
norms
null_value
position_increment_gap
properties
search_analyzer
similarity
store
term_vector
analyzer
分词器。es有内置的分词器,也可以使用第三方的分词工具。如IK。
{ "mappings": { "my_type": { "properties": { "content": { "type": "text", "analyzer": "ik_max_word",//写入时使用的分词器 "search_analyzer": "ik_max_word"//搜索时使用的分词器 } } } } }
normalizer
keyword类型的标准化解析器。我们知道keyword不能分词,可它也有标准化的需求,比如忽略大小写,比如统一去掉特殊字符。那么normalizer就是为此而准备的。
PUT index { "settings": { "analysis": { "normalizer": { "my_normalizer": { "type": "custom", "char_filter": [], "filter": ["lowercase", "asciifolding"] } } } }, "mappings": { "type": { "properties": { "foo": { "type": "keyword", "normalizer": "my_normalizer" } } } } } PUT index/type/1 { "foo": "BÀR" } PUT index/type/2 { "foo": "bar" } PUT index/type/3 { "foo": "baz" } POST index/_refresh GET index/_search { "query": { "match": { "foo": "BAR" } } }//返回前面两条记录
coerce
数据并不总是很干净。根据它的生成方式,一个数字可能会在JSON体中呈现为一个真正的JSON数字,例如5,但它也可以呈现为字符串,例如。 “5”。或者,可以将应该是整数的数字呈现为浮点,例如, 5.0或甚至“5.0”。
强制尝试清除脏值以适应字段的数据类型。例如:
字符串将被强制为数字。
对于整数值,浮点将被截断。
{ "mappings": { "my_type": { "properties": { "number_one": { "type": "integer" }, "number_two": { "type": "integer", "coerce": false } } } } } PUT my_index/my_type/1 { "number_one": "10" }//成功 PUT my_index/my_type/2 { "number_two": "10" }//失败
copy_to
允许将一个或者多个字段的值复制到某一个字段中。
{ "mappings": { "my_type": { "properties": { "first_name": { "type": "text", "copy_to": "full_name" }, "last_name": { "type": "text", "copy_to": "full_name" }, "full_name": { "type": "text" } } } } } PUT my_index/my_type/1 { "first_name": "John", "last_name": "Smith" } //full_name = ["John","Smith"]
doc_values
为了加快排序、聚合操作,在建立倒排索引的时候,额外增加一个列式存储映射,是一个空间换时间的做法。默认是开启的,对于确定不需要聚合或者排序的字段可以关闭。text类型没有doc_values。
{ "mappings": { "my_type": { "properties": { "status_code": { "type": "keyword" }, "session_id": { "type": "keyword", "doc_values": false } } } } }
dynamic
The dynamic
setting controls whether new fields can be added dynamically or not. It accepts three settings:
true
|
新检测的字段将添加到映射中。 (默认) |
false
|
新检测的字段将被忽略。这些字段将不会被索引,因此不会被搜索,但仍将显示在返回的匹配的_source字段中。这些字段将不会添加到映射中,必须显式添加新字段。 |
strict
|
如果检测到新字段,将抛出异常并拒绝文档。新字段必须明确添加到映射中。 |
enabled
enabled默认为true,将搜索所有字段。如果设置为false,该字段将不会被搜索。但仍会随着_source返回。
fielddata
对非text类型的字段进行排序可以使用doc_value来进行加速。但是对于,text类型的字段,却不能进行分组排序。更何况加速。下面这个异常展示了,对text类型的字段进行分组排序的错误。
但是可以通过设置fielddata值来达到这一目的。
它将字段加载到内存中,因此第一次肯定会很慢。而且将占用内存。如果内存达到某一个值,将请求执行失败。这个值可以配置。
format
对字段进行格式化。
{ "mappings": { "my_type": { "properties": { "date": { "type": "date", "format": "yyyy-MM-dd" } } } } }
ignore_above
大小超过ignore_above设置的字符串不会被索引或存储。
ignore_malformed
尝试将错误的数据类型索引到字段中,默认情况下会引发异常,并拒绝整个文档。 ignore_malformed参数(如果设置为true)允许忽略该异常。格式不正确的字段未编入索引,但文档中的其他字段正常处理。
前面讲到,可以通过coerce来进行数据清洗。例如,interger类型的数据写入了一个“10”,这是可以处理的。但如果写入的是一个"顶替",那就没法处理了。如果,将该字段ignore_malformed参数设置为true,那么将忽略该字段。
include_in_all
include_in_all参数提供对_all字段中包含哪些字段的字段控制。它默认为true,除非index设置为false。
index_options
控制索引时存储哪些信息到倒排索引中,接受以下配置:
参数 | 作用 |
---|---|
docs | 只存储文档编号 |
freqs | 存储文档编号和词项频率 |
positions | 文档编号、词项频率、词项的位置被存储,偏移位置可用于临近搜索和短语查询 |
offsets | 文档编号、词项频率、词项的位置、词项开始和结束的字符位置都被存储,offsets设为true会使用Postings highlighter |
fields
可以为一个字段映射多个数据类型。比如,一个字符串,可以映射为text,满足全文搜索。同时可以映射为keyword,满足分组和排序。
{ "mappings": { "my_type": { "properties": { "city": { "type": "text", "fields": { "raw": { "type": "keyword" } } } } } } }
也可以使用多个分词器来对同一个字段进行分词。
{ "mappings": { "my_type": { "properties": { "text": { "type": "text",//标准分词器 "fields": { "english": { "type": "text", "analyzer": "english" //英文分词器 } } } } } } }
norms
norms参数用于标准化文档,以便查询时计算文档的相关性。norms虽然对评分有用,但是会消耗较多的磁盘空间,如果不需要对某个字段进行评分,最好不要开启norms。
null_value
空值不能被索引或搜索。当一个字段设置为null(或一个空数组或一个空值数组)时,它被视为该字段没有值。
null_value参数允许您使用指定的值替换显式空值,以便对其进行索引和搜索。
position_increment_gap
不常用。忽略。
properties
当数据类型为object或者nested,有子节点的时候,可以使用properties来达到。
{ "mappings": { "my_type": { "properties": { "manager": { "properties": { "age": { "type": "integer" }, "name": { "type": "text" } } }, "employees": { "type": "nested", "properties": { "age": { "type": "integer" }, "name": { "type": "text" } } } } } } }
写入数据
{ "region": "US", "manager": { "name": "Alice White", "age": 30 }, "employees": [ { "name": "John Smith", "age": 34 }, { "name": "Peter Brown", "age": 26 } ] }
search_analyzer
大多数情况下索引和搜索的时候应该指定相同的分析器,确保query解析以后和索引中的词项一致。但是有时候也需要指定不同的分析器,例如使用edge_ngram过滤器实现自动补全。
{ "settings": { "analysis": { "filter": { "autocomplete_filter": { "type": "edge_ngram", "min_gram": 1, "max_gram": 20 } }, "analyzer": { "autocomplete": { //映射的时候使用autocomplete解析器 "type": "custom", "tokenizer": "standard", "filter": [ "lowercase", "autocomplete_filter" ] } } } }, "mappings": { "my_type": { "properties": { "text": { "type": "text", "analyzer": "autocomplete", //创建索引的时候,使用autocomplete解析器 "search_analyzer": "standard" //搜索的时候,使用standard解析器 } } } } } PUT my_index/my_type/1 { "text": "Quick Brown Fox" //创建索引的时候,使用autocomplete解析器,[ q, qu, qui, quic, quick, b, br, bro, brow, brown, f, fo, fox ] } GET my_index/_search { "query": { "match": { "text": { "query": "Quick Br", //搜索的时候,使用标准解析器,[ quick, br ] "operator": "and" } } } }
store
我们知道,_source字段存储了原始数据(默认)。当然可以通过设置其属性值来选择不存储。此外,还可以通过store选择是否额外存储某个字段。store属性默认为no,表示不存储。当设置为yes时,会在_source之外独立存储。此时,搜索时,会绕过_source,单独进行一次IO得到该字段的值。
store会严重影响搜索效率,尽管如此,在以下两种情况下,还是可以选择使用:
1:字段很长,每次检索_source代价很大。
2:需要单独对某些字段进行索引重建。
index
表示字段是否索引。接受true或false。老版的es接受三个值。分别是
analyzed 分词,索引。
not_analyzed 不分词,索引。
no 不索引。
动态映射
前面讲到的都是先映射后建立索引。但是,也可以,不先建立映射,直接写入数据;或者,加入没有映射的字段,es会根据数据的类型自动判断数据类型,创建索引。
default
可以使用default来作为基本映射。其它映射将继承默认的配置。
{ "mappings": { "_default_": { "_all": { "enabled": false } }, "user": {}, "blogpost": { "_all": { "enabled": true } } } }
_default_将_all字段设置为disabled.user字段继承,也将_all字段disabled。而blogpost则重写了_all属性。
dynamic
动态添加字段。它有三个选项。前面已经讲到。
这里补充一点,动态添加字段,可能会出现一个问题。比如:
先出现一个值:2017-08-22,动态匹配为date类型。而此字段此后进来的值为非日期类型,匹配失败。为了避免这种情况出现,可以选择关闭日期匹配
{ "mappings": { "my_type": { "date_detection": false } } }
dynamic templates
相当于使用模板模式匹配动态字段。
通过dynamic_templates
,你可以拥有对新字段的动态映射规则拥有完全的控制。你设置可以根据字段名称或者类型来使用一个不同的映射规则。
每个模板都有一个名字,可以用来描述这个模板做了什么。同时它有一个mapping
用来指定具体的映射信息,和至少一个参数(比如match
)用来规定对于什么字段需要使用该模板。
模板的匹配是有顺序的 - 第一个匹配的模板会被使用。比如我们可以为string
字段指定两个模板:
es
:以_es
结尾的字段应该使用spanish
解析器en
:其它所有字段使用english
解析器
我们需要将es
模板放在第一个,因为它相比能够匹配所有字符串字段的en
模板更加具体:
{ "mappings": { "my_type": { "dynamic_templates": [ { "es": { "match": "*_es", "match_mapping_type": "string", "mapping": { "type": "string", "analyzer": "spanish" } }}, { "en": { "match": "*", "match_mapping_type": "string", "mapping": { "type": "string", "analyzer": "english" } }} ] }}}
match_mapping_type
允许你只对特定类型的字段使用模板,正如标准动态映射规则那样,比如string
,long
等。
match
参数只会匹配字段名,path_match
参数用于匹配对象中字段的完整路径,比如address.*.name
可以匹配如下字段:
{ "address": "city": "name": "New York" } } }
unmatch
和path_unmatch
模式能够用来排除某些字段,没有被排除的字段则会被匹配。
总结
一。映射可以简单的理解为数据库建表的过程。但映射更为强大得多。
二。倒排索引。传统数据库索引的方式是,【表->字段】。而倒排索引的方式是先将字段进行分词,然后将单词跟文档进行关联,变为【文档->单词】,并将记录其它更为强大的信息(文档编号、词项频率、词项的位置、词项开始和结束的字符位置可以被存储)。倒排索引是elasticsearch区别于数据库的关键。
三。映射的字段包括元数据,就是默认的字段。以及用户自定义的字段。
1:元数据,一般以"_"开头。如下图。比较重要的字段有_index,_type,_id,_source,_routing。其它字段较为不重要。_all 6.0以后将移除该字段。可以忽略。
_index 索引名称。可以看作是数据库名称。
_type 可以看作是表名。
_id 可以看作是唯一标识。
_routing 是决定将该值保存在集群中哪个分片的值。
_source 存储原始数据的地方。es将原始数据存储在_source。另外还会保存倒排索引。另外,还可以将数据单独保存在_store。
点击其中一行数据:
2:用户自定义字段。
analyzer 首先一个字段进来,除了keyword类型的以外,都可以选择是否分词。
normalizer keyword类型的标准化解析器。我们知道keyword不能分词,可它也有标准化的需求,比如忽略大小写,比如统一去掉特殊字符。那么normalizer就是为此而准备的。
coerce 强制尝试清除脏值以适应字段的数据类型。与其说是数据清洗,不如说是数据适配。比如,该字段数据为整型,进来一个字段为 “10”,那么它会自动适配为 10。
doc_values 倒排索引加快了全文搜索的速度,但对于排序和聚合操作,很多时候还是显得有些力不从心。此字段,在倒排索引的基础上,再增加了一个列式存储。加快排序和聚合。默认开启。text因为一般存储字段比较长,没有此属性值。
dynamic 动态添加字段。在映射的时候没有考虑到的字段,后期数据出现了,es提供了三种选择方案。true,默认,添加,适配类型。false,忽略。存储原始值,但不会被添加到倒排索引。因此,不能搜索,但可以返回。strict,抛出异常。
需要注意的是,如果选择了true,那么es会根据首次进来的数据选择一个类型,如果后续的数据不符合这个类型 ,可能会抛出异常。如:先出现一个值:2017-08-22,动态匹配为date类型。而此字段此后进来的值为非日期类型,匹配失败。为了避免这种情况出现, 可以选择关闭日期匹配
enabled 这个字段可以设置在 _source中,也可以设置在自定义字段中,表示是否存储原始数据。
fielddata 对于text类型的字段,不能分组,但可以通过此字段来达到此目的。
format 对字段进行格式化。
ignore_above 对字段长度进行限制。
ignore_malformed 对字段类型进行限制。设置为true,在字段类型不匹配的情况下,忽略该异常。
index_options 对字段建立倒排索引的信息进行限制。信息分别为文档编号、词项频率、词项的位置、词项开始和结束的字符位置。
fields 一个字段映射为多个类型。
store store属性默认为no,表示不存储。当设置为yes时,会在_source之外独立存储。此时,搜索时,会绕过_source,单独进行一次IO得到该字段的值。
index 表示字段是否索引。接受true或false。
参考:
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html
http://blog.csdn.net/liyantianmin/article/details/52531500