# 如何使用 `dict='keywords_32k'` 搜索长哈希和 ID

在 Manticore Search 中搜索长哈希、事件 ID、消息 ID 和电子邮件地址的实用指南：限制、精确匹配、通配符搜索、分词、迁移和局限性。

全文搜索通常适用于普通词语：产品名、标题、评论和描述。这类 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 表 | 支持 | 支持 |
| 普通表 | 支持 | 支持 |

该设置作用于整张表：

```sql
CREATE TABLE events (
  message text,
  event_id text
)
dict='keywords_32k';
```

同一张表中的普通短词仍然使用已配置的词形处理。超过 42 字节的 token 会以其原始归一化形式存储，不进行 stemming 或 lemmatization。

对于机器标识符来说，这通常正是你需要的：哈希或事件 ID 并没有有用的词干。

## 何时使用 `keywords_32k`

当以下两个条件都成立时使用它：

1. 该值在分词后可能超过 42 字节。
2. 它必须能够通过 `MATCH()`、前缀或子串进行搜索。

典型示例包括：

- SHA-256 和其他长哈希；
- 事件 ID 和消息 ID；
- 请求 ID、trace ID 以及其他技术标识符；
- 长记录键；
- 本地部分或域部分很长的电子邮件地址；
- 来自日志的技术值；
- 包含分隔符的长标识符。

不过，并不是每一次 ID 查找都需要 `keywords_32k`。

### 如果你只需要精确相等

如果应用总是接收完整 ID，并且只需要检查是否完全相等，那么字符串属性就足够了：

```sql
CREATE TABLE events (
  message text,
  event_id string
);
```

然后使用普通过滤器：

```sql
SELECT id, message
FROM events
WHERE event_id =
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08';
```

### 如果你既需要精确匹配又需要全文搜索

使用 `string attribute indexed`：

```sql
CREATE TABLE events (
  message text,
  event_id string attribute indexed
)
dict='keywords_32k';
```

在这种情况下，Manticore：

- 将原始值作为字符串属性存储；
- 允许你通过 `WHERE` 进行过滤；
- 也会为 `MATCH()` 和通配符搜索建立索引。

对于技术标识符来说，这通常是最方便的 schema。

## 按完整 token 搜索

创建一张表并加入一个 64 字符的 SHA-256 哈希：

```sql
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：

```sql
SELECT id, message
FROM events
WHERE MATCH(
  '@event_id 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
);
```

`@event_id` 运算符会将搜索限制在指定字段中。如果不加它，Manticore 会在表的所有全文字段中搜索该值。

这里单个简单的字母数字 token 不需要加引号。引号表示短语搜索；它们不会把 `MATCH()` 变成对原始字符串逐字节的比较。

## 全文匹配不等于严格相等

`MATCH()` 作用于分词和归一化后的结果。它可能受到以下因素影响：

- `charset_table`；
- 转为小写；
- `blend_chars`；
- `ignore_chars`；
- 词形变化和其他文本处理设置。

如果要严格比较存储的值，请使用字符串属性：

```sql
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：

```sql
CALL KEYWORDS(
  '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
  'events'
);
```

`normalized` 列应该包含完整的 64 字符哈希。

`CALL KEYWORDS` 对包含以下内容的值尤其有用：

- 句点；
- 连字符；
- `@` 符号；
- 冒号；
- 斜杠；
- 来自不同书写系统的字符。

这样你就可以在索引数据之前检查实际的 token 边界。

## 前缀和子串搜索

要在 token 内部搜索，请启用 `min_infix_len`：

```sql
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'
);
```

前缀搜索：

```sql
SELECT id, message
FROM events_infix
WHERE MATCH('@event_id 9f86d081*');
```

子串搜索：

```sql
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`：

```sql
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 的一部分；
- 作为该值中常规部分之间的分隔符。

这样既可以搜索完整的电子邮件地址，也可以搜索其中的单个词。

搜索完整值：

```sql
SELECT id, subject
FROM mail_events
WHERE MATCH(
  '@sender "alessandro.verylonggeneratedlocalpart@example-corporate-domain.test"'
);
```

这里引号很重要，因为 `@` 也用于全文查询语法。在短语中，解析器可以将它按混合字符处理。

按片段搜索：

```sql
SELECT id, subject
FROM mail_events
WHERE MATCH('@sender *generatedlocalpart*');
```

检查分词结果：

```sql
CALL KEYWORDS(
  '"alessandro.verylonggeneratedlocalpart@example-corporate-domain.test"',
  'mail_events'
);
```

在真实应用中，传递给 `MATCH()` 的值必须正确转义。直接把用户输入拼接到查询字符串里，可能会因为 `@`、`-`、`|`、`!`、`"`、`*` 和其他运算符而改变查询含义。

## 如何转换现有表

对于 RT 表，可以使用 `ALTER TABLE` 更改设置：

```sql
ALTER TABLE events dict='keywords_32k';
```

不过，这只会影响在设置变更之后新增或替换的文档。

现有文档不会自动重新分词。它们的长 token 会继续以旧的截断形式存在，直到文档被重新索引。

按以下步骤操作：

1. 更新 `dict`。
2. 通过 `SHOW CREATE TABLE` 验证设置。
3. 重新索引或重新加载现有文档。
4. 用 `CALL KEYWORDS` 和 `MATCH()` 检查几个长值。

对于普通表：

1. 将配置改为 `dict = keywords_32k`。
2. 如果这符合你的更新流程，则使用 `ALTER TABLE ... RECONFIGURE` 应用设置。
3. 基于其数据源完整重建该表。

在数据重新索引之前，同一张表中可能同时包含：

- 旧文档，其中的 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` 之前，请检查：

1. 归一化 token 是否真的超过了 42 字节？
2. 你是否需要全文或通配符搜索，而不只是 `WHERE value = ...`？
3. `CALL KEYWORDS` 是否把完整值视为一个 token？
4. 对于句点、连字符、`@` 和其他分隔符，你是否需要 `blend_chars`？
5. `min_infix_len` 是否足够大？
6. 通配符展开的数量是否受限？
7. 旧文档是否已经重新索引？
8. 该字段是否不包含密钥数据？
9. 应用是否没有依赖高亮、`SUGGEST`、percolate 或全文 `REGEX`？

## 小结

`dict='keywords_32k'` 解决的是一个特定问题：它允许全文索引存储最长 32768 字节的归一化 token，而不是常规的 42 字节。

它非常适合长哈希、事件 ID、消息 ID、电子邮件地址以及其他机器标识符。请记住三点：

- `keywords_32k` 会增加 token 长度，但不会改变分词规则；
- 对完整 token 使用 `MATCH()` 不等于对原始字符串做严格比较；
- 设置变更后，现有文档必须重新索引。

如果你只需要精确相等，请使用字符串属性。如果你既需要精确匹配又需要部分值搜索，请将 `string attribute indexed` 与 `dict='keywords_32k'` 一起使用。

## 文档

- [`dict` 和 `keywords_32k` 的限制](https://manual.manticoresearch.com/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#dict)
- [Token 长度限制](https://manual.manticoresearch.com/Creating_a_table/NLP_and_tokenization/Data_tokenization#Token-length-limit)
- [通配符搜索设置](https://manual.manticoresearch.com/Creating_a_table/NLP_and_tokenization/Wildcard_searching_settings)
- [`blend_chars`](https://manual.manticoresearch.com/Creating_a_table/NLP_and_tokenization/Low-level_tokenization#blend_chars)
- [`CALL KEYWORDS`](https://manual.manticoresearch.com/Searching/Autocomplete#CALL-KEYWORDS)
- [字符串属性和已索引字符串](https://manual.manticoresearch.com/Creating_a_table/Local_tables#String)
- [更新全文设置和重新索引](https://manual.manticoresearch.com/Updating_table_schema_and_settings)
- [排序规则和字符串比较](https://manual.manticoresearch.com/Searching/Collations)
- [Manticore Search 27.1.5](https://manticoresearch.com/blog/manticore-search-27-1-5/)
