记一次线上 ElasticSearch多条件查询失效问题的排查过程
背景说明
在近期的业务维护中,遇到了一个典型的多环境查询表现不一致的问题。
问题现象:
在生产环境的某个业务分页查询页面中,仅使用“时间范围”条件进行查询时,能够正常返回数据;但当附加特定的过滤条件(如:设备名称、员工姓名、对话内容)时,查询结果直接为空。而在研发和测试环境中,相同的代码段和查询条件,均能正常过滤并返回正确数据。研发与生产环境相互隔离。
核心架构背景:
该业务模块的底层数据存储采用了 MySQL + Elasticsearch 的双存储架构。系统设计了一套动态路由机制,根据传入的查询条件决定请求是走 DB 还是走 ES。
结论前置
该问题是由 “代码层面的查询路由策略” 与 “生产/研发环境 ES 索引 Mapping 不一致” 共同叠加导致的一个隐蔽 Bug。
具体原因如下:
路由触发条件: 代码中配置了模糊查询拦截开关。仅通过日期查询时,请求路由至 MySQL,正常返回;附加设备名称等模糊查询字段后,请求切换至 ES。
底层 Mapping 差异: 在封装 ES 查询条件时,代码默认追加了系统来源标识(
sourceSystem)作为term精确匹配的公共基础过滤条件。研发环境: 该字段在 ES 中的 Mapping 类型为
keyword,term查询正常命中。生产环境: 该字段在 ES 中的 Mapping 类型被错误地创建为
text(附带keyword子字段)。由于text类型默认应用分词器(大写转小写等),导致 Java 代码中针对原值(大写英文字符串)的term强校验彻底失效,从而将符合条件的数据全部拦截,返回空集。
详细排查流程
整个排查过程遵循控制变量法,从数据层、配置层到代码层逐步剥离。
Step 1: 验证生产数据是否成功落库及字段完整性
动作: 在生产环境 UI 界面,仅输入“日期范围”进行查询。
结果: 页面列表成功展示了相关数据,并且记录中的“设备名称”、“员工姓名”等字段均有明确的值。
结论: 确认真实的业务数据已完整落库,且同步至存储介质(当时尚未确定展示的数据来自 DB),排除了“数据本身丢失或字段为空”的可能。
Step 2: 验证核心检索字段的 ES 倒排索引配置
动作: 怀疑生产环境对应业务字段(如设备名称、员工姓名)的分词器配置或 Mapping 类型与研发环境不一致,导致倒排索引匹配失败。通过运维获取并对比了两套环境的 ES Mapping 结构及 Settings(自定义分词器配置)。
结果: 两套环境中,目标核心字段的定义完全一致,均为
text类型,配置了相同的ignore_above阈值,并使用了相同的ngram自定义分词器,且分词器切分参数无差异。结论: 排除了目标业务字段本身的结构配置问题。
Step 3: 直接执行底层 DSL 验证及控制变量测试
动作: 提取了 Java 代码生成的完整 ES Query DSL,在生产环境直接对特定单条数据执行查询。尝试逐步剥离“员工”、“设备”、“内容”等组合条件。
结果: 无论如何剥离特定业务条件,只要携带基础的公共过滤条件,生产环境的 ES 始终返回 0 条结果。
结论: 锁定问题出在 ES 查询的 公共基础条件 上,而不是那几个触发查询异常的业务字段上。
Step 4: 追踪查询路由与代码逻辑链路
动作: 重新审视 Java 服务端代码,特别是分页查询的入口逻辑。发现代码中针对 ES 查询有一个配置开关(如:
onlyFuzzy)。结果: * 分析代码得知:当开关开启时,仅当请求参数中包含特定的模糊搜索字段时,才会调用
hasFuzzySearchCondition方法并返回true,从而将查询路由到 ES。若未传这些字段,查询默认走 DB。- 这完美解释了之前的现象:只传日期时走的是 DB,所以有数据;传了设备名后切到了 ES,而 ES 侧存在问题,导致返回空。
Step 5: 定位 ES 公共条件的 Mapping 冲突
动作: 重点审查走 ES 链路时,代码强制追加的公共过滤条件。发现组装逻辑中有一段:
{"term": {"sourceSystem": "特定系统标识"}}。结果: 对比生产与研发环境该字段的 Mapping:
研发 Mapping:
"sourceSystem": { "type": "keyword" }生产 Mapping:
"sourceSystem": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }
结论: 生产环境的
sourceSystem是text类型,存入的数据经过默认分词器处理。而代码中使用term查询直接匹配未分词的字符串常量,导致查询在生产环境 ES 层面被 100% 过滤。
解决方案
临时/修复方案: 若必须使用该字段过滤,应将代码中的查询条件修改为针对子字段的匹配(如
sourceSystem.keyword),或重建生产环境索引以对齐 Mapping。最终重构方案:
经评估,业务上使用系统来源作为数据隔离的条件并不够严谨。最终修改了 ES 的查询逻辑代码,废除了基于
sourceSystem的条件过滤,统一改为通过tenantId(租户 ID) 进行数据权限隔离。此举既解决了环境差异带来的 Bug,也更符合多租户架构的规范。
经验总结
多数据源架构下的陷阱: 在 DB 与 ES 混合使用的场景中,前端表现出的“有数据”并不代表 ES 中能查到数据。排查时必须首先明确当前请求的真实底层路由路径。
环境配置的强一致性: ES 的 Mapping 和 Analyzer 极其敏感。在发布流程中,除代码外,必须确保底层数据存储结构的脚本在各环境严格一致。
对
term查询的慎用: 对非显式声明为keyword的字段使用term精确匹配是极高风险的操作,极易因分词原因导致匹配失效。

