全文搜索通常适用于普通词语:产品名、标题、评论和描述。这类 token 很少长到几十个字符以上。
日志和技术数据则不同。SHA-256 哈希长 64 个字符,而消息 ID、关联键、事件标识符以及某些电子邮件地址可能更长。它们的值通常只有作为整体才有意义:如果尾部丢失,一个 ID 很容易与另一个 ID 混淆。
Manticore Search 为这些场景提供了 dict='keywords_32k'。
keywords_32k从 Manticore Search 27.1.1 起可用。将现有表从keywords转换过来时,建议使用 27.1.5 或更新版本。
常规字典的问题
默认情况下,Manticore 使用 dict='keywords'。它的 token 最大长度是归一化后的 42 字节。
这个限制按字节计算,不是按字符计算。一个 ASCII 字符占 1 字节,而一个 UTF-8 字符可能占多个字节。
如果某个 token 超过 42 字节,Manticore 会将其截断:
- 在索引文档时;
- 在处理搜索查询时。
结果是,查询完整的长值并不一定会返回 0 条结果。因为查询本身也会被截断,文档可能仍然能被找到,但只会按前 42 字节来匹配。
这会带来更严重的问题:两个不同的 ID 如果前 42 字节相同,就会在全文搜索中变得无法区分。并且,也无法通过位于第 42 字节之后的片段来查找某个值。
keywords_32k 改变了什么
dict='keywords_32k' 将归一化后的 token 最大长度提高到32768 字节,也就是 32 KB。
| 行为 | dict='keywords' | dict='keywords_32k' |
|---|---|---|
| Token 最大长度 | 42 字节 | 32768 字节 |
| 超出限制的 token | 被截断 | 跳过并给出警告 |
| 前缀和中缀搜索 | 支持 | 支持 |
| 超过 42 字节 token 的词形处理 | Token 已经被截断 | 不适用 |
| RT 表 | 支持 | 支持 |
| 普通表 | 支持 | 支持 |
该设置作用于整张表:
CREATE TABLE events (
message text,
event_id text
)
dict='keywords_32k';
同一张表中的普通短词仍然使用已配置的词形处理。超过 42 字节的 token 会以其原始归一化形式存储,不进行 stemming 或 lemmatization。
对于机器标识符来说,这通常正是你需要的:哈希或事件 ID 并没有有用的词干。
何时使用 keywords_32k
当以下两个条件都成立时使用它:
- 该值在分词后可能超过 42 字节。
- 它必须能够通过
MATCH()、前缀或子串进行搜索。
典型示例包括:
- SHA-256 和其他长哈希;
- 事件 ID 和消息 ID;
- 请求 ID、trace ID 以及其他技术标识符;
- 长记录键;
- 本地部分或域部分很长的电子邮件地址;
- 来自日志的技术值;
- 包含分隔符的长标识符。
不过,并不是每一次 ID 查找都需要 keywords_32k。
如果你只需要精确相等
如果应用总是接收完整 ID,并且只需要检查是否完全相等,那么字符串属性就足够了:
CREATE TABLE events (
message text,
event_id string
);
然后使用普通过滤器:
SELECT id, message
FROM events
WHERE event_id =
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
如果你既需要精确匹配又需要全文搜索
使用 string attribute indexed:
CREATE TABLE events (
message text,
event_id string attribute indexed
)
dict='keywords_32k';
在这种情况下,Manticore:
- 将原始值作为字符串属性存储;
- 允许你通过
WHERE进行过滤; - 也会为
MATCH()和通配符搜索建立索引。
对于技术标识符来说,这通常是最方便的 schema。
按完整 token 搜索
创建一张表并加入一个 64 字符的 SHA-256 哈希:
DROP TABLE IF EXISTS events;
CREATE TABLE events (
message text,
event_id string attribute indexed
)
dict='keywords_32k';
INSERT INTO events (id, message, event_id) VALUES
(
1,
'delivery accepted',
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);
在全文搜索中使用完整的归一化 token:
SELECT id, message
FROM events
WHERE MATCH(
'@event_id 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);
@event_id 运算符会将搜索限制在指定字段中。如果不加它,Manticore 会在表的所有全文字段中搜索该值。
这里单个简单的字母数字 token 不需要加引号。引号表示短语搜索;它们不会把 MATCH() 变成对原始字符串逐字节的比较。
全文匹配不等于严格相等
MATCH() 作用于分词和归一化后的结果。它可能受到以下因素影响:
charset_table;- 转为小写;
blend_chars;ignore_chars;- 词形变化和其他文本处理设置。
如果要严格比较存储的值,请使用字符串属性:
SET collation_connection='binary';
SELECT id, message
FROM events
WHERE event_id =
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
binary 会在当前 SQL 会话中启用逐字节字符串比较。这个设置不会影响全文搜索行为。
实用规则很简单:
WHERE event_id = ...— 对存储字符串进行严格比较;MATCH('@event_id ...')— 对归一化 token 进行搜索;MATCH('@event_id prefix*')— 前缀搜索;MATCH('@event_id *fragment*')— 子串搜索。
如何检查分词结果
在加载大量数据之前,先确认 Manticore 实际上是否把这个值视为单个 token:
CALL KEYWORDS(
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
'events'
);
normalized 列应该包含完整的 64 字符哈希。
CALL KEYWORDS 对包含以下内容的值尤其有用:
- 句点;
- 连字符;
@符号;- 冒号;
- 斜杠;
- 来自不同书写系统的字符。
这样你就可以在索引数据之前检查实际的 token 边界。
前缀和子串搜索
要在 token 内部搜索,请启用 min_infix_len:
DROP TABLE IF EXISTS events_infix;
CREATE TABLE events_infix (
message text,
event_id string attribute indexed
)
dict='keywords_32k'
min_infix_len='4';
INSERT INTO events_infix (id, message, event_id) VALUES
(
1,
'delivery accepted',
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);
前缀搜索:
SELECT id, message
FROM events_infix
WHERE MATCH('@event_id 9f86d081*');
子串搜索:
SELECT id, message
FROM events_infix
WHERE MATCH('@event_id *b2b0b822*');
正的 min_infix_len 也会启用前缀搜索。这个示例使用 4,以避免模式过短、覆盖过宽。
为什么短模式很危险
与常规 dict='keywords' 一样,dict='keywords_32k' 不会预先计算所有可能的子串。相反,它会在查询时将通配符模式展开为匹配的字典词条。
例如,模式 *ab* 可能匹配大量值。它产生的展开越多,且每个匹配词条所包含的文档越多,查询成本就越高。
对于生产系统:
- 不要允许用户搜索过短的片段;
- 根据真实数据选择
min_infix_len; - 使用
expansion_limit限制展开数量; - 用接近生产规模的字典测试性能;
- 用
@field将搜索限制在特定字段中。
index_exact_words='1' 本身并不是通配符搜索所必需的。如果你想在排序时区分精确匹配和通配符匹配,通常需要它,并且一般要与 expand_keywords 一起使用。
带分隔符的电子邮件地址和其他值
keywords_32k 只改变 token 的最大长度,不决定 token 从哪里开始、在哪里结束。
默认情况下,句点、@、连字符和其他字符可能会把一个值拆成多个部分。如果电子邮件地址或消息 ID 也必须作为整体建立索引,可以使用 blend_chars:
DROP TABLE IF EXISTS mail_events;
CREATE TABLE mail_events (
sender string attribute indexed,
subject text
)
dict='keywords_32k'
blend_chars='., @, -'
min_infix_len='4';
INSERT INTO mail_events (id, sender, subject) VALUES
(
1,
'alessandro.verylonggeneratedlocalpart@example-corporate-domain.test',
'delivery accepted'
);
被混合的字符会以两种方式建立索引:
- 作为完整 token 的一部分;
- 作为该值中常规部分之间的分隔符。
这样既可以搜索完整的电子邮件地址,也可以搜索其中的单个词。
搜索完整值:
SELECT id, subject
FROM mail_events
WHERE MATCH(
'@sender "alessandro.verylonggeneratedlocalpart@example-corporate-domain.test"'
);
这里引号很重要,因为 @ 也用于全文查询语法。在短语中,解析器可以将它按混合字符处理。
按片段搜索:
SELECT id, subject
FROM mail_events
WHERE MATCH('@sender *generatedlocalpart*');
检查分词结果:
CALL KEYWORDS(
'"alessandro.verylonggeneratedlocalpart@example-corporate-domain.test"',
'mail_events'
);
在真实应用中,传递给 MATCH() 的值必须正确转义。直接把用户输入拼接到查询字符串里,可能会因为 @、-、|、!、"、* 和其他运算符而改变查询含义。
如何转换现有表
对于 RT 表,可以使用 ALTER TABLE 更改设置:
ALTER TABLE events dict='keywords_32k';
不过,这只会影响在设置变更之后新增或替换的文档。
现有文档不会自动重新分词。它们的长 token 会继续以旧的截断形式存在,直到文档被重新索引。
按以下步骤操作:
- 更新
dict。 - 通过
SHOW CREATE TABLE验证设置。 - 重新索引或重新加载现有文档。
- 用
CALL KEYWORDS和MATCH()检查几个长值。
对于普通表:
- 将配置改为
dict = keywords_32k。 - 如果这符合你的更新流程,则使用
ALTER TABLE ... RECONFIGURE应用设置。 - 基于其数据源完整重建该表。
在数据重新索引之前,同一张表中可能同时包含:
- 旧文档,其中的 token 已被截断;
- 新文档,其中的 token 是完整的。
这可能会让看起来相同的文档产生不同的结果。
为什么 dict='crc' 不能解决这个问题
dict='crc' 存储的是关键词校验和,而不是原始文本。不过,它并不会增加允许的 token 长度。
常规 42 字节限制的例外,是由 dict='keywords_32k' 专门实现的。
keywords 和 keywords_32k 字典还会存储词项文本,这使 Manticore 能够针对字典展开前缀和中缀通配符查询。
如果你需要搜索很长的机器标识符,crc 不能替代 keywords_32k。
当前局限
在发布时,dict='keywords_32k' 还有若干局限:
- 不支持
CALL SUGGEST和CALL QSUGGEST; - 不能用于 percolate 表;
- 超过 42 字节的 token 不会在 snippets 或 highlights 中高亮;
indextool --dumpdict不能导出这种字典;- 全文
REGEX运算符可与dict='keywords'一起工作,但不能与keywords_32k一起工作。
最后这一点不要与用于过滤字符串属性的 REGEX() 函数混淆。如果字段声明为 string attribute indexed,属性过滤和全文搜索仍然是两种独立机制。
不要索引密钥
能够搜索一个长值,并不意味着你应该把它存进搜索索引。
除非绝对必要,否则不要索引以下内容:
- API 密钥;
- bearer token;
- 会话 cookie;
- 私钥;
- 密码和重置令牌;
- 其他可用于访问系统的数据。
keywords_32k 解决的是搜索问题,但不能阻止值被用户、备份、查询日志或系统管理员读取。
如果某个密钥必须按其精确值匹配,提前计算合适的哈希并只存储哈希会更安全。
快速检查清单
启用 keywords_32k 之前,请检查:
- 归一化 token 是否真的超过了 42 字节?
- 你是否需要全文或通配符搜索,而不只是
WHERE value = ...? CALL KEYWORDS是否把完整值视为一个 token?- 对于句点、连字符、
@和其他分隔符,你是否需要blend_chars? min_infix_len是否足够大?- 通配符展开的数量是否受限?
- 旧文档是否已经重新索引?
- 该字段是否不包含密钥数据?
- 应用是否没有依赖高亮、
SUGGEST、percolate 或全文REGEX?
小结
dict='keywords_32k' 解决的是一个特定问题:它允许全文索引存储最长 32768 字节的归一化 token,而不是常规的 42 字节。
它非常适合长哈希、事件 ID、消息 ID、电子邮件地址以及其他机器标识符。请记住三点:
keywords_32k会增加 token 长度,但不会改变分词规则;- 对完整 token 使用
MATCH()不等于对原始字符串做严格比较; - 设置变更后,现有文档必须重新索引。
如果你只需要精确相等,请使用字符串属性。如果你既需要精确匹配又需要部分值搜索,请将 string attribute indexed 与 dict='keywords_32k' 一起使用。
