news 2026/9/12 9:26:26

Turso 向量搜索完全指南:在 SQLite 兼容的 Rust 数据库中构建语义检索应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Turso 向量搜索完全指南:在 SQLite 兼容的 Rust 数据库中构建语义检索应用

Turso 向量搜索完全指南:在 SQLite 兼容的 Rust 数据库中构建语义检索应用

【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso

向量搜索是语义检索、推荐系统和 AI 应用的关键基础设施。Turso(一个用 Rust 实现的、SQLite 兼容的 SQL 数据库)原生支持向量操作,让你可以用纯 SQL 完成相似度检索,而无需引入独立的向量数据库。本文以 cli/manuals/vector.md 为骨架,结合仓库源码深入讲解 Turso 的向量类型、存储格式、距离函数与完整实战方案——读完你将掌握在 Turso 中建表、写入嵌入向量、执行 L2/余弦相似度检索、进行阈值过滤以及搭建端到端语义搜索应用的完整能力。

一、总览:Turso 向量能力的定位与当前限制

Turso 为构建相似度搜索与语义搜索应用提供完整的向量操作支持。向量以 BLOB 形式存储,可通过距离函数检索相似项,全部在 SQL 层完成,无需额外服务。

重要限制(当前版本):向量索引尚未正式支持,所有向量检索目前都采用暴力扫描(brute-force scan),即搜索耗时随行数线性增长。这意味着在数据量较大时,查询成本会显著上升,需要结合预过滤、降维等手段控制开销(详见性能考量一节)。

从源码结构看,仓库中已出现实验性的向量索引实现 core/index_method/toy_vector_sparse_ivf.rs,但本文以官方手册描述的行为为准:生产查询走全表扫描。

二、向量类型:vector32 与 vector64 的取舍

Turso 支持两种向量格式:

类型精度每维度字节数典型场景
vector3232 位浮点(f32)4 字节大多数嵌入模型(如 OpenAI ada-002 输出的 1536 维向量)
vector6464 位浮点(f64)8 字节对精度要求极高、维度较少的场景

从源码看,vector32vector64分别对应 core/vector/mod.rs 中的vector32()vector64()函数,它们将输入解析后转换为对应的VectorTypeFloat32Dense/Float64Dense)并序列化为 BLOB 值返回。内存占用差异是选型的关键:同样的维度,vector64占用空间是vector32的两倍(8 字节/维 vs 4 字节/维),暴力扫描时带宽消耗也翻倍。

值得补充的是,从源码中的类型枚举 core/vector/vector_types.rs 与函数注册表 core/dialect/sqlite.rs 看,Turso 底层还支持Float32Sparse(稀疏向量)、Float1Bit(1 比特量化)与Float8(8 比特量化)等额外类型,并注册了vector32_sparsevector1bitvector8等对应函数。这些类型面向更激进的压缩场景,但当前文档主推 dense 的vector32/vector64,日常开发以这两者为准即可。

三、创建与存储向量

向量存储在普通表的向量列中,磁盘上表现为 BLOB。嵌入向量在运行时被解析和校验:一个合法嵌入要么是浮点数值组成的 JSON 数组(文本),要么是由 Turso 向量函数vector32/vector64生成的二进制 BLOB

3.1 基础示例

-- 创建带向量嵌入的表 CREATE TABLE documents ( id INTEGER PRIMARY KEY, content TEXT, embedding BLOB -- 向量以 BLOB 存储 ); -- 使用 vector32() 或 vector64() 写入向量 INSERT INTO documents VALUES (1, 'Introduction to databases', vector32('[0.1, 0.2, 0.3, 0.4]')), (2, 'SQL query optimization', vector32('[0.2, 0.1, 0.4, 0.3]')), (3, 'Vector similarity search', vector32('[0.4, 0.3, 0.2, 0.1]'));

3.2 输入校验规则(源码级细节)

vector32(text)这类构造函数并非直接存储文本,而是走了一条严格的解析管线。入口parse_vector(见 core/vector/mod.rs)会根据值的类型分流:

  • Text 类型:交给operations::text::vector_from_text解析(实现见 core/vector/operations/text.rs)。解析要求文本以[开头、以]结尾,内部逗号分隔浮点数;任何 token 解析失败或出现NaNInfinity等非有限值时,都会直接报Invalid vector value错误,而不会静默写入。相邻元素间的空格(如[ 1.0 , 2.0 ])是允许的。
  • Blob 类型:直接按二进制格式解析(Vector::from_slice),用于读取已存储的向量。

因此,向vector32传入格式错误的字符串、非有限数值或错误类型,都会得到明确的转换错误(ConversionError)。这也是"嵌入在运行时被验证"这一表述的源码依据。

3.3 BLOB 的磁盘格式

了解 BLOB 布局有助于排查存储问题。序列化逻辑见 core/vector/operations/serialize.rs:

  • vector32(dense f32):连续dims × 4字节的原始小端浮点数据,无类型字节(偶数长度 blob 直接被识别为 f32);
  • vector64(dense f64)dims × 8字节数据 + 末尾 1 个类型字节0x02
  • 其他类型(稀疏、1bit、f8)同样在数据尾部附加类型标记字节,类型判定逻辑集中在 core/vector/vector_types.rs 的Vector::vector_type中(奇数长度 blob 通过尾部字节区分,其中1表示 f32、2表示 f64、3表示 1bit、4表示 f8、9表示稀疏)。

3.4 处理高维嵌入

真实世界的嵌入通常有数百甚至上千维度:

-- 1536 维嵌入示例(如 OpenAI 的 ada-002) CREATE TABLE embeddings ( id INTEGER PRIMARY KEY, text TEXT, vector BLOB ); -- 写入 1536 维向量 INSERT INTO embeddings VALUES (1, 'Sample text', vector32('[0.001, 0.002, ..., 0.1536]'));

高维向量的空间开销是实打实的:1536 维vector32一个向量就占约 6 KB(1536 × 4 字节)。写入时务必通过应用侧生成与目标模型维度一致的嵌入,否则运行时会因维度不匹配报错(见下文距离函数说明)。

四、向量函数全览

4.1 创建函数

  • vector32(text)— 由 JSON 数组文本创建 32 位浮点向量
  • vector64(text)— 由 JSON 数组文本创建 64 位浮点向量

两者都要求恰好一个参数(源码中args.len() != 1会直接报错,见 core/vector/mod.rs),返回的是可直接存入 BLOB 列的向量值。

4.2 距离函数

  • vector_distance_l2(v1, v2)— 欧几里得(L2)距离,值越小越相似
  • vector_distance_cos(v1, v2)— 余弦距离(1 − 余弦相似度),文本嵌入的常用选择

从实现看(core/vector/operations/distance_l2.rs 与 core/vector/operations/distance_cos.rs),距离计算前有两条硬性校验,不满足即报ConversionError

  1. 维度必须相同v1.dims != v2.dims直接报错);
  2. 类型必须相同(如 f32 dense 与 f64 dense 不能混算)。

计算本身做了优化:dense 向量在启用simd特性且非 WASM、非 Windows AArch64 平台下,通过 SimSIMD 库加速(f32::euclidean/f32::cosine);其余平台回退到纯 Rust 实现。测试还验证了 SIMD 路径与 Rust 回退路径在容差内一致(见prop_vector_distance_l2_rust_vs_simsimd_f32等 quickcheck 测试)。稀疏向量则使用双指针归并算法,只对非零下标求差,避免了把稀疏向量膨胀为 dense 的开销。

4.3 工具函数

  • vector_extract(blob)— 将向量 BLOB 还原为 JSON 文本
  • vector_concat(v1, v2)— 拼接两个向量
  • vector_slice(v, start, end)— 截取向量的一部分

vector_extract要求入参必须是 Blob(否则报Expected blob value),空 blob 返回[],非空则解析后通过vector_to_text格式化为 JSON 数组文本(见 core/vector/mod.rs)。vector_slicestart/end必须是非负整数(负数直接报InvalidArgument),且语义为闭区间(见 core/vector/mod.rs)。这些函数在方言层均有注册(core/dialect/sqlite.rs),并映射到 core/function.rs 中的VectorFunc枚举,由 VDBE 在 SQL 执行时统一分发调用。

五、相似度搜索实战

5.1 查找相似文档(按 L2 距离排序)

-- 找出与查询向量最相似的文档 WITH query AS ( SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector ) SELECT id, content, vector_distance_l2(embedding, query_vector) AS distance FROM documents, query ORDER BY distance LIMIT 5;

这里用 CTE 将查询向量只计算一次,再与全表做笛卡尔积逐行计算距离,最后按距离升序取前 5 条——这就是典型暴力扫描的写法。

5.2 余弦相似度检索

文本嵌入通常优先用余弦距离,因为它对向量模长不敏感:

-- 用余弦距离找语义相近的文档 WITH query AS ( SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector ) SELECT id, content, vector_distance_cos(embedding, query_vector) AS cosine_distance FROM documents, query ORDER BY cosine_distance LIMIT 5;

注意vector_distance_cos返回的是距离而非相似度:值域为 [0, 2],0 表示方向完全一致,2 表示方向完全相反。从测试用例(core/vector/operations/distance_cos.rs)可以看到,相同向量距离趋近 0,相反向量距离趋近 2,正交向量距离为 1。若业务上需要"相似度",用1 - distance换算即可。

5.3 阈值过滤检索

除了 Top-N,还可以按距离阈值过滤,只保留足够相似的候选:

-- 找出距离阈值内的所有文档 WITH query AS ( SELECT vector32('[0.15, 0.25, 0.35, 0.45]') AS query_vector ) SELECT id, content, vector_distance_l2(embedding, query_vector) AS distance FROM documents, query WHERE vector_distance_l2(embedding, query_vector) < 0.5 ORDER BY distance;

阈值方案特别适合"是否存在近似项"的判断(如重复内容检测)。阈值大小需要结合嵌入模型与业务数据实验确定。

六、向量数据检视与操作

6.1 检视向量内容

-- 将向量 BLOB 还原为 JSON 查看 SELECT id, vector_extract(embedding) AS vector_json FROM documents LIMIT 3;

这在调试嵌入是否正确写入、维度是否符合预期时非常有用——vector_extract会输出[0.1,0.2,0.3,0.4]形式的 JSON 数组文本。

6.2 拼接与切片

-- 拼接两个向量 SELECT vector_concat( vector32('[1.0, 2.0]'), vector32('[3.0, 4.0]') ) AS concatenated; -- 切片:提取第 2 到第 4 维(闭区间) SELECT vector_slice( vector32('[1.0, 2.0, 3.0, 4.0, 5.0]'), 2, 4 ) AS sliced;

vector_concat要求恰好两个参数,拼接结果仍是合法的向量 BLOB,可继续参与距离计算。vector_slice则常用于提取子空间特征或做降维实验(例如从 1536 维中截取前 256 维快速验证效果)。

七、构建端到端语义搜索应用

下面是一个完整的语义搜索应用 SQL 方案,覆盖建表、写数据、检索三个环节:

-- 1. 创建 schema CREATE TABLE articles ( id INTEGER PRIMARY KEY, title TEXT, content TEXT, embedding BLOB ); -- 2. 写入预计算的嵌入 INSERT INTO articles VALUES (1, 'Database Fundamentals', 'An introduction to relational databases...', vector32('[0.12, -0.34, 0.56, ...]')), (2, 'Machine Learning Basics', 'Understanding neural networks and deep learning...', vector32('[0.23, 0.45, -0.67, ...]')), (3, 'Web Development Guide', 'Modern web applications with JavaScript...', vector32('[0.34, -0.12, 0.78, ...]')); -- 3. 检索相似文章 WITH search_embedding AS ( -- 查询向量来自你的嵌入模型对搜索词的计算结果 SELECT vector32('[0.15, -0.30, 0.60, ...]') AS query_vec ) SELECT a.id, a.title, vector_distance_cos(a.embedding, s.query_vec) AS similarity_score FROM articles a, search_embedding s ORDER BY similarity_score LIMIT 10;

整套应用的嵌入生成(embedding model)、入库与查询三个环节中,只有入库查询发生在 Turso 内部:应用侧负责调用嵌入模型把文本/图片转为向量,Turso 负责存储与距离计算。得益于 SQLite 兼容性,这段 SQL 既可以通过 Turso 的 CLI(cli/manuals)执行,也可以嵌入到各语言绑定中使用(如 bindings/rust、bindings/python、bindings/javascript 等目录下的官方驱动)。

八、性能考量与优化技巧

由于向量索引尚未实现,暴力扫描是当前唯一检索路径,务必注意以下几点:

  • 线性扫描:每次检索都会扫描表中所有行,行数翻倍耗时也翻倍;生产环境需对数据规模有清晰预期。
  • 内存与磁盘占用vector32每维度 4 字节,vector64每维度 8 字节;1536 维 f32 向量单条约 6 KB。批量导入前先按行数估算总空间。
  • 优化技巧
    • 尽量使用较小维度:在不明显损失检索质量的前提下,优先低维嵌入;
    • 先用 WHERE 预过滤再算距离:例如按分类、时间、租户等业务字段先缩小候选集,再把距离计算作用于过滤后的行——这能成倍降低扫描开销;
    • 考虑对大数据集分区:将向量按业务维度拆分到多个表,检索时只查相关分区;
    • 优先vector32而非vector64:除非确实需要高精度,否则 f32 在存储与计算带宽上都有明显优势(同时 SIMD 加速路径对 f32 更友好)。

此外,从仓库的基准测试(如 core/benches)可以推断,向量操作在核心引擎层已纳入性能关注范围,实际调优时建议用真实数据规模做压测。

九、常见应用场景

  • 语义搜索:按含义而非关键词找文档,检索质量对同义词、多语言表述更鲁棒;
  • 推荐系统:基于物品/用户的嵌入向量找相似项;
  • 重复内容检测:通过距离阈值识别近似重复的文本或条目;
  • 图像相似度检索:用视觉模型生成的嵌入搜索相似图片;
  • 异常检测:在高维空间中定位远离簇中心的离群点。

十、进一步阅读

想深入源码或扩展能力,可继续探索:

  • 向量模块入口 — 全部 SQL 向量函数的 Rust 实现
  • 向量类型与 BLOB 格式 — 类型识别、维度校验、稀疏/量化格式
  • 文本解析与序列化 — 嵌入文本的解析与 JSON 输出
  • BLOB 序列化 — 各类向量落盘的字节布局
  • L2 距离实现 / 余弦距离实现 — 含 SIMD 加速与边界条件测试
  • 函数注册表 与 函数枚举 — 向量函数如何进入 SQL 方言
  • 官方手册 — 本文的原始依据

【免费下载链接】tursoA SQL database in Rust: SQLite-compatible, now also speaking Postgres (experimental). The LLVM of databases.项目地址: https://gitcode.com/GitHub_Trending/tu/turso

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 9:26:23

RAG端到端信息流设计:政务场景下的切块、Embedding与多路召回实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:26:20

LLC电源调试:欠谐振与过谐振的波形判断与ZVS实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:25:44

Crayfish容器版:桌面智能体的可编程服务总线实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 9:24:45

openPangu-2.0-Pro:昇腾原生大模型的工业级落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华