本文解析 OceanBase 4.3 全文检索(FTS)模块中的内置默认分词器——空白分词解析器(space parser)。它位于 storage/fts/ 目录,以 PARSER_NAME_SPACE("space") 注册到系统,是全文索引建立与查询时最轻量的词元切分方案。与 ngram(固定长度滑动窗口)、ik(中文 IK 分词,需词典)、beng(基础英文分析器)相比,space parser 无需词典、无需滑动窗口,仅依据「词字符/非词字符」边界切分,适合以英文、数字为主的语料场景。
一、核心数据结构
空白分词器由两个类协同实现:
- ObSpaceFTParser:词元迭代器,继承
ObITokenIterator,负责逐词扫描全文内容。核心成员包括字符集指针cs_、扫描起点start_、当前位置next_、终点end_,以及初始化守卫is_inited_。 - ObWhiteSpaceFTParserDesc:插件描述符/工厂,继承
ObIFTParserDesc,提供segment()(创建迭代器)、free_token_iter()(释放迭代器)和get_add_word_flag()(声明后续归一化需求)。
词字符判定由宏 true_word_char(ctype, character) 完成(定义于 ob_ft_char_utils.h):
#define true_word_char(ctype, character)
((ctype) & (_MY_U | _MY_L | _MY_NMR) || (character) == '_')
即大写字母、小写字母、数字、下划线被视为「词字符」,其余字符均为分隔符。该判定兼容多字节编码(如 UTF-8),通过 cs->cset->ctype() 获取字符类型并按多字节长度步进。
二、关键流程
1. 初始化(init)
init() 校验入参合法性(字符集、全文指针、长度大于 0),然后初始化扫描指针:
cs_ = param->cs_; // 字符集
start_ = param->fulltext_; // 全文起始
next_ = start_; // 当前扫描位置
end_ = start_ + param->ft_length_; // 全文结束
is_inited_ = true;
若校验失败,自动调用 reset() 清零状态,保证异常安全。is_inited_ 同时防止二次初始化(返回 OB_INIT_TWICE)。
2. 词元提取(get_next_token)
这是分词的核心循环,逻辑可概括为「跳过分隔符 → 收集词字符 → 输出 token」:
do {
// 阶段 1:跳过分隔符
while (next cset->ctype(cs, &ctype, (uchar *)next, (uchar *)end);
if (true_word_char(ctype, *next)) { break; }
next += mbl > 0 ? mbl : (mbl = end) { ret = OB_ITER_END; }
else {
// 阶段 2:收集连续词字符
int64_t c_nums = 0; start = next;
while (next cset->ctype(cs, &ctype, (uchar *)next, (uchar *)end);
if (!true_word_char(ctype, *next)) { break; }
++c_nums;
next += mbl > 0 ? mbl : (mbl < 0 ? -mbl : 1);
}
if (0 < c_nums) {
word = start;
word_len = next - start; // 字节长度
char_len = c_nums; // 字符数
word_freq = 1; // 词频固定为 1
start = next;
break;
}
}
} while (OB_SUCC(ret) && next < end);
几个关键设计点:
OB_ITER_END表示迭代结束,属于正常流程而非错误;mbl(multi-byte length)处理多字节字符步进,负数表示错误长度,取绝对值兜底;word_freq = 1表示 space parser 不做词频统计,词频聚合由后续ObAddWord::process_word()的groupby_word阶段完成。
3. 插件工厂(segment)
ObWhiteSpaceFTParserDesc::segment() 负责创建并初始化 ObSpaceFTParser 实例:
if (OB_ISNULL(parser = OB_NEWx(ObSpaceFTParser, param->allocator_))) {
ret = OB_ALLOCATE_MEMORY_FAILED;
} else {
if (OB_FAIL(parser->init(param))) {
LOG_WARN("fail to init whitespace fulltext parser", ...);
} else {
iter = parser;
}
}
if (OB_FAIL(ret)) {
OB_DELETEx(ObSpaceFTParser, param->allocator_, parser);
}
这里体现了 OceanBase 的内存管理范式:OB_NEWx 在指定分配器上创建对象,失败时返回 OB_ALLOCATE_MEMORY_FAILED;若 init() 失败,通过 OB_DELETEx 回滚释放,避免内存泄漏。成功后将迭代器指针交给调用方,最终由 free_token_iter() 显式析构并释放。
4. 归一化标志(get_add_word_flag)
space parser 声明需要后续四段归一化/过滤流水线:
flag.set_min_max_word(); // 按 min/max_token_size 过滤过长/过短词
flag.set_stop_word(); // 对照内置停用词表过滤
flag.set_casedown(); // 统一转小写
flag.set_groupby_word(); // 累加相同词元的词频
这与其它内置解析器形成鲜明对比:
| 解析器 | min_max_word | stop_word | casedown | groupby_word |
|---|---|---|---|---|
| space(本文) | ✅ | ✅ | ✅ | ✅ |
| ngram / ngram2 | ❌ | ❌ | ✅ | ✅ |
| beng | ✅ | ✅ | ❌ | ✅ |
| ik | ✅ | ✅ | ✅ | ✅ |
ngram 系列不做 min_max/stopword 过滤,是因为滑动窗口产生的 token 长度固定,且无需词典即可运行;beng 不做 casedown,是因为其内部已处理大小写归一化。
三、小结
ob_whitespace_ft_parser.cpp 以不到 220 行代码实现了 OceanBase 的默认全文分词器。其核心设计简洁而清晰:
- 边界驱动:以
true_word_char为唯一判定标准,连续词字符即为一个 token; - 多字节安全:通过字符集接口
ctype()与多字节长度mbl兼容 UTF-8 等编码; - 异常安全:
OB_NEWx/OB_DELETEx配对,is_inited_守卫,失败自动reset(); - 职责分离:分词只做切分,词频聚合、停用词过滤、大小写归一化全部委托给上层流水线。
对于以英文、数字、代码标识符为主的语料,space parser 是开销最低的选择;而面对中文或需要语义切分的场景,则可切换至 ik 或 ngram 解析器。OceanBase 通过统一的 ObIFTParserDesc 接口与 ObAddWordFlag 声明机制,让多种分词策略在同一套 FTS 框架下无缝协作。