← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

OceanBase seekdb 混合检索实战助手

OceanBase seekdb / OceanBase 的全文检索 + 向量检索 + 混合检索实战助手。当用户要在 seekdb 上做 RAG 检索(FULLTEXT INDEX、MATCH/AGAINST、向量索引 VECTOR INDEX + HNSW、l2_distance/cosine_distance)、排查“中文搜不到”“召回为空”“布尔模式查不到”“向量维度不匹配”“建表报 1235”“索引写了但查不到”、给中文语料选分词器(space/ngram/ngram2/ik + 自定义词典)、或编写混合检索 SQL 与 Recall@K 校验时使用。Use when working with OceanBase seekdb full-text search, vector index, hybrid search, HNSW, MATCH AGAINST, Chinese tokenization, or RAG retrieval over seekdb.

person作者: afbghshfshfhubModelScope

OceanBase seekdb 混合检索实战助手

给 AI 助手(或人)一份逐条实测过的 seekdb 检索速查表:语法怎么写、为什么查不到、性能怎么取舍。

本 Skill 的每条语法、每个报错、每个召回差异都在 OceanBase 4.3.5.3 seekdb (r1.4.0.0)(5.7.25-OceanBase seekdb-v1.4.0.0)上实跑验证过。 踩过的坑都写在这里,不要再重复踩。

何时使用

  • 要在 seekdb / OceanBase 上建全文索引或向量索引,写 MATCH ... AGAINST、l2_distance、cosine_distance;
  • 要做混合检索(全文召回 + 向量排序 + 结构化过滤),或给 RAG 应用选检索方案;
  • 排查:中文查不到、布尔模式查不到、建表报 1235、向量维度报错、索引写了数据却检索不到、召回率上不去;
  • 给中文/英文/混合语料选分词器,或给领域专有名词配自定义词典。

第 0 步:先确认环境(不要跳过)

SELECT version();
SHOW VARIABLES LIKE 'version_comment';

期望看到 version_comment 里含 seekdb。如果不是 seekdb, 下面 FULLTEXT 与 VECTOR INDEX 的语法可能不同(社区版 OceanBase 的向量索引语法不一样)。

环境体检用随附脚本(只读,不写任何数据):

export OB_HOST=127.0.0.1 OB_PORT=2881 OB_USER=root OB_PASSWORD='***'
python3 ob_check.py --db your_db --table your_table

脚本随 Skill 一起分发,位于 Skill 根目录(仓库副本中的路径为 scripts/ob_check.py)。

输出包含:版本、列定义、全文索引及实际分词器、向量索引、向量列维度、IK 词典表, 并检查常见问题(中文语料配 space 分词器、有向量列但没建向量索引等)。 加 --json 可输出结构化结果。退出码 0 = 无阻塞问题,1 = 有阻塞问题。

第 1 条线:全文检索

建表

CREATE TABLE kb (
  id        bigint PRIMARY KEY,
  doc_id    varchar(64),
  title     varchar(512),
  content   varchar(2048),
  FULLTEXT INDEX ft_title(title, content)
);
  • FULLTEXT INDEX 是独立于普通 INDEX 的索引类型,可以建多列(FULLTEXT INDEX f(col1, col2));
  • ❌ INDEX idx(title) fulltext 是语法错误(ERROR 1064);
  • ⚠️ 不写 WITH PARSER 时默认分词器是 space,中文直接废掉,见第 4 条线。

查询

-- 自然语言模式
SELECT doc_id, title FROM kb WHERE MATCH(title, content) AGAINST('OceanBase');

-- 布尔模式:+ 必须包含,- 必须不包含
SELECT doc_id, title FROM kb WHERE MATCH(title, content) AGAINST('+OceanBase -MySQL' IN BOOLEAN MODE);

MATCH(...) 里的列必须和建索引时的列一致(建了 (title, content) 就写两个)。 不一致报 ERROR 1191 (HY000): Can't find FULLTEXT index matching the column list。

第 2 条线:向量检索

建表:向量索引有独立语法

CREATE TABLE kb (
  id  bigint PRIMARY KEY,
  vec VECTOR(4),
  VECTOR INDEX idx_vec(vec) WITH (distance=l2, type=hnsw)
);

这是最容易写错的地方,实测结论:

| 写法 | 结果 | | --- | --- | | INDEX idx_vec(vec) | ❌ ERROR 1235: create index on vector column is not supported | | ALTER TABLE kb ADD INDEX idx_vec(vec) | ❌ 同上 | | VECTOR INDEX idx_vec(vec) | ❌ ERROR 1235: vector index params not set is not supported | | VECTOR INDEX idx_vec(vec) WITH (distance=l2) | ❌ ERROR 1235: vector index params not set distance and type is not supported | | VECTOR INDEX idx_vec(vec) WITH (distance=l2, type=hnsw) | ✅ 正确 |

也就是说 distance 和 type 必须同时给。type 目前只支持 hnsw(type=flat 报 ERROR 1235: this type of vector index type is not supported)。 建好之后 SHOW CREATE TABLE 会显示默认参数:

VECTOR KEY `idx_vec` (`vec`) WITH (DISTANCE=L2, TYPE=HNSW, LIB=VSAG, M=16, EF_CONSTRUCTION=200, EF_SEARCH=64)

查询

-- 近似检索(走 HNSW 索引,快)
SELECT id FROM kb ORDER BY l2_distance(vec, [1,0,0,0]) APPROXIMATE LIMIT 10;

-- 精确检索(暴力算,慢但准,用于算 Recall@K 的真值)
SELECT id FROM kb ORDER BY l2_distance(vec, [1,0,0,0]) LIMIT 10;

-- 余弦距离
SELECT id, cosine_distance(vec, [1,0,0,0]) AS d FROM kb ORDER BY d LIMIT 10;

APPROXIMATE 是开关,不是修饰词:加它才走索引,不加就是全表精确算。两者结果可能不同 (HNSW 是近似召回),这是正常的——但如果你要做 Recall@K 校验,真值必须用不带 APPROXIMATE 的精确查询。

第 3 条线:混合检索(一条 SQL 干完)

SELECT doc_id,
       title,
       l2_distance(vec, [1,0,0,0]) AS dist
FROM   kb
WHERE  MATCH(title, content) AGAINST('+检索' IN BOOLEAN MODE)
  AND  tenant_id = 42          -- 结构化过滤条件放在同一层
ORDER  BY dist
LIMIT  10;

要点:

  • 过滤、召回、排序在同一个 SELECT 里,不要在客户端把两路结果拼起来——那会让过滤条件失效、 召回率不可控,也失去数据库一次查询完成全部工作的优势;
  • ORDER BY <距离函数> 必须有对应的 VECTOR INDEX 兜底,否则退化成全表算;
  • 向量维度必须与 VECTOR(n) 完全一致,写错维度直接报错,不要"截断"或"补零"绕过。

更多可复制的 SQL 模板见同目录的 hybrid-search-sql.md(仓库副本中为 references/hybrid-search-sql.md)。

第 4 条线:中文检索(最容易踩的坑)

坑 1:默认分词器是 space,中文召回全空

space 按空白切分。中文文本没有空格,等于一个超长 token,所以:

-- 默认 space 分词器 + 中文:召回为空(实测 0 行)
SELECT id FROM kb WHERE MATCH(title, content) AGAINST('混合检索');

解决办法是显式指定分词器:

FULLTEXT INDEX ft(title, content) WITH PARSER ik      -- 中文首选,支持自定义词典
FULLTEXT INDEX ft(title, content) WITH PARSER ngram    -- n-gram 切分
FULLTEXT INDEX ft(title, content) WITH PARSER ngram2   -- 另一种 n-gram 切分

坑 2:布尔模式下,多字中文词召回为空

实测结论(ik / ngram / ngram2 三种分词器都一样):

| 查询 | 自然语言模式 | 布尔模式 | | --- | --- | --- | | AGAINST('混合检索') | ✅ 命中 | — | | AGAINST('混合检索' IN BOOLEAN MODE) | — | ❌ 0 行 | | AGAINST('混合检' IN BOOLEAN MODE) | — | ❌ 0 行 | | AGAINST('混合' IN BOOLEAN MODE) | — | ✅ 命中(2 字词) | | AGAINST('检索' IN BOOLEAN MODE) | — | ✅ 命中(2 字词) | | AGAINST('+混合' IN BOOLEAN_MODE) | — | ✅ 命中 | | AGAINST('"混合检索"' IN BOOLEAN MODE) | — | ❌ 0 行(引号短语也不行) |

结论:中文查询不要用 BOOLEAN 模式。 布尔模式对中文按 2 字粒度匹配, 3 字及以上的词查不到。中文走自然语言模式,英文走布尔模式,两者分开写。

坑 3:各分词器的参数名不一样

| 分词器 | 参数 | 可用值 | | --- | --- | --- | | space(默认) | min_token_size, max_token_size | 默认 3 / 84 | | ik | ik_mode | smart(默认)/ max_word 等 | | ngram | ngram_token_size | 如 2 | | ngram2 | min_ngram_size, max_ngram_size | 如 2 / 3 |

⚠️ 给 ngram 传 min_ngram_size/max_ngram_size 会报 ERROR 1235 ngram config not supported (这两个是 ngram2 的参数名);给 ngram2 传 ngram_token_size 同理不生效。

坑 4:自定义词典——语法接受,但实测不生效

WITH PARSER ik 支持 dict_table 属性,DDL 不会报错:

-- 词典表:放在 oceanbase 库里,带 word 列
CREATE TABLE oceanbase.my_dict (
  word varchar(100) PRIMARY KEY
);
INSERT INTO oceanbase.my_dict (word) VALUES ('OceanBase'), ('seekdb'), ('向量数据库');

-- 挂到全文索引上
ALTER TABLE kb ADD FULLTEXT INDEX ft(title, content)
  WITH PARSER ik PARSER_PROPERTIES=(dict_table='my_dict');

但 A/B 实测:加了自定义词典与不加,分词结果完全一致(构造长复合词 向量数据库检索系统 做对照,数据库/架构 的命中情况两边相同)。 往内置词典表 oceanbase.__ft_dict_ik_utf8 灌词后同样没有变化。 本版本的内置词典表本身也是空的(0 行)。

结论:seekdb r1.4.0.0 上不要指望用词典控制分词。 需要可控的切分粒度时,用 ngram(固定 ngram_token_size)或 ngram2 (min_ngram_size / max_ngram_size)——这两个分词器的粒度是显式可控且实测有效的。

另外几个与词典相关的版本差异(实测于 r1.4.0.0):

  • ❌ CREATE TABLE ... FULLTEXT_DICT='Y' → ERROR 1064,seekdb 没有这个表级属性;
  • ❌ ALTER SYSTEM REFRESH FULLTEXT DICT ... → ERROR 1064,seekdb 没有这条语句;
  • ✅ 词典表读取方式是 SELECT word FROM oceanbase.<表名> ORDER BY word, 所以表名不要带库名前缀(带 db. 前缀会拼成非法的三段式),表要建在 oceanbase 库里。

排障速查

| 现象 | 原因 | 处理 | | --- | --- | --- | | 中文 AGAINST 召回为空 | 默认 space 分词器不切中文 | 改用 WITH PARSER ik / ngram / ngram2 | | 中文 + BOOLEAN 模式召回为空 | 布尔模式按 2 字粒度匹配中文 | 中文改用自然语言模式 | | ERROR 1191 | MATCH 的列与索引列不一致 | 与建索引的列逐一对齐 | | ERROR 1235 ngram config not supported | ngram 用了 ngram2 的参数名 | ngram 用 ngram_token_size,ngram2 用 min/max_ngram_size | | ERROR 1235 create index on vector column is not supported | 用普通 INDEX 给向量列建索引 | 改用 VECTOR INDEX ... WITH (distance=l2, type=hnsw) | | ERROR 1235 vector index params not set | VECTOR INDEX 缺参数 | distance 和 type 必须同时给 | | 向量维度报错 | 写入维度 ≠ VECTOR(n) | 对齐维度,不要截断补零 | | 写入后检索不到新数据 | 增量索引尚未刷新 | 等待刷新或 call dbms_vector.rebuild_index('idx','tbl','col'); | | 精确与近似结果不一致 | APPROXIMATE 走 HNSW 近似召回 | 正常现象;用精确查询算真值校验 Recall@K | | 召回率低 | 只有向量召回 / 只有全文召回 | 混合检索;或给中文换分词器、加词典 | | 查询很慢 | ORDER BY 的距离函数没走索引 | 确认 VECTOR INDEX 存在;检查是否漏写 APPROXIMATE |

完整报错对照与更多场景见同目录的 troubleshooting.md(仓库副本中为 references/troubleshooting.md)。

性能与质量的取舍

  • 近似 vs 精确:APPROXIMATE 走 HNSW,延迟低;精确查询全表算距离,只用于小数据集算真值;
  • 过滤与召回的顺序:结构化过滤条件越有选择性(租户、状态、时间窗),向量检索越快;
  • 召回质量的验证方式(不要只看展示):
    1. 用同一批查询分别跑近似与精确查询;
    2. 以精确查询结果为真值,算 Recall@K = |近似TopK ∩ 精确TopK| / K;
    3. 同一口径下比较"量化前/后""换分词器前/后"的质量变化;
  • 分词器直接影响召回:默认 space 对中文等于没分词,换成 ngram/ik 后召回通常会显著变化, 换完必须重新测 Recall@K,而不是凭感觉;
  • 查询模式也要测:同一份语料,自然语言模式和布尔模式的召回结果可以完全不同, 上线前两种模式都跑一遍 Recall@K。

边界

  • 本 Skill 的语法基于 seekdb r1.4.0.0;更低版本或社区版 OceanBase 的全文/向量索引能力可能不同, 遇到语法错误先看 show create table;
  • 不负责模型调用与 Embedding 生成,只负责存储与检索这一层;
  • 不包含任何账号、密码、连接串。连接信息一律从环境变量读取。

参考

  • hybrid-search-sql.md —— 可直接复制的建表、召回、混合检索、RRF 融合、Recall@K 校验 SQL(仓库副本中为 references/hybrid-search-sql.md)
  • troubleshooting.md —— 报错与"查不到"问题的完整对照表(仓库副本中为 references/troubleshooting.md)
  • ob_check.py —— 只读体检脚本,输出索引与分词器现状(仓库副本中为 scripts/ob_check.py)
  • validate_skill_sql.sql —— 本文档全部 SQL 的可执行验证脚本,逐条断言(仓库副本中为 scripts/validate_skill_sql.sql)
  • OceanBase seekdb 项目:https://github.com/oceanbase/seekdb
<!-- OB-S:p22crrf7gNi7RpTYkLAb6Q -->