# 集合(Collections)
在 Typesense 中,您索引的每条记录称为一个文档(Document)
,而具有相似字段的一组文档称为一个集合(Collection)
。
集合大致相当于关系型数据库中的表。
# 创建集合
在能够向 Typesense 添加文档之前,我们需要先创建一个集合
——我们为其指定名称并描述将从文档
中索引的字段。
我们将这个定义称为集合的模式(schema)
,这只是用于描述文档中字段(及其数据类型)的一个专业术语。
TIP
将定义集合"模式"想象成在强类型编程语言(如 Typescript、C、Java、Dart、Rust 等)中定义"类型"可能会有所帮助。 这确保您添加到集合中的文档具有一致的数据类型并经过验证,有助于防止因文档间数据类型不匹配或不一致而导致的各类错误。
集合组织技巧
在专门指南文章中了解更多关于如何将数据组织到集合中的方法:组织集合。
有两种指定模式的方式:
最简单的选择是#2,您无需担心定义显式模式。 但如果您需要更细粒度的控制和/或验证,您会想要使用#1或者甚至将两者混合使用。
# 使用预定义模式
首先,让我们创建一个具有明确预定义模式的集合。
这种方式让您可以精细控制文档字段的数据类型,并配置集合以拒绝不符合模式中定义的数据类型的文档(默认情况下)。
如果您希望 Typesense 自动检测模式,请跳转到自动模式检测。
查看模式参数了解所有可用选项,以及字段类型了解所有可用的数据类型。
示例响应
定义
POST ${TYPESENSE_HOST}/collections
重要提示与技巧
集合模式(schema)中提到的所有字段都会被索引到内存中。
有些情况下,您可能不希望某些字段被搜索/过滤/分面/分组,而只是想将其存储(在磁盘上)并在文档命中搜索结果时原样返回。 例如:您可以在每个文档中存储图片URL用于显示搜索结果,但可能不想对实际URL进行文本搜索。
对于这类字段,您应该选择:
- 不在集合模式中声明这些字段
- 或者将这些字段标记为
index: false
(参见下方fields
模式参数)来设为非索引字段
当向集合添加文档时,您可以包含任意数量的这类额外非索引字段——它们只会被存储在磁盘上,不会占用任何内存。
# 使用自动模式检测
如果您的字段名称是动态的且事先未知,或者您只是想保持简单并默认索引文档中的所有字段,自动模式检测功能将会非常有用。
您可以通过定义一个名为 .*
、类型为 auto
的通配符字段,让 Typesense 在添加文档到集合时自动检测字段类型。实际上,您可以使用任何正则表达式来定义字段名称。
当以这种方式定义 .*
字段时,文档中的所有字段都会自动被索引以支持搜索和过滤功能。
# 数据强制转换
假设您为集合中的某个字段(或多个字段)(例如:popularity_score
)设置了 type: auto
,并发送了第一个文档:
由于 popularity_score
设置了 type: auto
,其数据类型将在内部自动设置为 int64
。
当后续文档的 popularity_score
字段不是整数而是字符串时会发生什么?例如:
默认情况下,Typesense 会尝试将值强制转换(convert)为先前推断的类型。
因此在本例中,由于第一个文档的 popularity_score
是数值类型,第二个文档的 popularity_score
字段会从字符串强制转换为整数。
然而,这种转换并不总能成功(例如:当值包含字母时,就无法转换为整数)。 在这种情况下,当 Typesense 无法将字段值强制转换为先前推断的类型时,索引操作将失败并返回相应的错误。
TIP
您可以通过写入时的 dirty_values
参数来控制这种默认的强制转换行为。
# 支持自动模式检测的分面字段
对于通配符字段 {"name": ".*" , ...}
,分面搜索功能默认不会启用,因为这可能会消耗大量内存,特别是对于大型文本字段。不过,您仍可以通过显式设置 facet: true
来为特定字段(无论是否使用正则表达式命名)启用分面功能。
例如,当您定义如下模式时:
{
"name": "companies",
"fields": [
{
"name": ".*_facet",
"type": "auto",
"facet": true
}
]
}
这将仅把文档中名称以 _facet
结尾的字段设置为分面字段。
# Geopoint
与自动模式检测
geopoint
字段需要显式类型定义,因为地理坐标字段值表示为 2 元素的浮点数组,我们无法区分经纬度定义和实际的浮点数组。
# 索引除部分字段外的所有字段
在某些情况下,您可能希望索引文档中的所有字段,但排除少数特定字段。这时可以使用 {"index": false, "optional": true}
设置来排除字段。
注意:目前无法将必填字段排除在索引之外,因此需要将字段设置为可选。
例如,如果您想索引除 description_
开头的字段外的所有字段,可以使用如下模式:
TIP
您可以将自动模式检测与显式字段定义混合使用。
如果某个字段有显式定义(如上例中的 country
字段),Typesense 会优先使用该定义,然后再回退到通配符定义。
当没有显式字段定义时,包含该字段名的第一个文档将决定该字段的类型。
例如,如果您索引的文档中有一个名为 title
的字段是字符串类型,那么后续包含 title
字段的文档也会被期望是字符串类型。
# 模式参数
参数 | 是否必需 | 描述 |
---|---|---|
name | 是 | 要创建的集合名称。 |
fields | 是 | 用于查询、过滤、分面、分组和排序的字段列表。每个字段至少需要指定name 和type 。例如: {"name": "title", "type": "string", "facet": false, "index": true} name 可以是简单字符串如"name": "score" ,也可以使用正则表达式指定匹配模式的字段名。例如:如果想指定所有以score_ 开头的字段应为整数类型,可以设置"name": "score_.*" 。声明可选字段 通过设置 "optional": true 可将字段声明为可选。声明分面字段 通过设置 "facet": true 可将字段声明为可分面字段。分面字段会原样索引,不进行任何分词或预处理。例如,构建产品搜索时,color 和brand 可定义为分面字段。一旦在模式中启用字段分面,就可以在facet_by 搜索参数中使用。启用词干提取 词干提取允许处理相同词根的常见词形变化(单复数、时态变化)。例如:启用词干提取后,搜索 walking 也会返回包含walk 、walked 、walks 等的结果。词干提取可通过两种方式启用:
声明非索引字段 通过设置 "index": false 可将字段设为非索引(无法搜索/排序/过滤/分面)。这在配合自动模式检测使用时特别有用,可以从索引中排除特定字段。阻止字段存储到磁盘: 设置 "store": false 可确保在文档保存到磁盘前移除该字段值。配置语言特定分词: Typesense默认的分词器适用于大多数语言(特别是以空格分隔单词的语言)。根据用户反馈,我们为以下语言添加了特定区域设置的自定义。可通过在字段定义中设置 locale 字段启用这些自定义。例如:{name: 'title', type: 'string', locale: 'ja'} 会为title 字段启用日语区域设置自定义。如需保留变音符号,设置对应语言的 locale 会有帮助。以下是部分语言特定区域设置的非穷举列表:
字段级分词分隔符和符号 可以使用 token_separators 和symbols_to_index 配置字段级分词。例如: {"name": "title", "type": "string", "token_separators": ["-"], "symbols_to_index": ["_"]} 字段级设置优先于集合级设置。 |
token_separators | 否 | 除空格和换行符外,用于将文本分割为单个单词的符号或特殊字符列表。 例如:添加 - (连字符)可使non-stick 在连字符处分割并索引为两个独立单词。阅读这篇指南文章获取更多使用此设置的示例。 |
symbols_to_index | 否 | 需要索引的符号或特殊字符列表。 例如:添加 + 可使c++ 按原样索引。阅读这篇指南文章获取更多使用此设置的示例。 |
default_sorting_field | 否 | 当搜索时未提供sort_by 子句时,用于确定搜索结果排序顺序的int32 / float 字段名称。此字段应表示某种受欢迎程度。例如在产品搜索应用中,可将 num_reviews 字段设为default_sorting_field ,默认将评论最多的产品排名靠前。此外,当搜索查询中的单词匹配多个可能单词时(前缀搜索或拼写错误),此参数用于对这类同等匹配记录进行排序。 例如:搜索"ap"会匹配数据集中以"ap"开头的数百个类似单词(如"apple"、"apply"、"apart"、"apron"等)。同样,搜索"jofn"会匹配数据集中与"john"、"joan"等1个拼写错误距离的变体。 出于性能考虑,Typesense默认只考虑前 4 个前缀或拼写错误变体(可通过max_candidates 搜索参数配置,默认为4 )。如果集合模式中未指定 default_sorting_field ,则"top"定义为匹配记录数最多的前缀或拼写错误变体。但如果每条记录中有名为 popularity 的字段,并希望Typesense使用该字段值定义"top"记录,则应设置default_sorting_field: popularity 。Typesense将使用该字段值获取前max_candidates 个最受欢迎的术语,随着用户输入更多字符,会进一步优化搜索,始终将最受欢迎的前缀排名最高。 |
# 字段参数
参数名 | 是否必填 | 描述 |
---|---|---|
name | 是 | 字段名称 |
type | 是 | 字段的数据类型(参见下方类型列表) |
facet | 否 | 启用字段的分面搜索功能。默认值:false |
optional | 否 | 设为 true 时,字段允许为空值、null 或缺失值。默认值:false |
index | 否 | 设为 false 时,字段不会被索引到任何内存索引中(如搜索/排序/过滤/分面)。默认值:true |
store | 否 | 设为 false 时,字段值不会存储到磁盘。默认值:true |
sort | 否 | 设为 true 时,字段可排序。默认值:数字类型为 true ,其他类型为 false |
infix | 否 | 设为 true 时,支持字段值的中缀搜索。会显著增加内存开销。默认值:false |
locale | 否 | 配置特定语言的分词规则,例如 jp 表示日语。默认值:en (同时广泛支持大多数欧洲语言) |
num_dim | 否 | 设为非零值时,将 float[] 类型字段视为向量字段 |
vec_dist | 否 | 向量搜索使用的距离度量方式。默认值:cosine (余弦相似度),也可使用 ip (内积) |
reference | 否 | 指向另一个集合中的字段名称,用于在查询时进行跨集合关联 |
range_index | 否 | 为数值字段启用范围过滤优化索引(例如 rating:>3.5 )。默认值:false |
stem | 否 | 在内存索引前对值进行词干提取。默认值:false |
# 字段类型
Typesense 支持索引以下类型的字段:
type | 描述 |
---|---|
string | 字符串值 |
string[] | 字符串数组 |
int32 | 最大值为 2,147,483,647 的整数值 |
int32[] | int32 数组 |
int64 | 大于 2,147,483,647 的整数值 |
int64[] | int64 数组 |
float | 浮点数/小数 |
float[] | 浮点数/小数数组 |
bool | true 或 false |
bool[] | 布尔值数组 |
geopoint | 以 [lat, lng] 格式指定的经纬度。详见此处。 |
geopoint[] | 以 [[lat1, lng1], [lat2, lng2]] 格式指定的经纬度数组。详见此处。 |
geopolygon | 由坐标数组定义的地理多边形,格式为 [lat1, lng1, lat2, lng2, ...] 。纬度/经度对必须按逆时针(CCW)或顺时针(CW)顺序排列。详见此处。 |
object | 嵌套对象。详见此处。 |
object[] | 嵌套对象数组。详见此处。 |
string* | 特殊类型,自动将值转换为 string 或 string[] 。 |
image | 特殊类型,用于表示图像搜索中使用的 base64 编码图像字符串。 |
auto | 特殊类型,根据添加到集合中的文档自动尝试推断数据类型。参见自动模式检测。 |
# 克隆集合架构
以下是如何克隆现有集合的架构(不复制文档)、覆盖规则和同义词的方法:
curl -k "http://localhost:8108/collections?src_name=existing_coll" -X POST -H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
"name": "new_coll"
}'
上述 API 调用将创建一个名为 new_coll
的新集合,其中包含集合 existing_coll
的架构、覆盖规则和同义词。existing_coll
集合中的实际文档不会被复制,因此这主要用于从现有参考模板创建新集合。
TIP
通过这种方式克隆集合不会复制数据。
# 向架构添加元数据
如果需要,您可以在创建集合时向架构中添加 metadata
对象,以记录集合的详细信息,例如创建时间、创建者等。
{
"name": "docs",
"enable_nested_fields": true,
"fields": [
{
"name": "title",
"type": "string",
"facet": true
}
],
"metadata": {
"batch_job": 325,
"indexed_from": "2023-04-20T00:00:00.000Z"
}
}
metadata
对象中的字段会被持久化,并在 GET /collections
接口中返回。
# 常见数据类型索引注意事项
以下是使用上述基本类型索引其他常见数据类型的方法:
# 嵌套字段索引
Typesense 从 v0.24
版本开始支持索引嵌套对象(以及对象数组)。
首先需要通过 enable_nested_fields
模式属性和 object
或 object[]
数据类型在集合级别启用嵌套字段:
curl -k "http://localhost:8108/collections" -X POST -H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
"name": "docs",
"enable_nested_fields": true,
"fields": [
{"name": "person", "type": "object"},
{"name": "details", "type": "object[]"}
]
}'
现在当你搜索对象字段名时,所有子字段都会被自动搜索。使用点标记法来引用特定子字段,例如 person.last_name
或 person.school.name
。
你也可以索引嵌套对象中的特定子字段。例如,如果你的文档如下所示:
{
"id": 1,
"name": "Jack",
"address": {
"line_1": "111 1st Street",
"city": "Alpha",
"zip": "98765"
}
}
假设你只想索引 address
嵌套对象中的 zip
字段,而不索引其他字段如 line_1
,可以在模式中这样指定:
curl -k "http://localhost:8108/collections" -X POST -H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
"name": "docs",
"enable_nested_fields": true,
"fields": [
{"name": "name", "type": "string"},
{"name": "address.zip", "type": "string"}
]
}'
要索引对象数组中的特定字段,你需要指定一个数组数据类型。例如,如果你的文档如下所示:
{
"id": 1,
"name": "Jack",
"addresses": [
{
"line_1": "111 1st Street",
"city": "Alpha",
"zip": "98765"
},
{
"line_1": "222 2nd Street",
"city": "Zeta",
"zip": "45678"
}
]
}
假设你只想索引 addresses
对象数组中每个地址对象的 zip
字段,而不索引其他字段,可以在模式中这样指定:
curl -k "http://localhost:8108/collections" -X POST -H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" -d '{
"name": "docs",
"enable_nested_fields": true,
"fields": [
{"name": "name", "type": "string"},
{"name": "addresses.zip", "type": "string[]"}
]
}'
注意
如果在嵌套层次结构的不同级别存在重叠的嵌套字段定义,更广泛的定义将优先于子字段的定义。
通过扁平化方式索引嵌套对象
在将数据发送到 Typesense 之前,您也可以将对象和对象数组扁平化为顶级键。
例如,包含嵌套对象的文档如下:
{
"nested_field": {
"field1": "value1",
"field2": ["value2", "value3", "value4"],
"field3": {
"fieldA": "valueA",
"fieldB": ["valueB", "valueC", "valueD"]
}
}
}
需要被扁平化为:
{
"nested_field.field1": "value1",
"nested_field.field2": ["value2", "value3", "value4"],
"nested_field.field3.fieldA": "valueA",
"nested_field.field3.fieldB": ["valueB", "valueC", "valueD"]
}
然后才能将其索引到 Typesense 中。
为了简化结果数据的遍历,您可能希望同时将嵌套字段的扁平化和非扁平化版本发送到 Typesense, 并且仅在集合模式中将扁平化键设置为索引字段,用于搜索/过滤/分面。 在解析结果进行显示时,您就可以使用嵌套版本。
# 日期索引
日期需要转换为 Unix 时间戳 (opens new window) 并作为 int64
类型字段存储在 Typesense 中。
大多数编程语言都有库可以帮助您完成这种转换。
之后您就可以使用 <
、>
等数值运算符来筛选早于、晚于或介于某些日期之间的记录。
# 其他类型数据的索引
阅读我们的专题指南文章,了解如何索引其他常见类型的数据,如电子邮件、电话号码、SKU、型号等,请点击这里。
# 获取集合信息
根据集合名称检索集合的详细信息。
示例响应
接口定义 GET ${TYPESENSE_HOST}/collections/:collection
# 列出所有集合
返回您所有集合的摘要信息。返回的集合会按创建时间排序,最新的集合会显示在最前面。
TIP
默认情况下会返回所有集合,但您可以使用 offset
和 limit
参数来实现集合列表的分页显示。
您还可以设置 exclude_fields=fields
来排除响应中返回的字段定义。
示例响应
定义 GET ${TYPESENSE_HOST}/collections
# 删除集合
永久删除一个集合。此操作不可撤销。对于大型集合,可能会对读取延迟产生影响。
示例响应
当删除一个集合(collection)时,系统会执行磁盘压缩操作以回收被删除集合占用的磁盘空间。不过,如果您需要管理大量集合并频繁删除集合,可以通过 compact_store
GET 参数禁用此压缩功能,以提高删除操作的性能。您仍然可以通过 API 执行完整的数据库压缩。
定义 DELETE ${TYPESENSE_HOST}/collections/:collection
# 清空集合
您可以通过 truncate 操作移除集合中的所有文档,同时保留集合和模式(schema)结构不变。
响应示例
接口定义 DELETE ${TYPESENSE_HOST}/collections/:collection/documents?truncate=true
响应中包含 num_deleted
字段,表示被删除的文档数量。对于空集合,该值将为 0。
# 更新或修改集合
Typesense 支持直接对集合的 schema 进行字段的添加或删除。
TIP
Typesense 支持更新所有字段,除了 id
字段(因为它是 Typesense 中的一个特殊字段)。
让我们看看如何向 companies
集合添加一个新的 company_category
字段,并删除现有的 num_employees
字段。
示例响应
TIP
删除字段只会影响 schema 和内存中的索引; 它不会从磁盘存储的现有文档中移除与该字段关联的数据。 在 Typesense 中,文档可以包含 schema 中未明确定义的额外字段。
若要永久移除文档中的某个字段及其数据,必须直接更新文档,通过向要移除的字段传递 null
值来实现。
定义 PATCH ${TYPESENSE_HOST}/collections/:collection
WARNING
模式更新操作是一个同步的阻塞操作。
当模式更新进行时,对该特定集合的所有写入操作都将等待模式更新完成。 在多节点高可用集群中,模式更新会在集群所有节点上并行执行。因此在整个集群中,模式更新期间写入操作都会被阻塞。 鉴于此,我们建议一次只更新一个字段,特别是对于大型集合和在非高峰期进行更新。
读取操作会正常进行,不会被阻塞。
或者,您也可以使用别名功能来实现零停机时间的模式变更。
WARNING
一个集群同一时间只能进行一个修改操作。
由于修改操作可能耗时较长,这确保了具有较短默认超时时间的客户端不会重复尝试相同的修改请求。
更新操作包含一个初始验证步骤,会评估磁盘上的记录以确保它们与提议的模式变更兼容。例如,假设有一个字符串类型的字段A
已经存在于磁盘上的文档中,但尚未包含在模式中。如果您尝试通过添加一个类型为integer
的字段A
来更新集合模式,验证步骤将拒绝此变更,因为它与现有数据的类型不兼容。
如果验证成功,则会执行实际的模式变更,并根据请求对记录进行索引/重新索引/删除。API调用返回时即表示操作完成(请确保使用较大的客户端超时值)。由于更新的阻塞特性,我们建议在非高峰期进行变更。
或者,您也可以使用别名功能来实现零停机时间的模式变更。
# 修改现有字段
由于 Typesense 目前仅支持添加/删除字段,任何对现有字段的修改都应通过先删除再添加的操作来实现。除 id
字段外,所有其他字段均可修改。
例如,要为 company_category
字段添加 facet
属性,我们可以在同一个变更集中执行删除+添加操作:
curl "http://localhost:8108/collections/companies" \
-X PATCH \
-H "Content-Type: application/json" \
-H "X-TYPESENSE-API-KEY: ${TYPESENSE_API_KEY}" \
-d '{
"fields": [
{"name": "company_category", "drop": true },
{"name": "company_category", "type": "string", "facet": true }
]
}'
# 获取模式变更状态
您可以通过模式变更端点检查正在进行的模式变更操作状态。
如果没有进行中的模式变更,您将收到空响应。当有模式变更进行时,您会获取到操作详情:
响应内容显示:
- 正在修改的集合名称
- 已根据新模式验证的文档数量
- 已完成修改的文档数量
接口定义 GET ${TYPESENSE_HOST}/operations/schema_changes
# 使用别名
如需实现零停机时间的 schema 变更,您也可以完全重新创建包含更新后 schema 的集合,并利用集合别名功能无缝切换到新集合:
假设您有一个名为 movies_jan_1
的集合需要修改其 schema。
- 首先创建一个别名指向您的集合。例如:创建名为
movies
的别名指向movies_jan_1
。在应用程序中使用此集合别名来搜索/索引集合中的文档。 - 创建一个带有更新后 schema 的新时间戳集合。例如:
movies_feb_1
。 - 在应用程序中临时同时向旧集合和新集合并行写入数据 - 即双写模式。例如:将写入同时发送到
movies_jan_1
集合和新的movies_feb_1
集合。 - 现在,将主数据库中的数据 upsert 到新集合中。例如
movies_feb_1
。 - 更新集合别名使其指向新集合。例如:将
movies
更新为指向movies_feb_1
。 - 停止应用程序向旧集合发送写入,并删除旧集合,在我们的示例中是
movies_jan_1
。
一旦更新别名后,所有搜索/索引操作将自动转到新集合(例如 movies_feb_1
),而无需在应用端进行任何额外更改。
# 动态字段添加
如果只需要动态地向 schema 添加新字段,我们建议在创建集合时使用自动 schema 检测。您可以通过定义正则表达式字段名,当包含匹配该正则表达式的新字段名的文档传入时,新字段将自动添加到 schema 中。