Go 两数相除
11个月前
自动或手动为index中的_doc建立的一种数据结构和相关配置,简称为mapping映射。Mapping决定了index中的field的特征。插入几条数据,让es自动为我们建立一个索引
概念:自动或手动为index中的_doc建立的一种数据结构和相关配置,简称为mapping映射。Mapping决定了index中的field的特征。
插入几条数据,让es自动为我们建立一个索引
PUT /website/_doc/1 { "post_date": "2019-01-01", "title": "my first article", "content": "this is my first article in this website", "author_id": 11400 } PUT /website/_doc/2 { "post_date": "2019-01-02", "title": "my second article", "content": "this is my second article in this website", "author_id": 11400 } PUT /website/_doc/3 { "post_date": "2019-01-03", "title": "my third article", "content": "this is my third article in this website", "author_id": 11400 }
动态映射:dynamic mapping,自动为我们建立index,以及对应的mapping,mapping中包含了每个field对应的数据类型,以及如何分词等设置。
重点:我们当然,后面会讲解,也可以手动在创建数据之前,先创建index,以及对应的mapping
GET /website/_mapping/ { "website" : { "mappings" : { "properties" : { "author_id" : { "type" : "long" }, "content" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "post_date" : { "type" : "date" }, "title" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } }
尝试各种搜索
GET /website/_search?q=2019 0条结果 GET /website/_search?q=2019-01-01 1条结果 GET /website/_search?q=post_date:2019-01-01 1条结果 GET /website/_search?q=post_date:2019 0 条结果
搜索结果为什么不一致,因为es自动建立mapping的时候,设置了不同的field不同的data type。不同的data type的分词、搜索等行为是不一样的。所以出现了_all field和post_date field的搜索表现完全不一样。
2019-01-01,exact value,搜索的时候,必须输入2019-01-01,才能搜索出来
如果你输入一个01,是搜索不出来的
select * from book where name= 'java'
搜“笔记电脑”,笔记本电脑词条会不会出现。
select * from book where name like '%java%'
(1)缩写 vs. 全称:cn vs. china
(2)格式转化:like liked likes
(3)大小写:Tom vs tom
(4)同义词:like vs love
2019-01-01,2019 01 01,搜索2019,或者01,都可以搜索出来
china,搜索cn,也可以将china搜索出来
likes,搜索like,也可以将likes搜索出来
Tom,搜索tom,也可以将Tom搜索出来
like,搜索love,同义词,也可以将like搜索出来
就不是说单纯的只是匹配完整的一个值,而是可以对值进行拆分词语后(分词)进行匹配,也可以通过缩写、时态、大小写、同义词等进行匹配。深入 NPL,自然语义处理。
normalization正规化,建立倒排索引的时候,会执行一个操作,也就是说对拆分出的各个单词进行相应的处理,以提升后面搜索的时候能够搜索到相关联的文档的概率
时态的转换,单复数的转换,同义词的转换,大小写的转换
mom ―> mother
liked ―> like
small ―> little
dogs ―> dog
query string必须以和index建立时相同的analyzer进行分词
query string对exact value和full text的区别对待
如: date:exact value 精确匹配
text: full text 全文检索
往es里面直接插入数据,es会自动建立索引,同时建立对应的mapping。(dynamic mapping)
mapping中就自动定义了每个field的数据类型
不同的数据类型(比如说text和date),可能有的是exact value,有的是full text
exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;full text,会经历各种各样的处理,分词,normaliztion(时态转换,同义词转换,大小写转换),才会建立到倒排索引中。
同时,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索
可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型;也可以提前手动创建index和tmapping,自己对各个field进行设置,包括数据类型,包括索引行为,包括分词器,等。
字符型:string,string类型包括 text 和 keyword text类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许es来检索这些词语。text类型不能用来排序和聚合。 Keyword类型不需要进行分词,可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索 数字型:long, integer, short, byte, double, float日期型:date 布尔型:boolean 二进制型:binary
详见:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/mapping-types.html
数组类型(Array datatype):数组类型不需要专门指定数组元素的type,例如: 字符型数组: [ "one", "two" ] 整型数组:[ 1, 2 ] 数组型数组:[ 1, [ 2, 3 ]] 等价于[ 1, 2, 3 ] 对象数组:[ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }]对象类型(Object datatype):_ object _ 用于单个JSON对象; 嵌套类型(Nested datatype):_ nested _ 用于JSON数组;
地理坐标类型(Geo-point datatype):_ geo_point _ 用于经纬度坐标; 地理形状类型(Geo-Shape datatype):_ geo_shape _ 用于类似于多边形的复杂形状;
IPv4 类型(IPv4 datatype):_ ip _ 用于IPv4 地址;Completion 类型(Completion datatype):_ completion _提供自动补全建议;Token count 类型(Token count datatype):_ token_count _ 用于统计做了标记的字段的index数目,该值会一直增加,不会因为过滤条件而减少。 mapper-murmur3 类型:通过插件,可以通过 _ murmur3 _ 来计算 index 的 hash 值; 附加类型(Attachment datatype):采用 mapper-attachments 插件,可支持_ attachments _ 索引,例如 Microsoft Office 格式,Open Document 格式,ePub, HTML 等。
true or false -> boolean
123 -> long
123.123 -> double
2018-01-01 -> date
hello world -> text
[] -> array
{} -> object
在上述的自动mapping字段类型分配的时候,只有text类型的字段需要分词器。默认分词器是standard分词器。
GET /index/_mapping/
{ "test_index": { # 索引名 "mappings": { # 映射列表 "test_type": { # 类型名 "properties": { # 字段列表 "age": { # 字段名 "type": "long" # 字段类型 }, "gender": { "type": "text", "fields": { # 子字段列表 "keyword": { # 子字段名 "type": "keyword", # 子字段类型,keyword不进行分词处理的文本类型 "ignore_above": 256 # 子字段存储数据长度 } } }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }}
GET /_mapping
创建索引后,应该立即手动创建映射
PUT book/_mapping { "properties": { "name": { "type": "text" }, "description": { "type": "text", "analyzer":"english", "search_analyzer":"english" }, "pic":{ "type":"text", "index":false }, "studymodel":{ "type":"text" } } }
analyzer
通过analyzer属性指定分词器。
上边指定了analyzer是指在索引和搜索都使用english,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性。
index
index属性指定是否索引。
默认为index=true,即要进行索引,只有进行索引才可以从索引库搜索到。
但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置为false。
删除索引,重新创建映射,将pic的index设置为false,尝试根据pic去搜索,结果搜索不到数据。
store
是否在source之外存储,每个文档索引后会在 ES中保存一份原始文档,存放在"_source"中,一般情况下不需要设置store为true,因为在_source中已经有一份原始文档了。
boost
字段级别的分数加权,默认值是1.0
doc_values
对not_analyzed字段,默认都是开启,分词字段不能使用,对排序和聚合能提升较大性能,节约内存
fielddata":{"format":"disabled"}
针对分词字段,参与排序或聚合时能提高性能,不分词字段统一建议使用doc_value
"fields":{"raw":{"type":"string","index":"not_analyzed"}}
可以对一个字段提供多种索引模式,同一个字段的值,一个分词,一个不分词
"ignore_above":100
超过100个字符的文本,将会被忽略,不被索引
"include_in_all":ture
设置是否此字段包含在_all字段中,默认是true,除非index设置成no选项
"index_options":"docs"//4个可选参数docs(索引文档号) ,freqs(文档号+词频),positions(文档号+词频+位置,通常用来距离查询),offsets(文档号+词频+位置+偏移量,通常被使用在高亮字段)分词字段默认是position,其他的默认是docs
"norms":{"enable":true,"loading":"lazy"}//分词字段默认配置,不分词字段:默认{"enable":false},存储长度因子和索引时boost,建议对需要参与评分字段使用 ,会额外增加内存消耗量
"null_value":"NULL"//设置一些缺失字段的初始化值,只有string可以使用,分词字段的null值也会被分词
"position_increament_gap":0//影响距离查询或近似查询,可以设置在多值字段的数据上火分词字段上,查询时可指定slop间隔,默认值是100
"search_analyzer":"ik"//设置搜索时的分词器,默认跟ananlyzer是一致的,比如index时用standard+ngram,搜索时用standard用来完成自动提示功能
"similarity":"BM25"//默认是TF/IDF算法,指定一个字段评分策略,仅仅对字符串型和分词类型有效
"term_vector":"no"//默认不存储向量信息,支持参数yes(term存储),with_positions(term+位置),with_offsets(term+偏移量),with_positions_offsets(term+位置+偏移量) 对快速高亮fast vector highlighter能提升性能,但开启又会加大索引体积,不适合大数据量用
测试
PUT book/_mapping { "properties": { "name": { "type": "text" }, "description": { "type": "text", "analyzer":"english", "search_analyzer":"english" }, "pic":{ "type":"text", "index":false }, "studymodel":{ "type":"text" } } }
插入文档:
PUT /book/_doc/1 { "name":"Bootstrap开发框架", "description":"Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现一个不受浏览器限制的精美界面效果。", "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg", "studymodel":"201002" }
Get /book/_search?q=name:开发
Get /book/_search?q=description:开发
Get /book/_search?q=pic:group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg
Get /book/_search?q=studymodel:201002
通过测试发现:name和description都支持全文检索,pic不可作为查询条件。
目前已经取代了"index": false。上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常用于过虑、排序、聚合等。
日期类型不用设置分词器。
通常日期类型的字段用于排序。
通过format设置日期格式
例子:
下边的设置允许date字段存储年月日时分秒、年月日及毫秒三种格式。
{ "properties": { "timestamp": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd" } }}
插入文档:
Post book/doc/3 {"name": "spring开发基础","description": "spring 在java领域非常流行,java程序员都在用。","studymodel": "201001", "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg", "timestamp":"2018-07-04 18:28:58"}
下边是ES支持的数值类型
尽量选择范围小的类型,提高搜索效率
对于浮点数尽量用比例因子,比如一个价格字段,单位为元,我们将比例因子设置为100这在ES中会按 分 存储,映射如下:
"price": { "type": "scaled_float", "scaling_factor": 100 },
由于比例因子为100,如果我们输入的价格是23.45则ES中会将23.45乘以100存储在ES中。
如果输入的价格是23.456,ES会将23.456乘以100再取一个接近原始值的数,得出2346。
使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。
如果比例因子不适合,则从下表选择范围小的去用:
更新已有映射,并插入文档:
PUT book/doc/3 { "name": "spring开发基础", "description": "spring 在java领域非常流行,java程序员都在用。", "studymodel": "201001", "pic":"group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg", "timestamp":"2018-07-04 18:28:58", "price":38.6 }
只能创建index时手动建立mapping,或者新增field mapping,但是不能update field mapping。
因为已有数据按照映射早已分词存储好。如果修改,那这些存量数据怎么办。
新增一个字段mapping
PUT /book/_mapping/ { "properties" : { "new_field" : { "type" : "text", "index": "false" } } }
如果修改mapping,会报错
PUT /book/_mapping/ { "properties" : { "studymodel" : { "type" : "keyword" } } }
返回:
{ "error": { "root_cause": [ { "type": "illegal_argument_exception", "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]" } ], "type": "illegal_argument_exception", "reason": "mapper [studymodel] of different type, current_type [text], merged_type [keyword]" }, "status": 400}
通过删除索引来删除映射。
true:遇到陌生字段,就进行dynamic mapping
false:新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍将出现在返回点击的源字段中。这些字段不会添加到映射中,必须显式添加新字段。
strict:遇到陌生字段,就报错
创建mapping
PUT /my_index { "mappings": { "dynamic": "strict", "properties": { "title": { "type": "text" }, "address": { "type": "object", "dynamic": "true" } } } }
插入数据
PUT /my_index/_doc/1 { "title": "my article", "content": "this is my article", "address": { "province": "guangdong", "city": "guangzhou" } }
报错
{ "error": { "root_cause": [ { "type": "strict_dynamic_mapping_exception", "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed" } ], "type": "strict_dynamic_mapping_exception", "reason": "mapping set to strict, dynamic introduction of [content] within [_doc] is not allowed" }, "status": 400}
es会根据传入的值,推断类型。
默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mapping成date,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型。
PUT /my_index { "mappings": { "date_detection": false, "properties": { "title": { "type": "text" }, "address": { "type": "object", "dynamic": "true" } } } }
测试
PUT /my_index/_doc/1 { "title": "my article", "content": "this is my article", "address": { "province": "guangdong", "city": "guangzhou" }, "post_date":"2019-09-10" }
查看映射
GET /my_index/_mapping
PUT my_index { "mappings": { "dynamic_date_formats": ["MM/dd/yyyy"] } }
插入数据
PUT my_index/_doc/1 { "create_date": "09/25/2019" }
虽然json支持本机浮点和整数数据类型,但某些应用程序或语言有时可能将数字呈现为字符串。通常正确的解决方案是显式地映射这些字段,但是可以启用数字检测(默认情况下禁用)来自动完成这些操作。
PUT my_index { "mappings": { "numeric_detection": true } }
PUT my_index/_doc/1 { "my_float": "1.0", "my_integer": "1" }
PUT /my_index { "mappings": { "dynamic_templates": [ { "en": { "match": "*_en", "match_mapping_type": "string", "mapping": { "type": "text", "analyzer": "english" } } } ] } }
插入数据
PUT /my_index/_doc/1 { "title": "this is my first article" } PUT /my_index/_doc/2 { "title_en": "this is my first article" }
搜索
GET my_index/_search?q=firstGET my_index/_search?q=is
title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的
title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到了
PUT my_index { "mappings": { "dynamic_templates": [ { "integers": { "match_mapping_type": "long", "mapping": { "type": "integer" } } }, { "strings": { "match_mapping_type": "string", "mapping": { "type": "text", "fields": { "raw": { "type": "keyword", "ignore_above": 256 } } } } } ] } }
模板参数
"match": "long_*", "unmatch": "*_text", "match_mapping_type": "string", "path_match": "name.*", "path_unmatch": "*.middle",
"match_pattern": "regex","match": "^profit_\d+$"
1结构化搜索
默认情况下,elasticsearch将字符串字段映射为带有子关键字字段的文本字段。但是,如果只对结构化内容进行索引,而对全文搜索不感兴趣,则可以仅将“字段”映射为“关键字”。请注意,这意味着为了搜索这些字段,必须搜索索引所用的完全相同的值。
{ "strings_as_keywords": { "match_mapping_type": "string", "mapping": { "type": "keyword" } } }
2仅搜索
与前面的示例相反,如果您只关心字符串字段的全文搜索,并且不打算对字符串字段运行聚合、排序或精确搜索,您可以告诉弹性搜索将其仅映射为文本字段(这是5之前的默认行为)
{ "strings_as_text": { "match_mapping_type": "string", "mapping": { "type": "text" } } }
3norms 不关心评分
norms是指标时间的评分因素。如果您不关心评分,例如,如果您从不按评分对文档进行排序,则可以在索引中禁用这些评分因子的存储并节省一些空间。
{ "strings_as_keywords": { "match_mapping_type": "string", "mapping": { "type": "text", "norms": false, "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } }
{ "tags": [ "tag1", "tag2" ]}
建立索引时与string是一样的,数据类型不能混
null,[],[null]
PUT /company/_doc/1{ "address": { "country": "china", "province": "guangdong", "city": "guangzhou" }, "name": "jack", "age": 27, "join_date": "2019-01-01"}
address:object类型
查询映射
GET /company/_mapping { "company" : { "mappings" : { "properties" : { "address" : { "properties" : { "city" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "country" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "province" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } }, "age" : { "type" : "long" }, "join_date" : { "type" : "date" }, "name" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } }
object
{ "address": { "country": "china", "province": "guangdong", "city": "guangzhou" }, "name": "jack", "age": 27, "join_date": "2017-01-01"}
底层存储格式
{ "name": [jack], "age": [27], "join_date": [2017-01-01], "address.country": [china], "address.province": [guangdong], "address.city": [guangzhou]}
对象数组:
{ "authors": [ { "age": 26, "name": "Jack White"}, { "age": 55, "name": "Tom Jones"}, { "age": 39, "name": "Kitty Smith"} ]}
存储格式:
{ "authors.age": [26, 55, 39], "authors.name": [jack, white, tom, jones, kitty, smith]}
20180901144437612.png
20180901144234453.png
以下的索引 Mapping中,_source设置为false,同时各个字段的store根据需求设置了true和false。
url的doc_values设置为false,该字段url不用于聚合和排序操作。
PUT blog_index{ "mappings": { "doc": { "_source": { "enabled": false }, "properties": { "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 100 } }, "store": true }, "publish_date": { "type": "date", "store": true }, "author": { "type": "keyword", "ignore_above": 100, "store": true }, "abstract": { "type": "text", "store": true }, "content": { "type": "text", "store": true }, "url": { "type": "keyword", "doc_values":false, "norms":false, "ignore_above": 100, "store": true } } } }}
目前ES主要有以下4种常用的方法来处理数据实体间的关联关系:
(1)Application-side joins(服务端Join或客户端Join)
这种方式,索引之间完全独立(利于对数据进行标准化处理,如便于上述两种增量同步的实现),由应用端的多次查询来实现近似关联关系查询。这种方法适用于第一个实体只有少量的文档记录的情况(使用ES的terms查询具有上限,默认1024,具体可在elasticsearch.yml中修改),并且最好它们很少改变。这将允许应用程序对结果进行缓存,并避免经常运行第一次查询。
(2)Data denormalization(数据的非规范化)
这种方式,通俗点就是通过字段冗余,以一张大宽表来实现粗粒度的index,这样可以充分发挥扁平化的优势。但是这是以牺牲索引性能及灵活度为代价的。使用的前提:冗余的字段应该是很少改变的;比较适合与一对少量关系的处理。当业务数据库并非采用非规范化设计时,这时要将数据同步到作为二级索引库的ES中,就很难使用上述增量同步方案,必须进行定制化开发,基于特定业务进行应用开发来处理join关联和实体拼接。
ps:宽表处理在处理一对多、多对多关系时,会有字段冗余问题,适合“一对少量”且这个“一”更新不频繁的应用场景。宽表化处理,在查询阶段如果只需要“一”这部分时,需要进行结果去重处理(可以使用ES5.x的字段折叠特性,但无法准确获取分页总数,产品设计上需采用上拉加载分页方式)
(3)Nested objects(嵌套文档)
索引性能和查询性能二者不可兼得,必须进行取舍。嵌套文档将实体关系嵌套组合在单文档内部(类似与json的一对多层级结构),这种方式牺牲索引性能(文档内任一属性变化都需要重新索引该文档)来换取查询性能,可以同时返回关系实体,比较适合于一对少量的关系处理。
ps: 当使用嵌套文档时,使用通用的查询方式是无法访问到的,必须使用合适的查询方式(nested query、nested filter、nested facet等),很多场景下,使用嵌套文档的复杂度在于索引阶段对关联关系的组织拼装。
(4)Parent/child relationships(父子文档)
父子文档牺牲了一定的查询性能来换取索引性能,适用于一对多的关系处理。其通过两种type的文档来表示父子实体,父子文档的索引是独立的。父-子文档ID映射存储在 Doc Values 中。当映射完全在内存中时, Doc Values 提供对映射的快速处理能力,另一方面当映射非常大时,可以通过溢出到磁盘提供足够的扩展能力。 在查询parent-child替代方案时,发现了一种filter-terms的语法,要求某一字段里有关联实体的ID列表。基本的原理是在terms的时候,对于多项取值,如果在另外的index或者type里已知主键id的情况下,某一字段有这些值,可以直接嵌套查询。具体可参考官方文档的示例:通过用户里的粉丝关系,微博和用户的关系,来查询某个用户的粉丝发表的微博列表。
ps:父子文档相比嵌套文档较灵活,但只适用于“一对大量”且这个“一”不是海量的应用场景,该方式比较耗内存和CPU,这种方式查询比嵌套方式慢5~10倍,且需要使用特定的has_parent和has_child过滤器查询语法,查询结果不能同时返回父子文档(一次join查询只能返回一种类型的文档)。而受限于父子文档必须在同一分片上,ES父子文档在滚动索引、多索引场景下对父子关系存储和联合查询支持得不好,而且子文档type删除比较麻烦(子文档删除必须提供父文档ID)。
如果业务端对查询性能要求很高的话,还是建议使用宽表化处理*的方式,这样也可以比较好地应对聚合的需求。在索引阶段需要做join处理,查询阶段可能需要做去重处理,分页方式可能也得权衡考虑下。
对比 | Nested Object | Parent/Child |
---|---|---|
优点 | 文档存储在一块,读取性能高 | 父子文档独立更新,互不影响 |
缺点 | 更新父或子文档时需要更新整个文档 | 为了维护join的关系,需要占用内存,读取性能较差 |
场景 | 子文档偶尔更新,查询频繁 | 子文档频繁更新 |
留言簿