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 支持两种向量格式:
| 类型 | 精度 | 每维度字节数 | 典型场景 |
|---|---|---|---|
vector32 | 32 位浮点(f32) | 4 字节 | 大多数嵌入模型(如 OpenAI ada-002 输出的 1536 维向量) |
vector64 | 64 位浮点(f64) | 8 字节 | 对精度要求极高、维度较少的场景 |
从源码看,vector32与vector64分别对应 core/vector/mod.rs 中的vector32()与vector64()函数,它们将输入解析后转换为对应的VectorType(Float32Dense/Float64Dense)并序列化为 BLOB 值返回。内存占用差异是选型的关键:同样的维度,vector64占用空间是vector32的两倍(8 字节/维 vs 4 字节/维),暴力扫描时带宽消耗也翻倍。
值得补充的是,从源码中的类型枚举 core/vector/vector_types.rs 与函数注册表 core/dialect/sqlite.rs 看,Turso 底层还支持Float32Sparse(稀疏向量)、Float1Bit(1 比特量化)与Float8(8 比特量化)等额外类型,并注册了vector32_sparse、vector1bit、vector8等对应函数。这些类型面向更激进的压缩场景,但当前文档主推 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 解析失败或出现NaN、Infinity等非有限值时,都会直接报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:
- 维度必须相同(
v1.dims != v2.dims直接报错); - 类型必须相同(如 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_slice的start/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),仅供参考