在 概览文章
中,我们已经说明了为什么要在搜索中使用与主数据库相同的 UUID(如果主数据库存在的话)。这里直接进入实操:创建表、通过 SQL 和 JSON API 执行基本操作,然后通过 /bulk 导入几份文档。
所有示例都以 Manticore Search 28.5.0 或更高版本为准。响应中的 <generated UUID> 表示 Manticore 在处理请求时生成的 UUID。下一次请求不需要复制这串内容:请用你自己响应里的实际 id 替换。
所有示例使用的表
要让 UUID 成为文档 ID,首先把字段声明为 id uuid。除了 id 的类型之外,RT 表的结构不会改变:
CREATE TABLE products_uuid (
id uuid,
title text,
sku string,
price int
);
DESC products_uuid 会显示字段 id 的类型是 uuid。SQL 和 HTTP API 都会把这个值当作文档 ID 使用,因此不需要把 UUID 复制到字符串属性里。
通过 SQL 处理 UUID
先插入一条带有现成 UUID 的商品:
INSERT INTO products_uuid (id, title, sku, price)
VALUES (
'550e8400-e29b-41d4-a716-446655440000',
'Mechanical keyboard',
'KB-001',
149
);
在 SQL 里,UUID 必须放在单引号中。可以用普通条件 WHERE id = '...' 来查找文档:
SELECT id, title, sku, price
FROM products_uuid
WHERE id = '550e8400-e29b-41d4-a716-446655440000';
UUID 也可以由 Manticore 生成。为此,只要不传任何 id 值即可:
INSERT INTO products_uuid (title, sku, price)
VALUES ('USB microphone', 'MIC-001', 89);
SELECT LAST_INSERT_ID();
LAST_INSERT_ID() 会返回这次请求生成的 UUID:
+--------------------------------------+
| last_insert_id() |
+--------------------------------------+
| <generated UUID> |
+--------------------------------------+
INSERT 多个文档以及 @@session.last_insert_id 变量在文档的 向表中添加文档
一节里有详细说明。
再添加一条已知 ID 的文档,以便测试 IN:
INSERT INTO products_uuid (id, title, sku, price)
VALUES (
'550e8400-e29b-41d4-a716-446655440001',
'USB-C dock',
'DOCK-001',
119
);
SELECT id, sku, price
FROM products_uuid
WHERE id IN (
'550e8400-e29b-41d4-a716-446655440000',
'550e8400-e29b-41d4-a716-446655440001'
);
属性可以用常规的 UPDATE 修改。此时 id 本身保持不变:
UPDATE products_uuid
SET price = 139
WHERE id = '550e8400-e29b-41d4-a716-446655440000';
INSERT 一个已存在 UUID 的文档不会覆盖原文档。Manticore 会返回重复错误:
INSERT INTO products_uuid (id, title, sku, price)
VALUES (
'550e8400-e29b-41d4-a716-446655440000',
'Duplicate keyboard',
'KB-DUP',
1
);
如果要为同一个 UUID 的文档写入新版本,请使用 REPLACE:
REPLACE INTO products_uuid (id, title, sku, price)
VALUES (
'550e8400-e29b-41d4-a716-446655440000',
'Mechanical keyboard, revised',
'KB-001',
129
);
如果只改价格,用 UPDATE 就够了。对于全文字段和列式属性,则需要 REPLACE:它会把同一 ID 的旧版本标记为已删除,并写入新版本。如果这个 ID 还不存在,Manticore 会直接添加文档。详情请参阅文档:UPDATE
和 REPLACE
。
删除文档时使用同一个 UUID:
DELETE FROM products_uuid
WHERE id = '550e8400-e29b-41d4-a716-446655440001';
检查一下刚才插入时指定的那个 UUID 的当前状态:
SELECT id, title, sku, price
FROM products_uuid
WHERE id = '550e8400-e29b-41d4-a716-446655440000';
SQL 客户端发送和接收 UUID 时都使用字符串。传参时请使用字符串;读取结果时,也要把 id(由 SELECT 返回)当作字符串。无需转换成数字或 BINARY(16)。按数字文档 ID 编写的代码需要相应调整。
通过 JSON API 执行相同操作
通过 JSON API 写入时,id 字段与 table 同级传递,而不是放在 doc 内部。第一个示例使用大写 UUID:
curl -sS http://localhost:9308/insert \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"id": "AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA",
"doc": {
"title": "Wireless keyboard",
"sku": "JSON-KB-001",
"price": 159
}
}'
Manticore 接受大写 UUID,保存时会转为小写,并且在响应中也以同样形式返回:
{
"table": "products_uuid",
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"created": true,
"result": "created",
"status": 201
}
自动生成时,只需要把 id 字段整个去掉:
curl -sS http://localhost:9308/insert \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"doc": {
"title": "Portable speaker",
"sku": "JSON-SPK-001",
"price": 79
}
}'
响应会包含应该保存下来供后续操作使用的 ID:
{
"table": "products_uuid",
"id": "<generated UUID>",
"created": true,
"result": "created",
"status": 201
}
在 /search 中,可以用 equals 过滤 UUID。如果请求将 id 包含在 _source 中,结果会在 _id 和 _source.id 中返回同一个 UUID:
curl -sS http://localhost:9308/search \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"query": {
"equals": {
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
}
},
"_source": ["id", "title", "sku", "price"]
}'
{
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": [
{
"_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"_score": 1,
"_source": {
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"title": "Wireless keyboard",
"sku": "JSON-KB-001",
"price": 159
}
}
]
}
}
_id 是搜索结果元信息的一部分,而 _source.id 是文档字段。对于 UUID 表,这两处包含的是同一串字符串。
现在依次调用其余用于修改数据的端点。UPDATE 只会修改价格:
curl -sS http://localhost:9308/update \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"doc": {"price": 149}
}'
如果要整条替换文档,就调用 /replace:
curl -sS http://localhost:9308/replace \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"doc": {
"title": "Wireless keyboard, revised",
"sku": "JSON-KB-001",
"price": 139
}
}'
现在删除刚刚替换的文档:
curl -sS http://localhost:9308/delete \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
}'
执行 /delete 之后,这个 UUID 对应的文档已经不在表里了。再用同一个 ID 做 /search 会返回 total: 0:
curl -sS http://localhost:9308/search \
-H 'Content-Type: application/json' \
-d '{
"table": "products_uuid",
"query": {
"equals": {
"id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
}
}
}'
{
"timed_out": false,
"hits": {
"total": 0,
"total_relation": "eq",
"hits": []
}
}
按 ID 和按条件删除的请求示例,收录在文档的 删除文档 一节。
在哪里生成 UUID
如果 UUID 已经由主数据库生成,直接把它作为 id 传给 Manticore 即可。重复提交同样的请求时,UUID 仍然保持不变。INSERT 会报告重复,REPLACE 则会用同一个 ID 写入文档的新版本。操作 insert 和 replace 在 /bulk 中也遵循同样的规则。
Manticore 也可以自己生成 UUID,只要不传 id 即可。但重复发送这种请求会创建另一条文档,因此在会自动重试的场景里,最好显式指定 UUID。
显式 ID 可使用 v1 到 v8 的任意版本 UUID。自动生成时,Manticore 使用自己的 UUIDv8 结构,但不会把来自客户端的 UUID 转换成这种格式。
通过 /bulk 批量加载
/bulk 接受 NDJSON:每一行都是一个单独操作。UUID 和其他 JSON API 请求一样,通过 id 字段传递:
POST /bulk
Content-Type: application/x-ndjson
{"insert":{"table":"products_uuid","id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb","doc":{"title":"USB hub","sku":"BULK-HUB-001","price":49}}}
{"insert":{"table":"products_uuid","id":"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbc","doc":{"title":"Laptop stand","sku":"BULK-STAND-001","price":39}}}
在最后一行数据后还需要一个结束换行。对 curl 来说,使用 --data-binary 传递请求体比较方便,这样它不会丢失换行。
这两个操作属于同一张表,因此 Manticore 会在同一个事务中执行它们。精简响应会显示已添加多少文档,以及整个批次的处理结果:
{
"items": [
{
"bulk": {
"created": 2,
"status": 201
}
}
],
"current_line": 2,
"skipped_lines": 0,
"errors": false
}
发生错误时,current_line 会指出处理停在哪一行,而 skipped_lines 表示被跳过的行数。如果空行或切换表把请求拆成了多个事务,Manticore 不会回滚已经完成的部分。
重复加载文档时,要选择合适的操作。如果 UUID 已存在,insert 会以重复错误结束。replace 会重新写入文档;如果该 ID 不存在,则会添加新文档。
字段 id 在 /bulk 请求中也可以省略,Manticore 会生成 UUID。但 /bulk 的响应只包含整个事务的总结果,不会给出每一条插入行的独立结果。如果需要保存每个文档的 ID,最好在批量请求前先生成 UUID,或者改为逐条使用 /insert 添加文档。
校验与常见错误
Manticore 会在添加文档之前校验 UUID。SQL、JSON API 和 /bulk 返回错误的方式不同,所以不要把代码绑定到错误消息的精确文本上。
| 传入内容 | Manticore 的处理 |
|---|---|
550e8400-e29b-41d4-a716-446655440000 | 添加文档 |
AAAAAAAA-AAAA-4AAA-8AAA-AAAAAAAAAAAA | 添加文档,并将 UUID 以小写形式保存和返回 |
| 没有连字符或包含非法字符的字符串 | 因 UUID 格式不正确而返回错误 |
00000000-0000-0000-0000-000000000000 | 返回错误:不能使用零 UUID |
数字,包括 0 | 返回错误:id 必须是字符串 |
版本不在 1–8 范围内,或 variant 不符合 RFC 的 UUID | 返回校验错误 |
INSERT 或 insert 在 /bulk 中使用已存在的 ID | 返回重复错误 |
几点说明:
- 标准写法由 36 个字符组成,分成
8-4-4-4-12五组。UUID 版本允许 1 到 8;按照 RFC,variant位置必须是以下字符之一:8、9、a或b。 - Manticore 只检查 ID 的格式。生成是否正确由应用负责:UUIDv4 依赖随机性,而 UUIDv7 依赖时间部分是否正确以及是否符合规则。Manticore 不会把传入的 v4 和 v7 转换成 v8。
- 在使用 UUID 作为文档 ID 的表里,
id = 0不会像数字 ID 那样触发自动生成:请求会报错。但如果根本不传id,Manticore 就会生成自己的 UUIDv8 结构并在响应中返回。要注意,它可以作为文档 ID 使用,但不能当作访问令牌之类的秘密值。 - 你可以在接收时用标准库校验 UUID,但要注意,Manticore 仍然会执行自己的校验。
- 在
/bulk响应里检查errors、current_line和skipped_lines:它们会告诉你处理停在哪一步,以及哪一部分批次未能提交。而在 SQL 的批量请求中,一旦发生错误,整个请求都会被取消。
Columnar RT 与复制
UUID 也可以作为带列式存储的 RT 表中的文档 ID 使用。只需要更改引擎声明:
CREATE TABLE products_uuid_columnar (
id uuid,
title text,
sku string,
price int
) engine='columnar';
对于普通 RT 表和列式 RT 表,INSERT、REPLACE、DELETE、精确查询以及 IN 条件的写法都是一样的。客户端代码不依赖表引擎。但 UPDATE 不会修改列式属性,因此例如这类表中的价格只能通过 REPLACE 与整条文档一起更新。
复制同样支持 UUID。假设集群 catalog 已经创建,节点也已加入,本地表 products_uuid 也已存在。现在把它加入集群:
ALTER CLUSTER catalog ADD products_uuid;
在 SQL 中,集群名和表名之间使用冒号:
INSERT INTO catalog:products_uuid (id, title, sku, price)
VALUES (
'550e8400-e29b-41d4-a716-446655441000',
'Replicated keyboard',
'REPL-KB-001',
169
);
SELECT id, sku, price
FROM catalog:products_uuid
WHERE id = '550e8400-e29b-41d4-a716-446655441000';
在 JSON API 中,表名原样传递,集群则放在单独的字段里。例如,下面这个操作会更新之前通过 SQL 添加的文档:
curl -sS http://localhost:9308/update \
-H 'Content-Type: application/json' \
-d '{
"cluster": "catalog",
"table": "products_uuid",
"id": "550e8400-e29b-41d4-a716-446655441000",
"doc": {"price": 159}
}'
复制之后,文档会在所有节点上保留相同的 UUID。可以据此执行精确搜索、UPDATE、REPLACE 和 DELETE。
落地前需要注意
uuid类型只能指定给id字段。它支持 RT 表,包括列式存储和复制场景,但不支持 plain、percolate/PQ 和 shard 表。- 现有表不能通过
ALTER TABLE从数字 ID 切换到 UUID,反之亦然。这个决定必须在创建新 schema 时做出。 - UUID 可以用于
=和IN条件。<、<=、>、>=范围查询,以及对id的算术运算都不受支持。 - UUIDv7 包含时间信息,但仍然不能按范围过滤
id。如果要按日期筛选,请添加类似created_at timestamp的属性,并据此过滤。 - 文档 ID 不能通过
UPDATE修改。若要让对象获得不同的 UUID,需要创建一个带新 ID 的文档,并单独删除旧文档。
SQL 会话的行为和完整限制列表见文档:UUID document IDs 。文档 ID 支持 UUID 是从 Manticore Search 28.5.0 开始提供的。
