ES_QL 稠密向量检索:本地部署实操

适配ES 9.x 本地部署

【前置必看】10分钟跑通本地最小闭环

从0开始,完成「创建索引→插入测试数据→跑通向量检索」的完整流程,先看到效果,再深入学原理。

前提条件

  1. 本地已部署Elasticsearch 9.x 版本(单机/集群均可),已启动服务,默认地址http://localhost:9200

  2. 推荐搭配Kibana(同版本),用Dev Tools执行命令,新手零门槛;无Kibana可直接用curl命令执行

  3. 关闭ES安全校验(测试环境用,生产环境需配置账号密码),或提前获取ES的账号密码

步骤1:创建支持向量检索的索引

打开Kibana Dev Tools,执行以下命令,创建名为documents的向量索引:

JSON 复制代码
# 创建向量索引(直接复制运行)
PUT documents
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "category": { "type": "keyword" },
      "content_vector": {
        "type": "dense_vector",
        "dims": 3,
        "similarity": "cosine",
        "index": true,
        "index_options": {
          "type": "hnsw",
          "m": 16,
          "ef_construction": 100
        }
      }
    }
  }
}
✅ 操作校验

执行以下命令,能正常返回索引配置,说明创建成功:

JSON 复制代码
GET documents/_mapping
📌 大白话解释(核心,后面细讲)
  • dense_vector:专门存AI生成的「向量数据」的字段类型,你可以理解成给文档做的语义身份证

  • dims: 3:向量的维度,就是这个「语义身份证」有多少个数字,这里用3维是为了测试方便,真实场景用384/768维

  • similarity: cosine:判断两个向量「像不像」的算法,就像判断两个身份证的相似度,文本场景默认用这个就行

  • index: true:给向量建快速查找的索引,就像给书做目录,不用翻完整本书找内容,大幅提升检索速度

步骤2:插入测试数据

给索引插入4条带向量的测试数据,直接复制运行:

JSON 复制代码
# 批量插入测试数据
POST documents/_bulk
{"index": {}}
{"title": "ES9.x 向量检索入门教程", "category": "教程", "content_vector": [0.1, 0.2, 0.3]}
{"index": {}}
{"title": "ES 本地部署避坑指南", "category": "教程", "content_vector": [0.2, 0.3, 0.4]}
{"index": {}}
{"title": "Java 开发实战", "category": "开发", "content_vector": [0.9, 0.8, 0.7]}
{"index": {}}
{"title": "ES 聚合查询用法", "category": "教程", "content_vector": [0.15, 0.25, 0.35]}
✅ 操作校验

执行以下ES|QL语句,能返回4条数据的标题和向量,说明插入成功:

SQL 复制代码
FROM documents
| KEEP title, content_vector

步骤3:跑通你的第一条ES|QL向量检索

我们要找和[0.12, 0.22, 0.32]这个向量最像的2条数据,执行以下语句:

SQL 复制代码
FROM documents METADATA _score
| WHERE KNN(content_vector, [0.12, 0.22, 0.32])
| SORT _score DESC
| KEEP title, _score
| LIMIT 2
✅ 预期返回结果

你会看到以下结果,说明向量检索跑通了!

title _score
ES9.x 向量检索入门教程 0.998(近似值)
ES 聚合查询用法 0.997(近似值)

【大白话核心原理】看完再也不会懵

很多新手卡壳,都是因为先被一堆术语劝退,这里用最通俗的话,把核心原理讲透,后面的所有操作都能对应上逻辑。

1. 到底什么是向量检索?

你可以这么理解:

  • 传统关键词搜索:就像查字典,只能找到字面完全匹配的内容,比如你搜"ES教程",找不到标题写"Elasticsearch入门指南"的内容

  • 向量检索:先通过AI模型,把每一篇文档、每一张图片,都转换成一串固定长度的数字(这串数字就叫向量/嵌入),就像给每个内容生成了一个「语义身份证」

    • 意思越相近的内容,「语义身份证」的数字就越像(向量距离越近)

    • 向量检索,就是找和你查询的内容「身份证最像」的结果,哪怕字面完全不一样,也能找到语义匹配的内容

2. 什么是ES|QL?为什么用它做向量检索?

ES|QL是Elasticsearch官方推出的管道式查询语言,你可以把它理解成「ES专用的SQL」,用|符号把查询步骤一步一步串起来,逻辑和流水账一样清晰。

用它做向量检索,相比传统的Query DSL,最大的好处是新手零踩坑、写法极简

  • 自动帮你处理最容易踩坑的「预过滤」逻辑,不会出现"查不到结果、结果数量不够"的问题

  • 自动推断参数,不用手动写一堆复杂的配置

  • 一套语法搞定「向量检索、关键词搜索、过滤、排序、自定义打分」,不用拼接复杂的JSON

3. 近似检索(KNN/ANN)和精确检索,到底选哪个?

这是新手最常问的问题,用一个表格讲透,看完直接选:

类型 大白话解释 优点 缺点 适用场景
近似检索(KNN/ANN) 先给向量建「目录索引」,查的时候不用比对所有数据,通过索引快速找最像的结果,就像查字典用部首索引 速度极快,百万/千万级数据也能毫秒级返回 不保证100%找到全局最像的结果(实际召回率99%+,几乎无影响) 90%的生产场景,数据量超过1万条,对查询速度有要求
精确检索 把库里所有向量,和你的查询向量挨个比对一遍,保证100%找到最像的结果,就像把整本书逐页翻一遍 结果100%精准,无遗漏 数据量超过1万条就会变得极慢,甚至查询超时 数据量小于1万条、过滤后只剩几千条数据、对召回率有100%强制要求

4. 什么是预过滤?为什么ES|QL能帮你避坑?

这是向量检索最容易踩的天坑,用大白话讲清楚:

  • 需求场景:我只想在「教程」分类里,找和查询向量最像的10条结果

  • 错误逻辑(后过滤):先在全库里找最像的10条结果,再从这10条里筛「教程」分类的内容

    • 坑点:如果全库最像的10条里,只有3条是「教程」分类,最终只会返回3条结果,达不到你要的10条,甚至返回空
  • 正确逻辑(预过滤):先把库里所有「教程」分类的内容筛出来,再在这个小范围里找最像的10条结果,保证返回数量足够

传统Query DSL:必须手动把过滤条件写到KNN的配置里,新手很容易写错,写成后过滤

ES|QL :你只要写WHERE过滤条件,ES会自动把所有过滤条件转成预过滤,根本不给你写错的机会,这是它最大的优势


【分场景实操手册】从基础到进阶,全是可复制代码

以下所有操作,均适配ES 9.x 本地部署,每一段代码都有「操作目的+逐行解释+校验方法+避坑提示」。

场景1:基础向量数据查询与校验

用于调试、验证向量数据是否写入成功,字段配置是否正确。

1.1 查询向量数据(调试用)
SQL 复制代码
-- 查看前5条数据的标题、分类、向量内容
FROM documents
| KEEP title, category, content_vector
| LIMIT 5

📌 注意:生产环境严禁返回全量向量数据,高维向量会大幅拖慢查询速度,仅调试时使用。

1.2 统计有效向量数据量
SQL 复制代码
-- 统计有向量内容的文档总数,验证数据写入成功率
FROM documents
| WHERE content_vector IS NOT NULL
| STATS 有效向量文档数 = COUNT(*)

场景2:基于KNN的近似检索(生产环境首选)

这是90%场景都会用到的核心功能,用于大规模数据的快速向量检索。

2.1 基础KNN检索(最简写法)
SQL 复制代码
-- 找和查询向量最像的Top3结果
FROM documents METADATA _score
-- 1. 执行KNN向量检索,第一个参数是向量字段名,第二个是查询向量
| WHERE KNN(content_vector, [0.12, 0.22, 0.32])
-- 2. 按相似度分数降序排序,最像的排在最前面
| SORT _score DESC
-- 3. 只返回需要的字段,不返回向量数据,提升速度
| KEEP title, category, _score
-- 4. 返回Top3结果,ES会自动把KNN的查询数量和LIMIT对齐
| LIMIT 3
📌 逐行核心解释
  • METADATA _score必须写 !KNN计算出来的相似度分数,存在ES的隐藏字段_score里,默认不返回,必须用这个关键字告诉ES"我要这个分数",不然没法排序、查看分数。

  • KNN(字段名, 查询向量):核心函数,触发近似向量检索,查询向量的维度必须和字段的dims完全一致,差一个数字都会报错。

  • LIMIT 3:ES会自动把KNN的查询数量设置为3,不用手动额外配置。

2.2 KNN进阶调优(自定义参数,平衡精度与速度)

当你需要调整检索精度、查询速度时,可以给KNN加第三个自定义参数,本地部署可直接复制调整:

SQL 复制代码
FROM documents METADATA _score
| WHERE KNN(
    content_vector, 
    [0.12, 0.22, 0.32], 
    -- 自定义参数,新手直接用默认值,有需求再调整
    {
      "k": 10, -- 每个分片先查Top10条,最终返回Top3,提升召回率
      "ef_search": 50, -- 检索时的候选集大小,越大越准、越慢,必须大于k
      "rescore_oversample": 2.0 -- 重采样倍数,越大越准、越慢,1-10之间
    }
  )
| SORT _score DESC
| KEEP title, _score
| LIMIT 3
📌 本地部署调优建议
  • 单机单分片部署:kLIMIT值保持一致即可,不用设置太大

  • 数据量超过10万条:k设为LIMIT的2-3倍,ef_search设为k的5倍

  • 追求极致精度:rescore_oversample设为2-4,不要超过10,否则查询会变慢

2.3 KNN+过滤条件(最常用的业务场景)

比如只想在「教程」分类里找结果,ES|QL会自动把过滤条件转成预过滤,彻底避免后过滤的坑,两种写法都可以,效果完全一致:

写法1:分开写WHERE子句(可读性更好,推荐)
SQL 复制代码
FROM documents METADATA _score
-- 先写过滤条件,ES自动转预过滤
| WHERE category == "教程"
-- 再写KNN检索,仅在过滤后的结果里查询
| WHERE KNN(content_vector, [0.12, 0.22, 0.32])
| SORT _score DESC
| LIMIT 3
| KEEP title, category, _score
写法2:合并写WHERE子句
SQL 复制代码
FROM documents METADATA _score
| WHERE category == "教程" AND KNN(content_vector, [0.12, 0.22, 0.32])
| SORT _score DESC
| LIMIT 3
| KEEP title, category, _score
✅ 校验预过滤是否生效

执行以下命令,查看查询的执行计划,能在knn查询里看到filter字段,说明预过滤生效了:

JSON 复制代码
# 查看ES|QL执行计划
POST _query/explain
{
  "query": """
    FROM documents METADATA _score
    | WHERE category == "教程"
    | WHERE KNN(content_vector, [0.12, 0.22, 0.32])
    | SORT _score DESC
    | LIMIT 3
  """
}

场景3:精确检索(全量比对,100%召回)

仅适用于小数据集、严格过滤后的小范围查询,用向量相似度函数实现。

3.1 常用相似度函数选择(新手直接选V_COSINE)
函数名 大白话用途 返回值规律
V_COSINE(a,b) 计算两个向量的余弦相似度,文本场景通用,不受向量长度影响 越接近1,相似度越高;越接近-1,相似度越低
V_DOT_PRODUCT(a,b) 点积计算,仅适用于已经做过归一化的向量,速度比V_COSINE快 越接近1,相似度越高
V_L2_NORM(a,b) 欧氏距离,多用于图片、音频等非文本数据 数值越小,相似度越高
3.2 精确检索完整示例
SQL 复制代码
-- 精确检索:在「教程」分类里,找和查询向量最像的Top3结果
FROM documents
-- 第一步:先过滤,缩小查询范围,必须写!否则全表扫描会超时
| WHERE category == "教程"
-- 第二步:计算每条数据和查询向量的余弦相似度,赋值给similarity字段
| EVAL similarity = V_COSINE(content_vector, [0.12, 0.22, 0.32])
-- 第三步:过滤掉向量为空、计算失败的数据
| WHERE similarity IS NOT NULL
-- 第四步:按相似度降序排序
| SORT similarity DESC
-- 第五步:返回需要的字段和Top3结果
| KEEP title, category, similarity
| LIMIT 3

📌 避坑提示:精确检索必须先写过滤条件,缩小查询范围,否则数据量超过1万条就会查询超时。

场景4:端到端语义检索(输入文本,自动转向量查询)

不用自己在代码里生成向量,ES自动帮你把自然语言文本转成向量,再执行检索,实现真正的语义搜索。

前置步骤:本地部署推理端点(一次性配置)

要实现文本自动转向量,需要先给ES导入一个开源的嵌入模型,配置推理端点,本地部署一次性配置,永久使用。

步骤1:安装Eland工具(用于导入模型到ES)

Eland是Elasticsearch官方的模型导入工具,本地电脑先安装Python,再执行以下命令安装:

Bash 复制代码
pip install eland
步骤2:导入开源嵌入模型到本地ES

这里用国内最常用的bge-small-zh-v1.5中文嵌入模型,384维,效果好、速度快,本地部署首选。执行以下命令:

Bash 复制代码
# Linux/Mac 命令
eland_import_hub_model \
  --url http://localhost:9200/ \
  --hub-model-id BAAI/bge-small-zh-v1.5 \
  --task-type text_embedding \
  --start

# Windows 命令(cmd里执行,一行写完)
eland_import_hub_model --url http://localhost:9200/ --hub-model-id BAAI/bge-small-zh-v1.5 --task-type text_embedding --start

📌 国内网络提示:如果huggingface连接超时,先设置国内镜像源,再执行命令:

Bash 复制代码
# 设置huggingface国内镜像
export HF_ENDPOINT=https://hf-mirror.com
步骤3:验证模型导入成功

执行以下命令,能看到模型状态为started,说明配置成功,推理端点ID就是bge-small-zh-v1.5

JSON 复制代码
GET _inference/text_embedding/bge-small-zh-v1.5
端到端语义检索实操

现在,你只需要输入自然语言,ES会自动转成向量,再执行检索,不用再写长长的向量数组了:

SQL 复制代码
FROM documents METADATA _score
-- 核心:TEXT_EMBEDDING(查询文本, 推理端点ID),自动把文本转成向量
| WHERE KNN(content_vector, TEXT_EMBEDDING("ES入门教程", "bge-small-zh-v1.5"))
| SORT _score DESC
| KEEP title, _score
| LIMIT 3

📌 避坑提示:模型生成的向量维度,必须和dense_vector字段的dims完全一致,比如bge-small-zh-v1.5是384维,你的字段dims也必须是384。

场景5:混合检索(关键词+语义,工业级最佳实践)

工业级搜索系统,从来都不是只用向量检索,而是关键词搜索+向量检索结合,兼顾精准匹配和语义理解,比如:

  • 关键词搜索:精准匹配用户输入的特定术语,比如用户搜"ES9.x",必须找到带这个关键词的内容

  • 向量检索:捕捉语义,找到字面不一样但意思一样的内容,比如"Elasticsearch最新版本"

ES|QL用FORK+FUSE两个关键字,极简实现混合检索,不用写复杂的JSON配置。

混合检索完整示例
SQL 复制代码
FROM documents METADATA _score, _id, _index
-- FORK:并行执行两个查询分支,同时跑向量检索和关键词检索
| FORK
  -- 分支1:向量语义检索,找语义最像的Top10结果
  (WHERE KNN(content_vector, TEXT_EMBEDDING("ES教程", "bge-small-zh-v1.5")) | SORT _score DESC | LIMIT 10)
  -- 分支2:关键词检索,找标题里匹配的Top10结果
  (WHERE MATCH(title, "ES教程") | SORT _score DESC | LIMIT 10)
-- FUSE:把两路结果融合、去重、重新排序,默认用业界最优的RRF算法
| FUSE
-- 最终按融合后的分数排序,返回Top5结果
| SORT _score DESC
| LIMIT 5
| KEEP title, _score
📌 必看注意事项
  • METADATA _score, _id, _index三个必须全写FUSE需要用_id_index去重、合并结果,少写一个就会报错。

  • FORK里的每个分支,都要单独写SORTLIMIT,建议LIMIT值设为最终返回结果的2倍,提升召回率。

  • FUSE默认用RRF算法,不用手动调权重,就能实现最优的结果融合,新手不用改任何配置。

场景6:自定义业务打分(适配真实业务场景)

真实业务里,不会只按相似度排序,还要结合热度、时效性、销量、点赞数 等业务因素,ES|QL用EVAL关键字,极简实现自定义打分,比传统的script_score简单10倍。

示例:结合语义相似度+时效性+热度的综合打分

比如我们给博客内容做检索,要求:

  • 70%的权重来自语义相似度

  • 20%的权重来自浏览量(热度)

  • 10%的加分给近30天发布的新内容(时效性)

SQL 复制代码
FROM documents METADATA _score, publish_date, view_count
-- 1. 先执行语义检索
| WHERE KNN(content_vector, TEXT_EMBEDDING("ES教程", "bge-small-zh-v1.5"))
-- 2. 自定义综合打分,用EVAL关键字实现
| EVAL 综合打分 = 
    _score * 0.7 + 
    (view_count / 10000) * 0.2 + 
    IF(publish_date > NOW() - 30 DAYS, 0.1, 0)
-- 3. 按综合打分降序排序
| SORT 综合打分 DESC
-- 4. 返回结果
| KEEP title, 综合打分, _score, view_count
| LIMIT 10

📌 优势:你可以像写Excel公式一样,灵活调整每个业务因素的权重,不用写复杂的脚本,调试起来也很方便,直接把中间字段返回就能验证逻辑是否正确。

场景7:参数化查询(生产环境强制最佳实践)

真实业务里,查询向量都是代码动态生成的,直接把向量写在ES|QL语句里,会有3个致命问题:

  1. 千维向量有上千个数字,语句会变得超长,可读性极差

  2. ES解析超长语句很慢,影响查询性能

  3. 有SQL注入风险,不安全

ES|QL的参数化查询,完美解决这些问题,生产环境必须用。

参数化查询完整示例

通过ES的_query API执行,把查询向量放在params里,和查询语句分离:

JSON 复制代码
POST _query
{
  "query": """
    FROM documents METADATA _score
    | WHERE KNN(content_vector, ?query_vector)
    | SORT _score DESC
    | KEEP title, _score
    | LIMIT 10
  """,
  "params": {
    "query_vector": [0.12, 0.22, 0.32]
  }
}
📌 核心优势
  1. 性能提升:向量通过请求解析器直接处理,不用经过ES|QL语法解析,千维向量解析耗时降低90%以上

  2. 安全无注入:语句和参数分离,彻底避免注入风险

  3. 可读性强:查询逻辑清晰,不用被长长的向量数组干扰

  4. 代码友好:业务代码里调用时,只需要把向量当参数传入,不用拼接语句

附:Python代码调用示例(elasticsearch-py库)
Python 复制代码
from elasticsearch import Elasticsearch

# 连接本地ES
es = Elasticsearch("http://localhost:9200")

# 定义ES|QL语句和参数
query = """
  FROM documents METADATA _score
  | WHERE KNN(content_vector, ?query_vector)
  | SORT _score DESC
  | KEEP title, _score
  | LIMIT 10
"""
params = {
  "query_vector": [0.12, 0.22, 0.32]
}

# 执行查询
response = es.esql.query(query=query, params=params)
print(response)

【本地部署避坑大全】常见报错+一步解决

新手90%的问题,都在这里能找到解决方案,直接对照排查。

常见报错/问题 根因 一步解决
KNN查询返回的结果数量,比LIMIT设置的少,甚至为空 1. 预过滤后候选集不足;2. k值设置太小 1. 调高KNN的k参数,设为LIMIT的2-3倍;2. 放宽过滤条件,保证候选集数量足够
向量检索报错:维度不匹配 查询向量的维度,和dense_vector字段的dims设置不一致 1. 核对嵌入模型的输出维度,和索引映射的dims必须完全一致;2. 检查查询向量的数字个数
TEXT_EMBEDDING函数报错,提示推理端点不存在 模型导入失败,或端点未启动 1. 执行GET _inference/text_embedding/*查看所有端点;2. 重新执行模型导入命令,加上--start参数
混合检索FUSE报错,提示缺少字段 没有在METADATA里声明_id_index 必须写成FROM 索引名 METADATA _score, _id, _index,三个字段缺一不可
精确检索查询超时 没有先过滤,全表扫描全量向量 必须先写WHERE过滤条件,把查询范围缩小到万条以内,再做精确计算
本地ES内存占用过高,甚至OOM崩溃 1. 向量维度太高、数量太多,堆外内存不足;2. HNSW索引参数设置过大 1. JVM堆内存设置为机器内存的50%,不超过31G;2. 调低HNSW的mef_construction参数;3. 单索引不超过4个dense_vector字段
KNN查询的_score都是0,或者排序不对 没有写METADATA _score,拿不到正确的分数 在FROM后面必须加上METADATA _score,才能获取KNN的相似度分数

【本地部署生产级优化指南】从测试到上线

1. 索引映射优化

  • 向量维度:满足语义效果的前提下,优先选低维度模型,比如384维的bge-small,比768维的bge-base内存占用少50%,速度快一倍

  • 相似度算法:已归一化的向量,优先用dot_product,比cosine性能更好

  • HNSW索引:百万级数据,m=16ef_construction=100;千万级数据,m=32ef_construction=200,不要盲目调大

2. 查询性能优化

  • 生产环境严禁返回全量向量数据,只返回业务需要的字段

  • 高并发场景,优先用近似检索,不要用精确检索

  • 过滤条件尽量前置,缩小KNN的查询范围,提升速度

  • 必须用参数化查询,提升解析性能,避免注入风险

3. 内存优化

  • JVM堆内存:设置为机器内存的50%,最大不超过31G,剩下的内存留给系统缓存向量索引

  • 单节点向量数量:单机部署,单节点向量总数不超过1000万条,超过建议分集群

  • 关闭不需要的字段索引:非检索、过滤的字段,设置index: false,节省内存

相关推荐
TDengine (老段)1 小时前
TDengine IDMP 高级功能——计量单位
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
侧耳倾听1112 小时前
kibana-基本使用
elasticsearch
海兰2 小时前
Elasticsearch 9.x Java 异步客户端
java·elasticsearch·jenkins
Elastic 中国社区官方博客2 小时前
推出 Elastic Serverless Plus 附加组件,支持 AWS PrivateLink 功能
大数据·elasticsearch·搜索引擎·云原生·serverless·全文检索·aws
light blue bird2 小时前
MES/ERP多段业务多线程并发组件
大数据
十月南城3 小时前
Flink实时计算心智模型——流、窗口、水位线、状态与Checkpoint的协作
大数据·flink·wpf
谁不学习揍谁!3 小时前
基于python大数据机器学习旅游数据分析可视化推荐系统(完整系统+开发文档+部署教程+文档等资料)
大数据·python·算法·机器学习·数据分析·旅游·数据可视化
AC赳赳老秦3 小时前
DeepSeek多模态Prompt优化:贴合2026技术趋势的精准指令设计方法
大数据·人工智能·自然语言处理·架构·prompt·prometheus·deepseek
侧耳倾听1113 小时前
ElasticSearch-基本入门
elasticsearch