1. Elasticsearch多条件查询入门指南
第一次接触Elasticsearch的开发者往往会被它强大的查询功能所震撼,但同时也容易被复杂的查询语法劝退。其实多条件查询就像搭积木,只要掌握几个基础组件,就能组合出各种复杂的查询场景。我在实际项目中处理过电商平台的商品搜索系统,从最初只会用简单match查询,到现在能灵活运用各种复合查询,这个过程让我深刻体会到Elasticsearch查询语法的精妙之处。
最基础的bool查询相当于SQL中的WHERE条件组合,它包含四个关键子句:must(必须满足)、should(应该满足)、must_not(必须不满足)和filter(过滤条件)。举个例子,我们要搜索价格在100-500元之间、库存充足、且不是二手商品的手机,对应的查询DSL是这样的:
{ "query": { "bool": { "must": [ {"match": {"category": "手机"}}, {"range": {"price": {"gte": 100, "lte": 500}}} ], "filter": [ {"range": {"stock": {"gt": 0}}} ], "must_not": [ {"term": {"is_used": true}} ] } } }这里有个新手常踩的坑:filter和must的区别。两者都能过滤文档,但filter不参与相关性评分,且结果会被缓存。对于不需要计算相关性的条件(如状态、库存等),用filter性能更好。我曾经优化过一个查询,把must改成filter后,响应时间从200ms降到了50ms。
2. 复合查询的进阶技巧
当基础查询无法满足需求时,就需要组合使用各种查询类型。dis_max查询特别适合处理"任意条件匹配"的场景,比如搜索标题或内容包含关键词的文章:
{ "query": { "dis_max": { "queries": [ {"match": {"title": "Elasticsearch教程"}}, {"match": {"content": "Elasticsearch教程"}} ], "tie_breaker": 0.3 } } }tie_breaker参数是个实用技巧,它控制着当文档匹配多个子查询时的得分计算方式。设置为0时只取最高分,0-1之间会对其他匹配子查询的分数进行加权求和。我建议初期可以设0.2-0.5,根据实际效果调整。
function_score查询则能实现更复杂的业务逻辑。比如电商网站希望将赞助商品置顶,同时考虑销量和好评率:
{ "query": { "function_score": { "query": {"match_all": {}}, "functions": [ { "filter": {"term": {"is_sponsored": true}}, "weight": 3 }, { "field_value_factor": { "field": "sales", "factor": 0.1, "modifier": "log1p" } }, { "field_value_factor": { "field": "rating", "factor": 2 } } ], "boost_mode": "sum" } } }这里用到了field_value_factor函数,可以对字段值进行数学运算。modifier参数支持多种计算方式,比如log1p能防止某个指标过高导致结果倾斜。我在实现一个推荐系统时,就通过调整这些参数使推荐结果更加均衡。
3. 处理复杂数据结构
现实中的数据往往不是简单的扁平结构,比如商品可能有多个SKU,博客文章有评论列表。这时就需要nested查询来处理嵌套对象。假设商品数据是这样的结构:
{ "name": "智能手机", "specs": [ {"color": "黑色", "memory": "8GB"}, {"color": "白色", "memory": "6GB"} ] }要搜索内存8GB的白色手机,普通查询会出错,因为它在数组元素间做的是或运算。正确做法是:
{ "query": { "nested": { "path": "specs", "query": { "bool": { "must": [ {"term": {"specs.color": "白色"}}, {"term": {"specs.memory": "8GB"}} ] } } } } }nested查询的性能消耗较大,我建议在mapping设计时就考虑好哪些字段需要nested类型。曾经有个项目因为误用nested导致查询超时,后来通过合理的数据建模解决了问题。
父子文档查询(has_child/has_parent)适合一对多关系,比如部门和员工。但要注意父子文档必须存储在同一个分片上,这会影响数据路由策略。相比nested,父子文档的查询性能更低,除非真的需要文档独立更新,否则建议优先使用nested。
4. 特殊场景查询优化
地理位置查询是很多应用的刚需。Elasticsearch支持三种地理查询方式:
// 距离查询:查找5公里内的店铺 { "query": { "geo_distance": { "distance": "5km", "location": { "lat": 39.9042, "lon": 116.4074 } } } } // 边界框查询:查找矩形区域内的店铺 { "query": { "geo_bounding_box": { "location": { "top_left": {"lat": 40, "lon": 116}, "bottom_right": {"lat": 39, "lon": 117} } } } } // 多边形查询:查找自定义区域内的店铺 { "query": { "geo_polygon": { "location": { "points": [ {"lat": 39.9, "lon": 116.4}, {"lat": 39.8, "lon": 116.5}, {"lat": 39.7, "lon": 116.3} ] } } } }对于中文搜索,建议使用ik分词器,并结合match_phrase处理精确短语匹配。有个项目原本用standard分词器导致搜索"机器学习"会出现不相关结果,改用ik后准确率提升明显:
{ "query": { "match_phrase": { "title": { "query": "机器学习", "slop": 2 // 允许中间有2个其他词 } } } }script查询虽然强大但性能较差,应该作为最后的选择。比如要找出价格低于平均价2倍的商品:
{ "query": { "script": { "script": { "source": "doc['price'].value < params.avg_price * 2", "params": { "avg_price": 300 } } } } }在大数据量下,这类查询可能会很慢。我通常会预计算这类指标,用filter代替script查询。