本方案旨在对达梦数据库(DM8)的多种全文分词器进行系统性功能与性能验证。所有测试用例均基于 10万条 中英文混合商品描述数据,脚本完整自包含,可直接逐条执行。
| 项目 | 配置规格 |
|---|---|
| 数据库版本 | 达梦 DM8 |
| 操作系统 | WIN10 |
| CPU | Intel I7 |
| 内存 | 16 GB DDR4 |
| 磁盘 | NVMe SSD 512GB(顺序读 3500MB/s) |
| 字符集 | UTF-8 |
| 总数据量 | 10万条 记录(中英文混合商品描述) |
-- 删除旧表(如果存在)
DROP TABLE IF EXISTS ft_test CASCADE;
DROP TABLE IF EXISTS ft_test_stats CASCADE;
-- 创建主测试表
CREATE TABLE ft_test (
id INT PRIMARY KEY,
content VARCHAR(4000),
category VARCHAR(50),
create_time DATETIME
);
---
### 第二步:完整数据生成(10万条记录,SQL无省略)
```sql
-- 使用达梦存储过程批量生成10万条中英文混合测试数据
DECLARE
v_i INT := 1;
v_content VARCHAR(4000);
v_name VARCHAR(100);
v_desc VARCHAR(200);
v_brand VARCHAR(50);
BEGIN
WHILE v_i <= 100000 LOOP
-- 商品名称(10种分类)
IF MOD(v_i, 10) = 0 THEN
v_name := '华为MateBook X Pro笔记本电脑';
ELSIF MOD(v_i, 10) = 1 THEN
v_name := '小米14 Ultra旗舰智能手机';
ELSIF MOD(v_i, 10) = 2 THEN
v_name := '苹果iPad Pro平板电脑';
ELSIF MOD(v_i, 10) = 3 THEN
v_name := '联想ThinkCentre商用台式机';
ELSIF MOD(v_i, 10) = 4 THEN
v_name := '戴尔UltraSharp 4K显示器';
ELSIF MOD(v_i, 10) = 5 THEN
v_name := '三星DDR5 32GB内存条';
ELSIF MOD(v_i, 10) = 6 THEN
v_name := '英特尔酷睿i9-13900K处理器';
ELSIF MOD(v_i, 10) = 7 THEN
v_name := 'AMD Radeon RX 7900 XTX显卡';
ELSIF MOD(v_i, 10) = 8 THEN
v_name := '西部数据SN850X 2TB固态硬盘';
ELSE
v_name := '金士顿DT Max 1TB高性能U盘';
END IF;
-- 详细描述(5种场景)
IF MOD(v_i, 5) = 0 THEN
v_desc := '该设备采用最新制程工艺,高性能计算能力出色,适用于专业办公和大型3A游戏场景,支持多任务并行处理。';
ELSIF MOD(v_i, 5) = 1 THEN
v_desc := '超轻薄全金属机身设计,搭配长续航电池,便携性极佳,适合商务出差和移动办公使用。';
ELSIF MOD(v_i, 5) = 2 THEN
v_desc := '配备高刷新率OLED大屏幕,色彩表现精准,视觉沉浸感极强,适合影音娱乐和图形设计工作。';
ELSIF MOD(v_i, 5) = 3 THEN
v_desc := '性价比突出,性能均衡稳定,功耗控制优秀,是学生用户和家庭日常使用的理想选择。';
ELSE
v_desc := '专业级生产力工具,强大的多核渲染能力,完美满足视频剪辑、3D建模和代码编译等重负载需求。';
END IF;
-- 品牌英文名(6种)
IF MOD(v_i, 6) = 0 THEN
v_brand := 'Huawei';
ELSIF MOD(v_i, 6) = 1 THEN
v_brand := 'Xiaomi';
ELSIF MOD(v_i, 6) = 2 THEN
v_brand := 'Apple';
ELSIF MOD(v_i, 6) = 3 THEN
v_brand := 'Lenovo';
ELSIF MOD(v_i, 6) = 4 THEN
v_brand := 'Dell';
ELSE
v_brand := 'Samsung';
END IF;
-- 拼接最终文本(包含编号、中文名称、中文描述、英文品牌,特意加入"和服装"用于测试分词差异)
v_content := '序号' || v_i || ',' || v_name || ',' || v_desc || ',品牌英文:' || v_brand;
-- 每1000条插入一条特殊的"和服装"测试数据,用于验证分词边界
IF MOD(v_i, 1000) = 0 THEN
v_content := v_content || ',备注:本批次包含玩具和服装类周边产品。';
END IF;
INSERT INTO ft_test(id, content, category, create_time)
VALUES(v_i, v_content, 'CAT_' || LPAD(MOD(v_i, 20), 2, '0'), SYSDATE);
v_i := v_i + 1;
-- 每1000条提交一次事务,避免回滚段过大
IF MOD(v_i, 1000) = 0 THEN
COMMIT;
END IF;
END LOOP;
COMMIT;
END;
/
-- 验证数据量
SELECT COUNT(*) AS total_records FROM ft_test;
-- 预期输出:100000
分词器特性说明
达梦数据库默认分词器。对英文按空格、标点符号和停用词(如 is、are、the)进行切分;对中文采用 最少分词 策略(即消除歧义,优先切出最大语义单元),索引体积适中。典型切分示例:"中华人民共和国" → "中华"、"人民"、"共和国"。
-- 确保没有残留的其他全文索引(同一列只能有一个全文索引)
DROP CONTEXT INDEX IF EXISTS idx_ft_default ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_english ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_chinese ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_vgram ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_fp ON ft_test;
-- 创建当前测试索引
CREATE CONTEXT INDEX idx_ft_default ON ft_test(content)
LEXER DEFAULT_"LEXER" SYNC;
-- 强制全量重建,确保所有10万条数据完成索引填充
ALTER CONTEXT INDEX idx_ft_default ON ft_test REBUILD;
-- 查看索引是否创建成功
SELECT * FROM CTISYS.SYSCONTEXTINDEXES WHERE NAME = 'IDX_FT_DEFAULT';
-- 验证1:搜索中文词汇"华为"(预期大量命中,因为 DEFAULT 支持中文分词)
SELECT COUNT(*) AS default_hit_huawei FROM ft_test WHERE CONTAINS(content, '华为');
-- 验证2:搜索英文词汇"Huawei"(预期命中所有品牌含 Huawei 的记录)
SELECT COUNT(*) AS default_hit_english_brand FROM ft_test WHERE CONTAINS(content, 'Huawei');
-- 验证3:搜索"和服"
-- 由于 DEFAULT_"LEXER" 对"和服装"切分为"和"+"服装",搜索"和服"时,
-- 实际上不会匹配包含"和"且包含"服装"的记录,导致无结果。
SELECT COUNT(*) AS default_hit_hefu FROM ft_test WHERE CONTAINS(content, '和服');
原因分析:DEFAULT_"LEXER"(中文最少分词)采用词典匹配 + 最长匹配策略。当处理"玩具和服装类周边产品"时,分词器会查找词典中最长的匹配词条。若词典中存在"服装类",则会优先将"服装类"作为一个整体切出,而不会再切出"服装"。因此索引中只有"服装类",没有"服装"。
查看分词函数的实际输出(确认分词结果)
-- 创建查看分词的函数(需在 SYSDBA 或拥有相应权限的用户下执行)
CREATE OR REPLACE FUNCTION F_GET_CTI_TOKEN(V_INPUT VARCHAR2) RETURN VARCHAR2 IS
PRAGMA AUTONOMOUS_TRANSACTION;
V_TMP_TABNAME VARCHAR2(128);
V_RET VARCHAR2(32767);
BEGIN
V_TMP_TABNAME := 'T_'||SYS_GUID;
EXECUTE IMMEDIATE 'CREATE TABLE '||V_TMP_TABNAME||'(VAL VARCHAR2(4000))';
EXECUTE IMMEDIATE 'INSERT INTO '||V_TMP_TABNAME||' VALUES(?)' USING V_INPUT;
EXECUTE IMMEDIATE 'COMMIT';
EXECUTE IMMEDIATE 'CREATE CONTEXT INDEX CTI_'||V_TMP_TABNAME||' ON '||V_TMP_TABNAME||' (VAL) LEXER CHINESE_"LEXER" SYNC';
EXECUTE IMMEDIATE 'SELECT LISTAGG(WORD,'','') WITHIN GROUP (ORDER BY ROWID) FROM CTI$CTI_'||V_TMP_TABNAME||'$I' INTO V_RET;
EXECUTE IMMEDIATE 'DROP TABLE '||V_TMP_TABNAME||' CASCADE PURGE';
RETURN V_RET;
END;
/
-- 测试函数
SELECT F_GET_CTI_TOKEN('玩具和服装类周边产品') FROM DUAL;
可见分词结果为"服装类"、"和"等,因此搜索"和服"无法命中。
-- 验证4:搜索"性能"(最少分词不会切出"能性"等噪声)
SELECT COUNT(*) AS default_hit_xingneng FROM ft_test WHERE CONTAINS(content, '性能');
-- 业务场景:运营人员要求精确找出商品描述中连续出现"和服"两个字的记录(例如"和服周边")。
-- 但 DEFAULT_"LEXER" 将"玩具和服装"切分为"玩具"、"和"、"服装类"。
-- 方式A:LIKE 精确匹配(业务基准,结果集为A)
SELECT id, content FROM ft_test WHERE content LIKE '%和服%';
-- 此结果集只包含真正连续写出"和服"的记录。
-- 方式B:纯全文检索(结果集为B)
SELECT id, content FROM ft_test WHERE CONTAINS(content, '和服');
由于"玩具和服装类"被分词为"玩具"、"服装类"、"和",并不存在"和服"词条,因此搜索"和服"无法命中,导致结果集 B 为空。
-- 计算差异数量(误召回数)——此处实际为漏检数,因为 CONTAINS 返回0,LIKE 返回100
SELECT COUNT(*) AS false_negative_count
FROM ft_test
WHERE content LIKE '%服装%' -- 业务基准:实际存在的记录
AND NOT CONTAINS(content, '服装'); -- 全文索引遗漏的记录
-- 该数值即为纯全文索引带来的漏检数据量。
-- 对比验证:查看 CONTAINS 与 LIKE 的结果集规模差异
-- 实际输出:CONTAINS = 0, LIKE = 100
-- 利用全文索引快速缩小扫描范围(走索引),再用 LIKE 进行精确字符串匹配(去除噪声)
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '服装类') -- 走索引,实测命中 100 条
AND content LIKE '%服装类%' -- LIKE 二次校验
AND category = 'CAT_00'; -- 其他业务过滤条件
-- 测试1:纯 LIKE 模糊查询(全表扫描,基准线)
SELECT COUNT(*) FROM ft_test WHERE content LIKE '%智能手机%';
-- 执行时间:64 毫秒,返回行数:10000
-- 测试2:纯全文索引查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '智能手机');
-- 执行时间:14 毫秒,返回行数:10000
-- 测试3:多关键词 AND 布尔查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '华为 AND 笔记本');
-- 执行时间:1 毫秒,返回行数:0
-- 测试4:多关键词 OR 布尔查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '华为 OR 小米');
-- 执行时间:1 毫秒,返回行数:0
-- 测试5:组合查询(CONTAINS + LIKE)
SELECT COUNT(*) FROM ft_test
WHERE CONTAINS(content, '处理器')
AND content LIKE '%处理器%';
-- 执行时间:40 毫秒,返回行数:10000
| 测试编号 | 查询方式 | SQL 示例 | 执行时间 (ms) | 返回行数 | 适用场景说明 |
|---|---|---|---|---|---|
| 1 | 纯 LIKE 模糊查询(全表扫描) | LIKE '%智能手机%' |
64 | 10000 | 全表扫描,无索引可用,适合数据量极小或一次性查询的场景。 |
| 2 | 纯 CONTAINS 全文检索(单关键词) | CONTAINS(content, '智能手机') |
14 | 10000 | 走全文索引,最适合高频单关键词搜索,性能最优。 |
| 3 | 组合查询(CONTAINS + LIKE) | CONTAINS(content, '处理器') AND LIKE '%处理器%' |
40 | 10000 | CONTAINS 走索引快速圈定,LIKE 做二次精确校验。精度最高,适合对准确性有极致要求的场景。 |
| 维度 | 结论 |
|---|---|
| 性能排名 | CONTAINS 布尔查询(AND/OR) > 单关键词 CONTAINS > CONTAINS + LIKE 组合 > 纯 LIKE |
| 最快查询 | 布尔查询(AND/OR)仅需 1 ms,因为索引内部直接完成集合运算,无需回表扫描。 |
| 精度最高 | CONTAINS + LIKE 组合,用 LIKE 二次校验可剔除分词带来的噪声(但本例中返回行数仍为 10000,说明无噪声)。 |
| 最慢查询 | 纯 LIKE 需 64 ms,因为必须全表扫描(10万行),是 CONTAINS 的 4~6 倍。 |
| 单关键词场景 | 纯 CONTAINS 是最佳选择(14 ms),精度足够且性能优异,无需追加 LIKE 拖慢速度。 |
| 组合查询代价 | CONTAINS + LIKE(40 ms)比纯 CONTAINS(14 ms)慢约 2.9 倍,需在精度与性能间权衡。 |
分词器特性说明
专为英文语种优化。按空格、标点、数字进行切分,自动过滤英文停用词(如 the、a、an)。对中文字符串不进行内部切分,将连续的多个中文字符视为一个完整的 Token。因此中文搜索能力极弱,仅能匹配完全一致的中文连续字符串。
-- 创建纯英文测试表
DROP TABLE IF EXISTS ft_test_en CASCADE;
CREATE TABLE ft_test_en (
id INT PRIMARY KEY,
title VARCHAR(500),
content VARCHAR(4000),
category VARCHAR(50),
create_time DATETIME
);
-- 生成10万条纯英文测试数据(与用例1完全一致,此处省略)
DECLARE
v_i INT := 1;
v_title VARCHAR(200);
v_content VARCHAR(4000);
v_brand VARCHAR(50);
v_category VARCHAR(20);
BEGIN
...
END LOOP;
COMMIT;
END;
/
-- 验证数据量
SELECT COUNT(*) AS total_records FROM ft_test_en;
-- 预期:100000
-- 清理旧索引,索引详细方法可参考DM管理员手册18.3 全文索引
DROP CONTEXT INDEX IF EXISTS idx_ft_en ON ft_test_en;
-- 创建英文全文索引,索引详细方法可参考DM管理员手册18.3 全文索引
CREATE CONTEXT INDEX idx_ft_en ON ft_test_en(content)
LEXER ENGLISH_"LEXER" SYNC TRANSACTION;
-- 【关键】强制全量重建,索引详细方法可参考DM管理员手册18.3 全文索引
ALTER CONTEXT INDEX idx_ft_en ON ft_test_en REBUILD;
COMMIT;
-- 验证辅助表是否有数据
SELECT COUNT(*) AS token_count FROM CTI$IDX_FT_EN$I;
-- 预期:> 0(纯英文文本分词后词条数通常远大于记录数)
-- 验证1:搜索完整英文单词(预期大量命中)
SELECT COUNT(*) AS hit_laptop FROM ft_test_en WHERE CONTAINS(content, 'laptop');
-- 预期:约 10000(每10条有1条包含 Laptop)
SELECT COUNT(*) AS hit_processor FROM ft_test_en WHERE CONTAINS(content, 'processor');
-- 预期:约 10000
SELECT COUNT(*) AS hit_design FROM ft_test_en WHERE CONTAINS(content, 'design');
-- 预期:约 40000(多个描述中都有 design)
-- 验证2:搜索品牌名(精确匹配)
SELECT COUNT(*) AS hit_huawei FROM ft_test_en WHERE CONTAINS(content, 'Huawei');
-- 预期:约 23333
SELECT COUNT(*) AS hit_apple FROM ft_test_en WHERE CONTAINS(content, 'Apple');
-- 预期:约 23333
-- 验证3:【英文分词器核心特性】大小写不敏感测试
SELECT COUNT(*) AS hit_lower FROM ft_test_en WHERE CONTAINS(content, 'huawei');
SELECT COUNT(*) AS hit_upper FROM ft_test_en WHERE CONTAINS(content, 'HUAWEI');
SELECT COUNT(*) AS hit_mixed FROM ft_test_en WHERE CONTAINS(content, 'HuaWei');
-- 预期:三者结果相同(均为 ~23333),证明 ENGLISH_"LEXER" 不区分大小写
-- 验证4:标点符号分割测试
-- content 中包含 "Product 1. Brand: Huawei."
-- 搜索 "Product" 应能命中所有记录
SELECT COUNT(*) AS hit_product FROM ft_test_en WHERE CONTAINS(content, 'Product');
-- 预期:100000(所有记录都包含 Product)
-- 搜索 "Brand" 应能命中所有记录
SELECT COUNT(*) AS hit_brand FROM ft_test_en WHERE CONTAINS(content, 'Brand');
-- 预期:100000
-- 验证5:短语匹配(多个连续单词)
SELECT COUNT(*) AS hit_high_performance FROM ft_test_en
WHERE CONTAINS(content, 'high-performance');
-- 预期:约 20000(描述中含有 high-performance 的记录)
SELECT COUNT(*) AS hit_business_travel FROM ft_test_en
WHERE CONTAINS(content, 'business travel');
-- 预期:约 20000
-- 验证6:对比 LIKE 验证(结果应一致)
SELECT
(SELECT COUNT(*) FROM ft_test_en WHERE CONTAINS(content, 'Huawei')) AS contains_result,
(SELECT COUNT(*) FROM ft_test_en WHERE content LIKE '%Huawei%') AS like_result;
-- 预期:两者相等(纯英文场景下,ENGLISH_"LEXER" 不会漏检)
-- 场景1:搜索 "performance"
SELECT
(SELECT COUNT(*) FROM ft_test_en WHERE CONTAINS(content, 'performance')) AS contains_cnt,
(SELECT COUNT(*) FROM ft_test_en WHERE content LIKE '%performance%') AS like_cnt;
-- 预期:两者相等
-- 场景2:搜索 "gaming"
SELECT
(SELECT COUNT(*) FROM ft_test_en WHERE CONTAINS(content, 'gaming')) AS contains_cnt,
(SELECT COUNT(*) FROM ft_test_en WHERE content LIKE '%gaming%') AS like_cnt;
-- 预期:两者相等
-- 场景3:搜索 "video editing"(短语)
SELECT
(SELECT COUNT(*) FROM ft_test_en WHERE CONTAINS(content, 'video editing')) AS contains_cnt,
(SELECT COUNT(*) FROM ft_test_en WHERE content LIKE '%video editing%') AS like_cnt;
-- 预期:两者相等
-- 计算差异(应为0)
SELECT COUNT(*) AS diff_count
FROM ft_test_en
WHERE content LIKE '%Xiaomi%'
AND NOT CONTAINS(content, 'Xiaomi');
-- 预期:0(纯英文场景下无漏检)
执行方式:每条 SQL 在管理工具中单独执行,查看"消息"选项卡中的执行时间。
-- 测试1:纯 LIKE 模糊查询(全表扫描,基准线)
SELECT COUNT(*) FROM ft_test_en WHERE content LIKE '%smartphone%';
-- 执行时间:74 毫秒,返回行数:0
-- 测试2:纯 CONTAINS 全文检索
SELECT COUNT(*) FROM ft_test_en WHERE CONTAINS(content, 'smartphone');
-- 执行时间:15 毫秒,返回行数:10000
-- 测试3:多关键词 AND 布尔查询
SELECT COUNT(*) FROM ft_test_en
WHERE CONTAINS(content, 'Huawei AND laptop');
-- 执行时间:20 毫秒,返回行数:10000
-- 测试4:多关键词 OR 布尔查询
SELECT COUNT(*) FROM ft_test_en
WHERE CONTAINS(content, 'Apple OR Samsung');
-- 执行时间:1 毫秒,返回行数:0
-- 测试5:短语匹配
SELECT COUNT(*) FROM ft_test_en
WHERE CONTAINS(content, 'high refresh rate');
-- 执行时间:29 毫秒,返回行数:20000
-- 测试6:组合查询(CONTAINS + LIKE,纯英文场景下结果应一致)
SELECT COUNT(*) FROM ft_test_en
WHERE CONTAINS(content, 'Processor')
AND content LIKE '%Processor%';
-- 执行时间:31 毫秒,返回行数:10000
| 测试编号 | 查询方式 | SQL 示例 | 执行时间 (ms) | 返回行数 | 适用场景说明 |
|---|---|---|---|---|---|
| 1 | 纯 LIKE 模糊查询(全表扫描) | LIKE '%smartphone%' |
74 | 0 | 全表扫描,无索引可用。返回 0 是因为数据中确实无 "smartphone" 这个词(标题为 "Xiaomi 14 Ultra Smartphone",但测试脚本使用的数据中可能因大小写或词形差异导致未命中,实际业务中 LIKE 为基准线)。 |
| 2 | 纯 CONTAINS 全文检索(单关键词) | CONTAINS(content, 'smartphone') |
15 | 10000 | 走全文索引,最适合高频单关键词搜索。返回 10000 条,说明 "Smartphone" 作为独立词条被正确索引且大小写不敏感。 |
| 3 | 组合查询(CONTAINS + LIKE) | CONTAINS(content, 'Processor') AND LIKE '%Processor%' |
31 | 10000 | CONTAINS 走索引快速圈定,LIKE 做二次精确校验。在纯英文场景下,此写法与纯 CONTAINS 结果完全一致(均为 10000 条),LIKE 仅作为"心理保险",会增加约 16 ms 的额外开销。 |
| 维度 | 结论 |
|---|---|
| 性能排名 | CONTAINS OR 布尔查询(1 ms) > CONTAINS 单关键词(15 ms) > CONTAINS AND(20 ms) > 短语匹配(29 ms) > CONTAINS + LIKE 组合(31 ms) > 纯 LIKE(74 ms) |
| 最快查询 | OR 布尔查询仅需 1 ms,索引内部直接完成并集运算,效率极高。 |
| 最慢查询 | 纯 LIKE 需 74 ms(全表扫描),是 CONTAINS 的 5 倍左右。 |
| 纯英文场景核心结论 | CONTAINS 与 LIKE 结果完全一致(测试2与测试6均为 10000 条),因此无需追加 LIKE 兜底,直接使用 CONTAINS 即可同时保证性能与准确性。 |
| 短语匹配代价 | 短语匹配(29 ms)比单关键词(15 ms)慢约 2 倍,因为需要验证词序和相邻位置,适合对词序敏感的场景(如产品型号、专有名词)。 |
| 组合查询代价 | CONTAINS + LIKE(31 ms)比纯 CONTAINS(15 ms)慢约 2 倍,额外开销来自 LIKE 的二次字符串扫描,在纯英文场景下可省略。 |
-- 查看索引状态
SELECT NAME, ID, TABLEID FROM CTISYS.SYSCONTEXTINDEXES WHERE NAME = 'IDX_FT_EN';
-- 增量更新,索引更新详细方法可参考DM管理员手册18.3 全文索引
ALTER CONTEXT INDEX idx_ft_en ON ft_test_en INCREMENT;
-- 完全重建,索引更新详细方法可参考DM管理员手册18.3 全文索引
ALTER CONTEXT INDEX idx_ft_en ON ft_test_en REBUILD;
-- 删除索引,索引删除详细方法可参考DM管理员手册18.3 全文索引
DROP CONTEXT INDEX IF EXISTS idx_ft_en ON ft_test_en;
-- 搜索包含 "laptop" 的商品
SELECT id, title, content
FROM ft_test_en
WHERE CONTAINS(content, 'laptop')
AND category = 'CAT_01' -- 利用普通索引辅助过滤
ORDER BY create_time DESC
LIMIT 20; -- 分页限制,避免结果集过大
SELECT id, title, content
FROM ft_test_en
WHERE CONTAINS(content, 'Huawei AND laptop')
AND category IN ('CAT_01', 'CAT_02');
SELECT id, title, content
FROM ft_test_en
WHERE CONTAINS(content, 'Apple OR Samsung');
SELECT id, title, content
FROM ft_test_en
WHERE CONTAINS(content, '"high refresh rate"');
-- 【可选】如果对准确性有极致要求,可追加 LIKE 二次校验
-- 在纯英文场景下,此写法与纯 CONTAINS 结果完全相同,LIKE 仅作为"心理保险"。
-- 建议仅在关键业务(如金融、法律)中使用。
SELECT id, title, content
FROM ft_test_en
WHERE CONTAINS(content, 'Processor')
AND content LIKE '%Processor%' -- 二次校验(可选)
AND category = 'CAT_01';
| 查询方式 | SQL 示例 | 预期耗时 | 返回行数 | 结果准确性 |
|---|---|---|---|---|
| 纯 LIKE(全表扫描) | LIKE '%laptop%' |
60 ms | ~10000 | ✅ 100% |
| 纯 CONTAINS(走索引) | CONTAINS(content, 'laptop') |
5~15 ms | ~10000 | ✅ 100% |
| 组合 CONTAINS + LIKE | CONTAINS + LIKE |
40 ms | ~10000 | ✅ 100% |
结论:在纯英文场景下,直接使用
CONTAINS即可,无需追加LIKE兜底。组合查询仅在业务对准确性有极端要求(如法律文书检索)时使用,但会带来约 2~3 倍的性能损耗。
SYNC TRANSACTION 保证事务内实时同步;若批量导入,建议使用 SYNC 并定期执行 INCREMENT 增量更新。ENGLISH_"LEXER" 会自动过滤英文停用词(如 the、a、an、is、are 等)。搜索这些词会返回 0,属于正常行为。N-Acetylglucosamine),需注意分词完整性。分词器特性说明
采用 二元切分法(Bi-gram),将任意相邻的两个汉字切分为一个词条。例如"中华人民共和国"切分为:"中华"、"华人"、"人民"、"民共"、"共和"、"和国"。索引体积较大(约为 CHINESE_"LEXER" 的 1.5~2 倍),召回率极高(只要连续两个字匹配就能命中),但 噪声也极高(会切出大量无意义组合如"民共")。
-- 若 ft_test 表已存在则直接使用,否则执行以下建表及数据生成语句
DROP TABLE IF EXISTS ft_test CASCADE;
CREATE TABLE ft_test (
id INT PRIMARY KEY,
content VARCHAR(4000),
category VARCHAR(50),
create_time DATETIME
);
-- 生成10万条中英文混合测试数据(与用例1完全一致,此处省略)
DECLARE
v_i INT := 1;
v_content VARCHAR(4000);
v_name VARCHAR(100);
v_desc VARCHAR(200);
v_brand VARCHAR(50);...
END LOOP;
COMMIT;
END;
/
-- 验证数据量
SELECT COUNT(*) AS total_records FROM ft_test;
-- 预期:100000
-- 清理所有旧索引
DROP CONTEXT INDEX IF EXISTS idx_ft_default ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_english ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_chinese ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_vgram ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_fp ON ft_test;
-- 创建 VGRAM 索引
CREATE CONTEXT INDEX idx_ft_vgram ON ft_test(content)
LEXER CHINESE_VGRAM_"LEXER" SYNC TRANSACTION;
-- 【关键】强制全量重建
ALTER CONTEXT INDEX idx_ft_vgram ON ft_test REBUILD;
COMMIT;
-- 验证辅助表数据
SELECT COUNT(*) AS token_count FROM CTI$IDX_FT_VGRAM$I;
-- 预期:远大于 0(VGRAM 词条数通常远多于 DEFAULT_"LEXER")
-- 参考:DEFAULT_"LEXER" 为 129809 条,VGRAM 预计 30万+
-- 5.1 【核心差异】搜索"和服"
-- VGRAM 将"玩具和服装"切分为"玩具"、"具和"、"和服"、"服装"
SELECT COUNT(*) AS vgram_hit_hefu FROM ft_test WHERE CONTAINS(content, '和服');
-- 预期:100(命中那100条包含"玩具和服装"的记录)
-- 对比:DEFAULT_"LEXER" 下此查询返回 0
-- 查看具体命中记录(验证是否包含"玩具和服装")
SELECT id, content FROM ft_test
WHERE CONTAINS(content, '和服') AND ROWNUM <= 5;
-- 5.2 搜索"共"(无意义组合,但 VGRAM 会切出)
SELECT COUNT(*) AS vgram_hit_gong FROM ft_test WHERE CONTAINS(content, '共');
-- 预期:> 0(所有包含"共和国"的记录都能命中)
-- 对比:DEFAULT_"LEXER" 下此查询返回 0
-- 5.3 搜索"华人"(从"中华人民共和国"切出)
SELECT COUNT(*) AS vgram_hit_huaren FROM ft_test WHERE CONTAINS(content, '华人');
-- 预期:> 0
-- 5.4 搜索"华为"(独立词条,应大量命中)
SELECT COUNT(*) AS vgram_hit_huawei FROM ft_test WHERE CONTAINS(content, '华为');
-- 预期:约 10000(与 DEFAULT_"LEXER" 一致)
-- 5.5 搜索"服装"(VGRAM 从"服装类"中切出"服装")
SELECT COUNT(*) AS vgram_hit_fuzhuang FROM ft_test WHERE CONTAINS(content, '服装');
-- 预期:100(命中那100条包含"玩具和服装"的记录)
-- 6.1 验证技术等价性(CONTAINS 与 LIKE 结果应完全一致)
SELECT
(SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '和服')) AS contains_result,
(SELECT COUNT(*) FROM ft_test WHERE content LIKE '%和服%') AS like_result;
-- 预期:100 vs 100(完全相等)
-- 6.2 验证差异数量(应为0)
SELECT COUNT(*) AS tech_diff_count
FROM ft_test
WHERE CONTAINS(content, '和服')
AND content NOT LIKE '%和服%';
-- 预期:0(因为"玩具和服装"本身包含连续子串"和服")
-- 6.3 业务误召回数量识别
-- 统计命中了"和服"但实际是"玩具和服装"的记录数
SELECT
COUNT(*) AS total_hit,
COUNT(CASE WHEN content LIKE '%玩具%' THEN 1 END) AS business_false_positive
FROM ft_test
WHERE CONTAINS(content, '和服');
-- 预期:total_hit = 100, business_false_positive = 100
-- 说明:命中的 100 条记录全部来自"玩具和服装",并非真正的"和服"商品
-- 查看具体的业务误召回记录(前5条)
SELECT id, content
FROM ft_test
WHERE CONTAINS(content, '和服')
AND content LIKE '%玩具%' -- 业务规则:包含"玩具"的"和服"属于误召回
AND ROWNUM <= 5;
-- 剔除业务噪声后的有效结果(若业务要求排除"玩具"类)
SELECT COUNT(*) AS valid_result
FROM ft_test
WHERE CONTAINS(content, '和服')
AND content NOT LIKE '%玩具%'; -- 排除包含"玩具"的记录
-- 预期:0(因为数据中没有真正的"和服"商品)
-- 6.4 漏检分析:VGRAM 几乎不漏检
SELECT
(SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '服装')) AS contains_result,
(SELECT COUNT(*) FROM ft_test WHERE content LIKE '%服装%') AS like_result;
-- 两者相等(均为100),证明 VGRAM 无漏检
-- 计算漏检数量(False Negative)——应为0
SELECT COUNT(*) AS false_negative_count
FROM ft_test
WHERE content LIKE '%服装%'
AND NOT CONTAINS(content, '服装');
-- 预期:0
-- 7.1 搜索"和服"
SELECT 'VGRAM' AS lexer, COUNT(*) AS result_count
FROM ft_test WHERE CONTAINS(content, '和服')
UNION ALL
SELECT 'DEFAULT' AS lexer, 0 FROM DUAL;
-- VGRAM: 100, DEFAULT: 0
-- 7.2 搜索"服装"
SELECT 'VGRAM' AS lexer, COUNT(*) AS result_count
FROM ft_test WHERE CONTAINS(content, '服装')
UNION ALL
SELECT 'DEFAULT' AS lexer, 0 FROM DUAL;
-- VGRAM: 100, DEFAULT: 0
-- 7.3 搜索"民共"
SELECT 'VGRAM' AS lexer, COUNT(*) AS result_count
FROM ft_test WHERE CONTAINS(content, '民共')
UNION ALL
SELECT 'DEFAULT' AS lexer, 0 FROM DUAL;
-- VGRAM: > 0, DEFAULT: 0
执行方式:每条 SQL 在管理工具中单独执行,查看"消息"选项卡中的执行时间。
-- 测试1:纯 LIKE 模糊查询(全表扫描,基准线)
SELECT COUNT(*) FROM ft_test WHERE content LIKE '%智能手机%';
-- 执行时间:63 毫秒,返回行数:10000
-- 测试2:纯 CONTAINS 全文检索(单关键词)
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '智能手机');
-- 执行时间:14 毫秒,返回行数:10000
-- 测试3:搜索"和服"(VGRAM 特有命中)
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '智能手机');
-- 执行时间:1 毫秒,返回行数:100
-- 测试4:多关键词 AND 布尔查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '华为 AND 笔记本');
-- 执行时间:1 毫秒,返回行数:0
-- 测试5:多关键词 OR 布尔查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '华为 OR 小米');
-- 执行时间:1 毫秒,返回行数:0
-- 测试6:组合查询(CONTAINS + LIKE,去除 VGRAM 噪声)
SELECT COUNT(*) FROM ft_test
WHERE CONTAINS(content, '智能手机')
AND content LIKE '%智能手机%';
-- 执行时间:1 毫秒,返回行数:100(应为100,因为数据中无真实"和服",但LIKE会进一步过滤)
| 序号 | 查询场景 | SQL | 执行时间(ms) | 返回行数 |
|---|---|---|---|---|
| 1 | LIKE 全表扫描 | LIKE '%智能手机%' |
63 | 10000 |
| 2 | CONTAINS + LIKE | CONTAINS(content, '和服') AND LIKE '%智能手机%' |
1 | 100 |
由于 CHINESE_VGRAM_"LEXER" 召回率极高但噪声也高,生产环境必须配合 LIKE 进行二次精确过滤。
-- 用户搜索"和服",但业务要求排除"玩具和服装"这类噪声
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '和服') -- VGRAM 索引快速圈定(100条)
AND content NOT LIKE '%玩具%' -- 业务规则:排除"玩具"相关
AND category = 'CAT_01'
AND ROWNUM <= 100;
-- 结果:返回 0(因为数据中没有真正的"和服"商品)
-- 若业务允许"玩具和服装"被搜到(高召回优先),则直接使用:
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '和服')
AND ROWNUM <= 100;
-- 结果:返回 100 条(包含"玩具和服装")
-- 查看索引状态
SELECT NAME, ID, TABLEID FROM CTISYS.SYSCONTEXTINDEXES WHERE NAME = 'IDX_FT_VGRAM';
-- 查看辅助表记录数
SELECT COUNT(*) AS token_count FROM CTI$IDX_FT_VGRAM$I;
-- 增量更新,索引更新详细方法可参考DM管理员手册18.3 全文索引
ALTER CONTEXT INDEX idx_ft_vgram ON ft_test INCREMENT;
-- 完全重建,索引更新详细方法可参考DM管理员手册18.3 全文索引
ALTER CONTEXT INDEX idx_ft_vgram ON ft_test REBUILD;
-- 删除索引,索引删除详细方法可参考DM管理员手册18.3 全文索引
DROP CONTEXT INDEX IF EXISTS idx_ft_vgram ON ft_test;
| 对比维度 | CHINESE_VGRAM_"LEXER" | DEFAULT_"LEXER"(参照) |
|---|---|---|
| 分词方式 | 强制双字切分(二元分割) | 基于词典的最长匹配 |
| 索引体积 | 大(词条数量多) | 中等(129,809条) |
| 召回率 | 极高(几乎不漏检) | 低("服装"查不到"服装类") |
| 噪声率 | 高("和服"误召回"玩具和服装") | 低 |
| 非常用词汇 | ✅ 能检索 | ❌ 依赖词典,非常用词查不到 |
| 生产推荐写法 | 必须 CONTAINS + LIKE | 视场景而定 |
| 推荐场景 | 电商搜索、知识库、全文检索 | 精确匹配、词典词检索 |
最终建议:
CHINESE_VGRAM_"LEXER",但 必须配合 LIKE 进行二次精确过滤,以去除"玩具和服装"被"和服"误召回这类噪声。CONTAINS(不加 LIKE),因为 VGRAM 的噪声可能导致搜索结果包含大量不相关内容。CHINESE_"LEXER" 的 1.5~2 倍,建议在创建前评估存储空间。CONTAINS(content, '服装') = 0(漏检)CONTAINS(content, '服装') = 100(命中)CONTAINS(content, '和服') = 0(无命中)CONTAINS(content, '和服') = 100(含误召回"玩具和服装")分词器特性说明
采用 最多分词 策略,保留所有可能出现的词组组合。例如"中华人民共和国"切分为:"中"、"中华"、"华人"、"人民"、"民共"、"共和"、"和国"、"国"等所有子串。索引体积 最大(约为 CHINESE_"LEXER" 的 2~3 倍),召回率最高(几乎不漏检),但 噪声也最高(单字都能匹配)。
-- 清理旧索引
DROP CONTEXT INDEX IF EXISTS idx_ft_default ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_english ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_chinese ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_vgram ON ft_test;
DROP CONTEXT INDEX IF EXISTS idx_ft_fp ON ft_test;
-- 创建 FP 索引
CREATE CONTEXT INDEX idx_ft_fp ON ft_test(content)
LEXER CHINESE_FP_LEXER SYNC TRANSACTION;
--全文索引详细方法可参考DM管理员手册18.3 全文索引
-- 强制全量重建
ALTER CONTEXT INDEX idx_ft_fp ON ft_test REBUILD;
COMMIT;
--索引更新详细方法可参考DM管理员手册18.3 更新全文索引
-- 验证辅助表数据(预期词条数远大于 VGRAM)
SELECT COUNT(*) AS token_count FROM CTI$IDX_FT_FP$I;
-- 4.1 搜索"和服"(分词中存在,应命中100条)
SELECT COUNT(*) AS fp_hit_hefu FROM ft_test WHERE CONTAINS(content, '和服');
-- 预期:100(命中"玩具和服装")
-- 4.2 搜索"服装"(分词中存在,应命中100条)
SELECT COUNT(*) AS fp_hit_fuzhuang FROM ft_test WHERE CONTAINS(content, '服装');
-- 预期:100
-- 4.3 搜索"服装类"(分词中存在,应命中100条)
SELECT COUNT(*) AS fp_hit_fuzhuanglei FROM ft_test WHERE CONTAINS(content, '服装类');
-- 预期:100
-- 4.4 搜索单字"玩"(分词中存在,预期命中包含"玩具"的所有记录,即100条)
SELECT COUNT(*) AS fp_hit_wan FROM ft_test WHERE CONTAINS(content, '玩');
-- 预期:100(仅命中那100条"玩具"记录)
-- 4.5 搜索单字"产"(命中所有包含"产品"的记录,数量极大)
SELECT COUNT(*) AS fp_hit_chan FROM ft_test WHERE CONTAINS(content, '产');
-- 预期:20100(几乎所有记录都包含"产品")
-- 4.6 搜索"和"(命中包含"和"的所有记录,接近全表)
SELECT COUNT(*) AS fp_hit_he FROM ft_test WHERE CONTAINS(content, '和');
-- 预期:约100000(大部分记录都有"和"字)
基于分词结果,FP 与 LIKE 在匹配 连续字符串 上完全等价,但 FP 会召回大量“近义词”,这些“近义词”不是实际业务需要的,实际使用时利用FP进行快速检索,LIKE进行精准定位。
-- 搜索"和服":FP 和 LIKE 均命中 100 条
SELECT
(SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '和服')) AS contains_result,
(SELECT COUNT(*) FROM ft_test WHERE content LIKE '%和服%') AS like_result;
-- 预期:100 vs 100
-- 搜索"服装":FP 和 LIKE 均命中 100 条
SELECT
(SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '服装')) AS contains_result,
(SELECT COUNT(*) FROM ft_test WHERE content LIKE '%服装%') AS like_result;
-- 预期:100 vs 100
FP 分词包含"和服",因此 CONTAINS('和服') 会命中"玩具和服装"。这些命中记录 技术上 包含连续"和服",但 业务上 不是真正的"和服"商品,属于业务噪声。
-- 统计"和服"命中的记录中,哪些是"玩具和服装"(业务噪声)
SELECT
COUNT(*) AS total_hit,
COUNT(CASE WHEN content LIKE '%玩具%' THEN 1 END) AS noise_count
FROM ft_test
WHERE CONTAINS(content, '和服');
-- 预期:total_hit = 100, noise_count = 100(全部是噪声)
-- 查看具体噪声记录
SELECT id, content FROM ft_test
WHERE CONTAINS(content, '和服') AND content LIKE '%玩具%' AND ROWNUM <= 5;
由于 FP 切出了单字"产",搜索"产"会命中几乎所有记录,这是 FP 最大的噪声来源。
-- 搜索"产" vs LIKE '%产%',两者技术等价,但业务上"产"过于宽泛
SELECT
(SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '产')) AS contains_result,
(SELECT COUNT(*) FROM ft_test WHERE content LIKE '%产%') AS like_result;
-- 预期:20100 vs 20100(两者均命中全表,但业务期望可能只是"产品")
| 搜索词 | DEFAULT_"LEXER" | CHINESE_VGRAM_"LEXER" | CHINESE_FP_"LEXER" |
|---|---|---|---|
| 服装 | ❌ 0(漏检) | ✅ 100 | ✅ 100 |
| 和服 | ❌ 0(漏检) | ✅ 100 | ✅ 100 |
| 服装类 | ✅ 100 | ✅ 100(通过"服装"命中) | ✅ 100 |
| 单字"玩" | ❌ 0(未切出) | ❌ 0(VGRAM 未切单字) | ✅ 100(命中"玩具") |
| 单字"产" | ❌ 0 | ❌ 0 | ✅ 100000(命中全表) |
-- 搜索"和服",但排除"玩具"类噪声
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '和服') -- FP 索引快速圈定
AND content LIKE '%和服%' -- 技术校验(可选)
AND content NOT LIKE '%玩具%' -- 业务规则去噪
AND ROWNUM <= 100;
-- 搜索"周边产品"(FP 分词中存在完整词条)
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '周边产品') -- 直接走索引
AND ROWNUM <= 100;
-- 由于"周边产品"本身是完整词条,且业务含义清晰,一般无需加 LIKE
-- 不建议允许用户搜索单字(如"产"),否则会命中全表
-- 若业务必须支持,需强制添加其他过滤条件
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '产')
AND category = 'CAT_01' -- 利用普通索引缩小范围
AND ROWNUM <= 100;
SELECT id, content, category
FROM ft_test
WHERE CONTAINS(content, '华为 AND 笔记本')
AND content LIKE '%华为%'
AND content LIKE '%笔记本%'
AND ROWNUM <= 100;
执行方式:每条 SQL 在管理工具中单独执行,查看"消息"选项卡中的执行时间。
-- 测试1:纯 LIKE 模糊查询(全表扫描,基准线)
SELECT COUNT(*) FROM ft_test WHERE content LIKE '%智能手机%';
-- 执行时间:49 毫秒,返回行数:10000
-- 测试2:纯 CONTAINS 全文检索(单关键词)
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '智能手机');
-- 执行时间:16 毫秒,返回行数:10000
-- 测试3:搜索短词"和服"
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '和服');
-- 执行时间:3 毫秒,返回行数:100
-- 测试4:搜索单字"产"
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '产');
-- 执行时间:27 毫秒,返回行数:20100
-- 测试5:多关键词 AND 布尔查询
SELECT COUNT(*) FROM ft_test WHERE CONTAINS(content, '华为 AND 笔记本');
-- 执行时间:0 毫秒,返回行数:0
-- 测试6:组合查询(CONTAINS + LIKE,去除业务噪声)
SELECT COUNT(*) FROM ft_test
WHERE CONTAINS(content, '和服')
AND content NOT LIKE '%玩具%';
-- 执行时间:8 毫秒,返回行数:0(排除"玩具"后,真正的"和服"商品为0)
性能数据记录表:
| 查询场景 | SQL | 执行时间(ms) | 返回行数 |
|---|---|---|---|
| LIKE 全表扫描 | LIKE '%智能手机%' |
49 | 10000 |
| CONTAINS + LIKE | CONTAINS('和服') AND NOT LIKE '%玩具%' |
8 | 0 |
-- 查看索引状态
SELECT NAME, ID, TABLEID FROM CTISYS.SYSCONTEXTINDEXES WHERE NAME = 'IDX_FT_FP';
-- 增量更新
ALTER CONTEXT INDEX idx_ft_fp ON ft_test INCREMENT;
--索引更新详细方法可参考DM管理员手册18.3 全文索引
-- 完全重建
ALTER CONTEXT INDEX idx_ft_fp ON ft_test REBUILD;
--索引重建详细方法可参考DM管理员手册18.3 全文索引
-- 删除索引
DROP CONTEXT INDEX IF EXISTS idx_ft_fp ON ft_test;
--索引删除详细方法可参考DM管理员手册18.3 全文索引
| 对比维度 | CHINESE_FP_"LEXER" |
|---|---|
| 分词方式 | 最多分词(单字、双字、多字全切) |
| 索引体积 | 最大(词条数远超 VGRAM) |
| 召回率 | 最高(单字都能命中) |
| 噪声率 | 最高(单字查询可能命中全表) |
| 技术等价性 | 与 LIKE 完全等价(匹配连续字符串) |
| 生产推荐写法 | 必须 CONTAINS + LIKE(或业务规则)去噪,尤其是短词和单字 |
| 推荐场景 | 高召回优先、允许业务层面二次过滤的全文检索 |
最终建议:
NOT LIKE '%玩具%' 等业务规则,否则搜索结果充斥噪声。CONTAINS,因其业务含义明确,且分词中存在完整词条。基于上述四个测试用例的执行结果与分词特性分析,现对达梦 DM8 的四种全文分词器进行横向综合对比,并给出面向不同业务场景的选型建议。
| 对比维度 | DEFAULT_"LEXER"(默认分词) | ENGLISH_"LEXER"(英文分词) | CHINESE_VGRAM_"LEXER"(双字机械分词) | CHINESE_FP_"LEXER"(最多分词) |
|---|---|---|---|---|
| 分词策略 | 基于词典的最长匹配(最少分词) | 按空格、标点、数字切分,过滤停用词 | 强制二元切分(Bi-gram),相邻两字为一个词条 | 保留所有可能子串(单字、双字、多字全切) |
| 中文支持能力 | 良好,依赖内置词典覆盖度 | 极弱,将连续中文字符视为一个完整 Token | 极强,任意相邻两字均可匹配 | 最强,单字即可命中 |
| 英文支持能力 | 支持,按空格及标点切分 | 专为英文优化,支持大小写不敏感、停用词过滤 | 支持,但无特殊优化 | 支持,但无特殊优化 |
| 索引体积(10万条) | 中等(约 12.98 万词条) | 中等(与英文文本密度相关) | 较大(约 30 万+ 词条,约为 DEFAULT 的 1.5~2 倍) | 最大(约 50 万+ 词条,约为 DEFAULT 的 2~3 倍) |
| 召回率(查全率) | 中等,存在漏检风险(如“服装”查不到“服装类”) | 英文场景下召回准确;中文场景召回极低 | 极高,几乎不漏检(如“和服”可命中“玩具和服装”) | 最高,单字皆可命中,理论上绝不漏检 |
| 噪声率(误召回) | 低,极少产生无意义匹配 | 低 | 较高,会切出“民共”等无意义组合,短词搜索噪声明显 | 最高,单字搜索可能命中全表,噪声控制难度大 |
| CONTAINS 查询性能 | 毫秒级(1~15 ms) | 毫秒级(5~20 ms) | 毫秒级(1~14 ms) | 毫秒级(单字查询略慢,约 27 ms,其余 1~16 ms) |
| 与 LIKE 结果一致性 | 不一致(存在漏检) | 完全一致(纯英文场景) | 技术等价(连续子串一致),但业务噪声需过滤 | 技术等价(连续子串一致),但业务噪声需过滤 |
| 生产推荐写法 | 建议采用 CONTAINS + LIKE 双重校验,或根据业务容忍度决定 |
直接使用 CONTAINS,无需追加 LIKE(纯英文场景) |
必须采用 CONTAINS + LIKE,并通过业务规则(如 NOT LIKE '%玩具%')去噪 |
必须采用 CONTAINS + LIKE,并严格限制单字查询,配合业务规则强力去噪 |
| 索引维护成本 | 较低 | 较低 | 中等(索引体积大,重建耗时略长) | 较高(索引体积最大,重建和增量更新开销最大) |
| 业务场景 | 推荐分词器 | 理由与注意事项 |
|---|---|---|
| 通用电商商品搜索(中英文混合) | CHINESE_VGRAM_"LEXER" | 召回率优先,能覆盖用户输入的各类长短词组合。必须配合 LIKE 二次过滤及业务黑名单(如排除“玩具”类噪声),以平衡召回与精度。 |
| 知识库/文档全文检索(对查全率要求极高) | CHINESE_FP_"LEXER" | 绝不漏检,适合法律、专利、学术文献检索。必须在业务层限制单字查询,并对短词搜索结果进行后置过滤,同时需预留充足的索引存储空间。 |
| 纯英文内容搜索(如英文新闻、邮件) | ENGLISH_"LEXER" | 专为英文优化,性能与准确性俱佳,直接使用 CONTAINS 即可,无需额外 LIKE 兜底,开发维护成本最低。 |
| 精确词典匹配(如商品类目、标准术语) | DEFAULT_"LEXER" | 基于词典的最长匹配,噪声低,适合已知标准词汇的精确检索。若业务可接受少量漏检(如“服装”类目下不要求搜出“服装类”),则可直接使用 CONTAINS,否则追加 LIKE 提升精度。 |
| 用户评论/弹幕/短文本搜索(关键词短、口语化) | CHINESE_VGRAM_"LEXER" | 用户常输入“性能”、“跑分”等短词,VGRAM 能有效命中“高性能”等组合,召回效果优于 DEFAULT。务必结合 LIKE 去噪。 |
| 高并发实时搜索API(对响应时间要求极致) | DEFAULT_"LEXER" 或 ENGLISH_"LEXER" | 索引体积小,查询速度最快,且噪声可控。需接受其召回率上的局限,或通过缓存、预聚合等方式弥补。 |
[开始] │ ▼ 数据是否为纯英文? │ ├── 是 ──► 选用 ENGLISH_"LEXER"(直接 CONTAINS,最优解) │ └── 否(中英文混合或纯中文) │ ▼ 业务是否要求“零漏检”(100%召回)? │ ├── 是 ──► 选用 CHINESE_FP_"LEXER" │ (必须:限制单字 + CONTAINS + LIKE + 业务规则去噪) │ └── 否 │ ▼ 是否允许少量漏检,但要求噪声尽可能低? │ ├── 是 ──► 选用 DEFAULT_"LEXER" │ (建议:CONTAINS + LIKE 兜底,可选) │ └── 否(平衡型) │ ▼ 选用 CHINESE_VGRAM_"LEXER" (强制:CONTAINS + LIKE + 业务黑名单去噪)
没有绝对“最好”的分词器,只有“最合适”的分词器。四种分词器在召回率、精度、存储开销和维护成本上构成了一条连续的取舍光谱:
生产环境的黄金法则是“索引圈定 + 条件精筛”。对于 VGRAM 和 FP,CONTAINS 用于快速缩小数据范围(走索引),LIKE 及业务规则用于剔除分词带来的噪声,两者结合才能在保证性能的同时满足业务准确性要求。
索引体积和重建时间是不可忽略的运维成本。若数据量达到千万级或亿级,FP 和 VGRAM 的索引大小可能带来显著的存储和运维压力,建议在选型前使用本测试方案中的脚本在小规模数据上预估索引膨胀率。
建议在项目初期完成分词器选型,因不同分词器对应的索引结构不兼容,切换成本较高。可依据本测试方案,使用业务真实样本数据进行小规模验证后,再确定最终选型。
参考文献
[1] 武汉达梦数据库股份有限公司. DM8系统管理员手册[M]. 第18章 全文检索.
文章
阅读量
获赞
