自定义分词
自定义分词(Custom Analyzer)通过组合 字符过滤器(char_filter)、分词器(tokenizer) 和 词元过滤器(token_filter),让用户根据业务需求精细定义文本如何被切分成可搜索的词项。它突破了内置分词的局限,直接影响搜索结果的相关性与数据分析的准确性。

适用场景
| 场景 | 推荐组合 |
|---|---|
| 多语言混合文本搜索 | icu 分词器 + icu_normalizer 过滤器 |
| 电话号码 / 编码前缀匹配 | edge_ngram 分词器 |
| 复杂英文文本拆分(驼峰、连字符等) | standard 分词器 + word_delimiter 过滤器 |
| 自动补全 / 输入提示 | edge_ngram 分词器 + lowercase 过滤器 |
| 精确匹配(保留原词) | keyword 分词器 + lowercase/asciifolding 过滤器 |
核心组件
自定义分词由四类对象组成,按顺序作用于原始文本:
原始文本 ──► [char_filter] ──► [tokenizer] ──► [token_filter] ──► 词项
| 组件 | 作用 | 数量 |
|---|---|---|
char_filter | 分词前对字符进行预处理(如替换、标准化) | 0 ~ N |
tokenizer | 将文本切分成词项 | 1 |
token_filter | 对切分后的词项做加工(如小写、ASCII 折叠) | 0 ~ N |
analyzer | 将上述组件组装为完整的分词管道 | 1 |
创建自定义分词
1. 字符过滤器(char_filter)
CREATE INVERTED INDEX CHAR_FILTER IF NOT EXISTS x_char_filter
PROPERTIES (
"type" = "char_replace"
-- 其他参数见下文
);
支持的字符过滤器类型:
-
char_replace:在分词前将指定字符替换为目标字符。char_filter_pattern:需要替换的字符列表char_filter_replacement:替换后的字符(默认空格)
-
icu_normalizer:使用 ICU 标准化对文本进行预处理。name:标准化形式(默认nfkc_cf)。可选:nfc、nfkc、nfkc_cf、nfd、nfkdmode:标准化模式(默认compose)。可选:compose(组合)、decompose(分解)unicode_set_filter:指定需要标准化的字符集(如[a-z])
2. 分词器(tokenizer)
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS x_tokenizer
PROPERTIES (
"type" = "standard"
);
支持的分词器类型:
| 类型 | 说明 | 主要参数 |
|---|---|---|
standard | 标准分词(遵循 Unicode 文本分割),适用于多数语言 | 无 |
ngram | 按 N 元组切分 | min_gram、max_gram、max_ngram_diff、token_chars |
edge_ngram | 从词首起始位置生成 N 元组 | min_gram、max_gram、token_chars |
keyword | 整段文本作为一个词项输出,常与 token_filter 组合 | 无 |
char_group | 按给定字符切分 | tokenize_on_chars |
basic | 简单英文 / 数字 / 中文 / Unicode 分词 | extra_chars |
icu | ICU 国际化分词,支持多语言复杂脚本 | 无 |
ik_smart | IK 中文智能分词,优先输出较完整的词;自 4.1.5 版本起可用于自定义分析器 | 无 |
ik_max_word | IK 中文细粒度分词,输出更多可能重叠的词;自 4.1.5 版本起可用于自定义分析器 | 无 |
参数说明:
min_gram:最小长度(默认 1)max_gram:最大长度(默认 2)token_chars:保留字符类别(默认保留全部)。可选:letter、digit、whitespace、punctuation、symboltokenize_on_chars:字符列表或类别,类别支持whitespace、letter、digit、punctuation、symbol、cjkextra_chars:额外分割的 ASCII 字符(如[]().)
ngram 范围限制(4.1.5 起)
自 4.1.5 版本起,ngram 分词器支持 max_ngram_diff,可显式放宽原先最大长度与最小长度之差不超过 1 的限制。创建分词器时会检查以下条件:
| 参数 | 默认值 | 约束 |
|---|---|---|
min_gram | 1 | 正整数,不大于 max_gram;新建 ngram 分词器时不大于 1024 |
max_gram | 2 | 正整数,不小于 min_gram;新建 ngram 分词器时不大于 1024 |
max_ngram_diff | 1 | 0 到 255 的整数,必须满足 max_gram - min_gram <= max_ngram_diff;设为 0 时只允许固定长度的 N 元组 |
例如,min_gram=1、max_gram=8 必须同时设置 max_ngram_diff 至少为 7。省略该参数会导致创建失败,返回 The difference between max_gram and min_gram in NGram Tokenizer must be less than or equal to: [ 1 ] but was [7]。完整用法见示例 5。
这些新增限制和 max_ngram_diff 参数仅适用于 ngram。edge_ngram 不接受该参数,本文的电话号码和自动补全示例仍按其自身的 min_gram、max_gram 设置执行。长度范围越大,每个输入位置生成的词项越多,应按实际检索需求设置,避免不必要的索引膨胀。
3. 词元过滤器(token_filter)
CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS x_token_filter
PROPERTIES (
"type" = "word_delimiter"
);
支持的词元过滤器类型:
| 类型 | 作用 |
|---|---|
word_delimiter | 在非字母数字字符处切分,并可执行标准化 |
ascii_folding | 将非 ASCII 字符映射为等效 ASCII |
lowercase | 将 token 文本转为小写 |
icu_normalizer | 使用 ICU 标准化对词元进行处理 |
word_delimiter 详解
默认行为:
- 使用非字母数字字符作为分隔符(例:
Super-Duper→Super,Duper) - 清除 token 首尾分隔符(例:
XL---42+'Autocoder'→XL,42,Autocoder) - 在大小写转换处切分(例:
PowerShot→Power,Shot) - 在字母与数字交界处切分(例:
XL500→XL,500) - 移除英文所有格
's(例:Neil's→Neil)
可选参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
generate_number_parts | true | 是否输出数字部分 |
generate_word_parts | true | 是否输出单词部分 |
protected_words | 无 | 受保护的词,不参与切分 |
split_on_case_change | true | 是否在大小写变化处切分 |
split_on_numerics | true | 是否在字母与数字交界处切分 |
stem_english_possessive | true | 是否移除英文所有格 's |
type_table | 无 | 自定义字符类型映射表 |
type_table 支持映射类型:
ALPHA(字母)ALPHANUM(字母数字)DIGIT(数字)LOWER(小写字母)SUBWORD_DELIM(非字母数字分隔符)UPPER(大写字母)
示例:["+ => ALPHA", "- => ALPHA"] 可将 +、- 视为字母而不被切分。
icu_normalizer 参数
name:标准化形式(默认nfkc_cf)。可选:nfc、nfkc、nfkc_cf、nfd、nfkdunicode_set_filter:指定需要标准化的字符集
4. 分析器(analyzer)
将上述组件组装成完整的分词管道:
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS x_analyzer
PROPERTIES (
"tokenizer" = "x_tokenizer", -- 单个分词器
"token_filter" = "x_filter1, x_filter2" -- 一个或多个 token_filter,按顺序执行
);
在表中使用自定义分词
通过索引 PROPERTIES 中的 analyzer 字段引用已创建的自定义分析器:
CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("analyzer" = "x_custom_analyzer", "support_phrase" = "true")
)
table_properties;
注意事项:
- 自定义分词在索引
PROPERTIES中通过analyzer设置 - 与
analyzer同时使用的 properties 仅有support_phrase
一列多分词索引
Doris 支持在同一列上创建多个使用不同分词器的倒排索引,使同一份数据可用不同分词策略检索。
应用场景
- 多语言支持:在同一文本列上使用不同语言的分词器
- 搜索精度与召回率平衡:关键词分词器用于精确匹配,标准分词器用于模糊搜索
- 自动补全:edge_ngram 分词器用于前缀匹配,标准分词器用于常规搜索
创建多个索引
-- 1. 创建不同分词策略的分词器
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS std_analyzer
PROPERTIES ("tokenizer" = "standard", "token_filter" = "lowercase");
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS kw_analyzer
PROPERTIES ("tokenizer" = "keyword", "token_filter" = "lowercase");
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS edge_ngram_tokenizer
PROPERTIES (
"type" = "edge_ngram",
"min_gram" = "1",
"max_gram" = "20",
"token_chars" = "letter"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS ngram_analyzer
PROPERTIES ("tokenizer" = "edge_ngram_tokenizer", "token_filter" = "lowercase");
-- 2. 在同一列上创建多个索引
CREATE TABLE articles (
id INT,
content TEXT,
-- 标准分词器用于分词搜索
INDEX idx_content_std (content) USING INVERTED
PROPERTIES("analyzer" = "std_analyzer", "support_phrase" = "true"),
-- 关键词分词器用于精确匹配
INDEX idx_content_kw (content) USING INVERTED
PROPERTIES("analyzer" = "kw_analyzer"),
-- edge n-gram 分词器用于自动补全
INDEX idx_content_ngram (content) USING INVERTED
PROPERTIES("analyzer" = "ngram_analyzer")
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");
查询时指定分词器
使用 USING ANALYZER 子句指定使用哪个索引:
-- 插入测试数据
INSERT INTO articles VALUES
(1, 'hello world'),
(2, 'hello'),
(3, 'world'),
(4, 'hello world test');
-- 分词搜索:匹配包含 'hello' 词项的行
-- 返回:1, 2, 4
SELECT id FROM articles WHERE content MATCH 'hello' USING ANALYZER std_analyzer ORDER BY id;
-- 精确匹配:仅匹配精确的 'hello' 字符串
-- 返回:2
SELECT id FROM articles WHERE content MATCH 'hello' USING ANALYZER kw_analyzer ORDER BY id;
-- 使用 edge n-gram 进行前缀匹配
-- 返回:1, 2, 4(所有以 'hel' 开头的行)
SELECT id FROM articles WHERE content MATCH 'hel' USING ANALYZER ngram_analyzer ORDER BY id;
也可直接使用内置分词器:
SELECT * FROM articles WHERE content MATCH 'hello' USING ANALYZER standard;
SELECT * FROM articles WHERE content MATCH 'hello' USING ANALYZER none;
SELECT * FROM articles WHERE content MATCH '你好' USING ANALYZER chinese;
为已有表添加索引
-- 添加使用不同分词器的新索引
ALTER TABLE articles ADD INDEX idx_content_chinese (content)
USING INVERTED PROPERTIES("parser" = "chinese");
-- 等待 schema change 完成
SHOW ALTER TABLE COLUMN WHERE TableName='articles';
构建索引
添加索引后,需要为已有数据构建索引:
-- 构建指定索引(非云模式)
BUILD INDEX idx_content_chinese ON articles;
-- 构建所有索引(云模式)
BUILD INDEX ON articles;
-- 查看构建进度
SHOW BUILD INDEX WHERE TableName='articles';
关键说明
- 分词器身份识别:两个具有相同
tokenizer和token_filter配置的分词器被视为相同。不能在同一列上创建具有相同分词器身份的多个索引。 - 索引选择行为:
- 使用
USING ANALYZER时,如果指定分词器的索引存在且已构建,则使用该索引 - 如果索引未构建,查询会降级到非索引路径(结果正确,但性能较慢)
- 未使用
USING ANALYZER时,可能使用任意可用的索引
- 使用
- 性能考虑:
- 每增加一个索引都会增加存储空间和写入开销
- 根据实际查询模式选择分词器
- 如果查询模式可预测,考虑使用较少的索引
管理与维护
查看
SHOW INVERTED INDEX TOKENIZER;
SHOW INVERTED INDEX TOKEN_FILTER;
SHOW INVERTED INDEX ANALYZER;
删除
DROP INVERTED INDEX TOKENIZER IF EXISTS x_tokenizer;
DROP INVERTED INDEX TOKEN_FILTER IF EXISTS x_token_filter;
DROP INVERTED INDEX ANALYZER IF EXISTS x_analyzer;
完整示例
示例 1:电话号码前缀匹配
使用 edge_ngram 对电话号码生成所有前缀片段,便于实现「输入即搜」。
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS edge_ngram_phone_number_tokenizer
PROPERTIES
(
"type" = "edge_ngram",
"min_gram" = "3",
"max_gram" = "10",
"token_chars" = "digit"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS edge_ngram_phone_number
PROPERTIES
(
"tokenizer" = "edge_ngram_phone_number_tokenizer"
);
CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "edge_ngram_phone_number")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
SELECT tokenize('13891972631', '"analyzer"="edge_ngram_phone_number"');
返回结果:
[
{"token":"138"},
{"token":"1389"},
{"token":"13891"},
{"token":"138919"},
{"token":"1389197"},
{"token":"13891972"},
{"token":"138919726"},
{"token":"1389197263"}
]
示例 2:复杂英文文本精细分词
使用 standard 分词器配合 word_delimiter 实现更精细的分词,并叠加大小写归一化与 ASCII 折叠。
CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS word_splitter
PROPERTIES
(
"type" = "word_delimiter",
"split_on_numerics" = "false",
"split_on_case_change" = "false"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS lowercase_delimited
PROPERTIES
(
"tokenizer" = "standard",
"token_filter" = "asciifolding, word_splitter, lowercase"
);
CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "lowercase_delimited")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
SELECT tokenize('The server at IP 192.168.1.15 sent a confirmation to user_123@example.com, requiring a quickResponse before the deadline.', '"analyzer"="lowercase_delimited"');
返回结果:
[
{"token":"the"},
{"token":"server"},
{"token":"at"},
{"token":"ip"},
{"token":"192"},
{"token":"168"},
{"token":"1"},
{"token":"15"},
{"token":"sent"},
{"token":"a"},
{"token":"confirmation"},
{"token":"to"},
{"token":"user"},
{"token":"123"},
{"token":"example"},
{"token":"com"},
{"token":"requiring"},
{"token":"a"},
{"token":"quickresponse"},
{"token":"before"},
{"token":"the"},
{"token":"deadline"}
]
示例 3:保留原词的精确匹配
使用 keyword 分词器保留整段文本,再通过 lowercase、asciifolding 做归一化,常用于精确匹配大小写不敏感的字符串。
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS keyword_lowercase
PROPERTIES
(
"tokenizer" = "keyword",
"token_filter" = "asciifolding, lowercase"
);
CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "keyword_lowercase")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);
SELECT tokenize('hÉllo World', '"analyzer"="keyword_lowercase"');
返回结果:
[
{"token":"hello world"}
]
示例 4:IK 中文智能与细粒度分词(4.1.5 起)
自 4.1.5 版本起,可以在自定义分析器中直接引用内置 ik_smart 或 ik_max_word 分词器,也可以先创建一个指定该 type 的命名分词器,再组合字符过滤器和词元过滤器。两种 IK 分词器没有额外的分词器参数,均使用 BE 配置 inverted_index_dict_path 下 ik 目录中的词典;参与分词和索引构建的 BE 需要支持这些组件并可读取词典。
下面分别演示直接引用和创建命名分词器。创建这些分词组件需要全局 ADMIN 权限;后续建表、导入和查询还需要目标库表的相应权限。
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS cn_ik_smart
PROPERTIES ("tokenizer" = "ik_smart");
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS cn_ik_max_tokenizer
PROPERTIES ("type" = "ik_max_word");
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS cn_ik_max
PROPERTIES ("tokenizer" = "cn_ik_max_tokenizer");
创建成功返回 Query OK。等待分词组件同步到 BE 后,可以用 TOKENIZE 对比效果。以下结果使用内置词典;调整词典后切分结果可能不同。
SELECT TOKENIZE('中华人民共和国', '"analyzer"="cn_ik_smart"');
[{"token":"中华人民共和国"}]
SELECT TOKENIZE('中华人民共和国', '"analyzer"="cn_ik_max"');
[
{"token":"中华人民共和国"},
{"token":"中华人民"},
{"token":"中华"},
{"token":"华人"},
{"token":"人民共和国"},
{"token":"人民"},
{"token":"共和国"},
{"token":"共和"},
{"token":"国"}
]
将分析器用于表的倒排索引:
CREATE TABLE ik_articles (
id INT,
content STRING,
INDEX idx_content(content) USING INVERTED
PROPERTIES("analyzer" = "cn_ik_smart")
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");
INSERT INTO ik_articles VALUES (1, '中华人民共和国');
SELECT id FROM ik_articles
WHERE content MATCH '中华人民共和国' USING ANALYZER cn_ik_smart
ORDER BY id;
+----+
| id |
+----+
| 1 |
+----+
ik_smart 更适合保留完整中文词的检索,ik_max_word 更适合需要更多子词召回的场景。需要进一步处理词项时,可通过分析器的 token_filter 组合词元过滤器。
示例 5:扩大 ngram 长度范围(4.1.5 起)
以下分词器生成长度为 1 到 8 的 N 元组,显式设置 max_ngram_diff=7:
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS ngram_1_8_tokenizer
PROPERTIES (
"type" = "ngram",
"min_gram" = "1",
"max_gram" = "8",
"max_ngram_diff" = "7"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS ngram_1_8_analyzer
PROPERTIES ("tokenizer" = "ngram_1_8_tokenizer");
创建成功并同步到 BE 后执行:
SELECT TOKENIZE('abcdefgh', '"analyzer"="ngram_1_8_analyzer"');
[
{"token":"a"}, {"token":"ab"}, {"token":"abc"}, {"token":"abcd"},
{"token":"abcde"}, {"token":"abcdef"}, {"token":"abcdefg"}, {"token":"abcdefgh"},
{"token":"b"}, {"token":"bc"}, {"token":"bcd"}, {"token":"bcde"},
{"token":"bcdef"}, {"token":"bcdefg"}, {"token":"bcdefgh"},
{"token":"c"}, {"token":"cd"}, {"token":"cde"}, {"token":"cdef"},
{"token":"cdefg"}, {"token":"cdefgh"},
{"token":"d"}, {"token":"de"}, {"token":"def"}, {"token":"defg"}, {"token":"defgh"},
{"token":"e"}, {"token":"ef"}, {"token":"efg"}, {"token":"efgh"},
{"token":"f"}, {"token":"fg"}, {"token":"fgh"},
{"token":"g"}, {"token":"gh"}, {"token":"h"}
]
使用限制
tokenizer和token_filter中的type与参数只能填写当前支持的分词器和词元过滤器,否则建表失败。- 只有在没有任何表使用某个
analyzer时,才能删除该analyzer。 - 只有在没有任何
analyzer使用某个tokenizer或token_filter时,才能删除它们。 - 自定义分词语法在执行 10 秒后同步到 BE,之后导入不会报错。
注意事项
- 自定义分词
analyzer嵌套多个组件可能导致分词性能降低。 select tokenize分词函数支持自定义分词,可用于调试切分效果。- 预定义分词
built_in_analyzer与自定义分词analyzer在同一索引上只能存在一个。