不部署向量数据库,Java 向量搜索怎么用 sqlite-vec 跑起来?
【免费下载链接】sqlite-vecA vector search SQLite extension that runs anywhere!项目地址: https://gitcode.com/GitHub_Trending/sq/sqlite-vec
写给正在评估向量搜索方案的 Java 工程师:sqlite-vec 是一个 SQLite 向量扩展,通过 JDBC 就能在本地文件上完成建表、写入与相似性搜索,不用部署独立向量数据库。
Java 团队为什么需要本地向量检索
多数团队听到"向量搜索"的第一反应是上 Milvus 或 Qdrant。但有三类场景让这套选择变得别扭。
一是运维成本。中小团队没有专职搜索基础设施,为三十万条嵌入多养一个独立服务,太重。二是环境受限。内网、离线,或者需要离线相似性搜索的桌面工具,根本装不下外部服务——数据就在本地磁盘,检索逻辑也该贴着它走。三是规模错配。百万向量以内,暴力全量扫描就是毫秒级,ANN 索引那套重型武器用不上。
sqlite-vec 就是一个无第三方依赖的单 C 文件,编译成可加载扩展后,一条load_extension语句即可挂进你现有的 SQLite 数据库,和业务表共用同一个 .db。Java 侧走的就是常规 JDBC,没有新协议、新客户端,运维清单上也不多一个组件。
一条链路跑通 vec0 建表到 MATCH 查询
我跑一次完整检索。前提是先拿到vec0.so:git clone https://gitcode.com/GitHub_Trending/sq/sqlite-vec,然后执行./scripts/vendor.sh && make loadable,产物在dist/下。
连接、加载扩展、建表。vec0 是虚拟表,向量列声明为FLOAT[n],加+前缀的辅助列单独存放,查出来不用额外 JOIN:
Connection conn = DriverManager.getConnection("jdbc:sqlite:kb.db"); org.sqlite.SQLiteConnection sc = (org.sqlite.SQLiteConnection) conn; sc.enableLoadExtension(true); sc.loadExtension("vec0"); // 在当前工作目录找 vec0.so conn.createStatement().execute( "CREATE VIRTUAL TABLE chunks USING vec0(" + "doc_id INTEGER, embedding FLOAT[384], +content TEXT)");写入用批处理。嵌入以 JSON 数组字符串传入,一个事务里攒 500 条再落盘:
String sql = "INSERT INTO chunks(rowid, doc_id, embedding, content) VALUES (?,?,?,?)"; try (PreparedStatement ps = conn.prepareStatement(sql)) { for (int i = 0; i < 500; i++) { ps.setInt(1, i); ps.setInt(2, 1000 + i); ps.setString(3, vecToJson(embeds[i])); // "[0.1, 0.2, ...]" ps.setString(4, "文档正文 " + i); ps.addBatch(); } ps.executeBatch(); }检索就是MATCH加k,distance列自动返回:
String q = "SELECT doc_id, content, distance FROM chunks " + "WHERE embedding MATCH ? AND k = 5 ORDER BY distance"; try (PreparedStatement ps = conn.prepareStatement(q)) { ps.setString(1, vecToJson(queryVec)); ResultSet rs = ps.executeQuery(); while (rs.next()) { System.out.println(rs.getInt(1) + " -> " + rs.getFloat(3)); } }vec0 的完整语法,包括元数据列和分区键的差别,写在 site/features/vec0.md,调表结构时对着看。
三种部署形态:嵌入式、多进程共享、容器化
| 形态 | 适用场景 | 关键注意点 |
|---|---|---|
| 进程内嵌入 | 桌面工具、本地检索服务 | 单连接写入,管好文件权限 |
| 多进程共享 | 多个 JVM 读同一个库 | 开 WAL,写入方严格单一 |
| 容器化 | CI 与 serverless 临时检索 | 挂 volume,别随容器丢库 |
嵌入式是主流姿势:Spring Boot 服务加一个 .db 文件。多进程共享要小心 SQLite 的写锁,读端随便开连接,写端收口到一个进程即可。容器化重点在持久化,db 文件和vec0.so都放到挂载卷或镜像里。
批大小卡在 500:写入吞吐与查询精度的取舍
vec0 基础表是暴力全量扫描,不是 ANN 索引。精度永远是 100%,你只拿规模和延迟做交换。
两个我实际用的数:写入侧,批大小 500 一次executeBatch,全程单事务——对比逐条 autocommit,三十万行、384 维的墙钟时间能短一个数量级,批开到 1000 以上反而不再快,内存峰值还涨。查询侧,维度压在 768 以内、k取 10,单文件百万行以内 p95 在几十毫秒是正常水平;如果 WHERE 里带元数据过滤,k放大到 2~3 倍,防止候选被过滤没。
需要缩小搜索范围时用 partition key 分区,注意每个 key 值要对应几百到上千条向量,过度分片会让 KNN 更慢而不是更快。
高频踩坑速查:加载失败、驱动版本、内存占用
load_extension报 not authorized→ xerial JDBC 默认禁用扩展加载,老版本驱动支持也不稳 → 先调enableLoadExtension(true),驱动用 3.45 以上版本。
加载后建表报错→.so路径不对或架构不匹配(x86 与 arm64 混用)→ 放到工作目录,确认扩展由同平台编译。
MATCH 参数报错或查不出结果→ 直接把float[]或字节序列塞了进去 → 必须是 JSON 数组字符串。
内存随连接数上涨→ 每个连接独立加载扩展并持有影子表 → 共享单连接或严格复用池化连接,用完即还。
行数上来后延迟变差→ 暴力全扫是必然代价 → 百万以内直接扛,超了就上分区键,或评估扩展里的 ivf、diskann 表。
先用一万条真实嵌入跑个 POC,把 p95 延迟和内存峰值量出来再下结论。验证通过后,把vec0.so随镜像或发布包一起交付,db 文件留在本地磁盘。sqlite-vec 的表结构与量化选项仍在演进,升级版本前先看一眼变更说明。
【免费下载链接】sqlite-vecA vector search SQLite extension that runs anywhere!项目地址: https://gitcode.com/GitHub_Trending/sq/sqlite-vec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考