SQLite-Vec
开源(GitHub,7.9K Star,335 Fork)用C+Python编写、零依赖SQLite向量搜索插件,以vec0虚拟表为核心,采用分块存储和暴力KNN搜索策略,在10万级向量规模下可实现亚百毫秒延迟。
前身是sqlite-vss,2K Star,73 Fork,依赖Facebook的Faiss C++库,存在问题:仅支持Linux和macOS、整个向量索引必须驻留内存、事务语义存在缺陷(Faiss每次提交需要完整重写索引)、编译极为困难,且二进制文件体积达3~5MB。
SQLite-Vec,纯C实现意味着二进制仅几百KB,能编译到Linux、macOS、Windows、WASM(浏览器)、iOS、Android、树莓派等所有目标平台。事务完全遵循SQLite的ACID语义,INSERT/UPDATE/DELETE均支持原子操作和正确回滚。
定位:嵌入式/本地优先应用、边缘AI、单文件部署、原型开发、数据集在10万到50万向量以内的生产场景。
对比SQLite-Vector
注意:和嵌入式向量数据库:Zvec、ObjectBox、SQLite-Vector、SatoriDB里提到的SQLite-Vector,是两个不同的项目,前者是SQLite-AI官方实现。
| 对比维度 | sqlite-vec | sqlite-vector |
|---|---|---|
| 源码风格 | 单体C文件(amalgamation) | 模块化多文件(按ISA分离) |
| 存储方式 | vec0虚拟表 | 普通表BLOB字段 |
| 向量类型 | float32、int8、bit | float32/16/bf16 + int8/uint8/1bit + TurboQuant |
| SIMD覆盖 | AVX(内联在主体中) | AVX2、AVX512、NEON、RVV、SSE2(独立文件) |
| 索引策略 | 默认ANN(HNSW/IVF实验性) | 默认精确搜索,HNSW可选 |
| 预索引 | 需要 | 不需要 |
| 内存 | 极小(无额外开销) | 默认30MB |
| 目标场景 | 轻量级、嵌入式、浏览器WASM | 移动端、边缘AI、生产级 |
| 成熟度 | pre-v1(破坏性变更中) | 生产级(稳定) |
架构
当执行CREATE VIRTUAL TABLE ... USING vec0(...)时,扩展会在SQLite中创建一组影子表(shadow tables)来管理数据,其架构与SQLite内置FTS5全文搜索高度类似。
vec0虚拟表对应以下内部表:
| 影子表 | 功能 |
|---|---|
{table}_chunks | 记录每个分块的元数据(大小、序列号、分区键值) |
{table}_rowids | 将用户rowid映射到内部分块位置 |
{table}_vector_chunksNN | 每个向量列一张,存储原始向量数据BLOB |
{table}_metadata_chunksNN | 每个元数据列一张,存储元数据值(支持位图过滤) |
{table}_auxiliary | 存储辅助列数据,非索引,仅用于SELECT |
{table}_info | 键值对形式的表配置信息 |
向量不是逐行存储的,而是被分组到固定大小的分块(chunk)中。每个分块默认包含8192条向量,以连续BLOB形式紧凑排列。
两个核心优势:
- KNN查询时逐块从磁盘读取,无需将全部数据载入内存;
- 分块内的连续布局可改善CPU缓存命中率。每个分块维护一个有效性位图(validity bitmap),删除操作仅清除位图标记而非立即回收空间。
vec0表支持四种列类型,SQL示例:
-- 创建带完整列类型的表CREATEVIRTUALTABLEvec_articlesUSINGvec0(article_idINTEGERPRIMARYKEY,-- 主键headline_embeddingfloat[384],-- 向量列(float32/int8/bit)yearINTEGERPARTITIONKEY,-- 分区键:内部分片,加速过滤查询categoryTEXT,-- 元数据列:可出现在KNN的WHERE中word_countINTEGER,-- 元数据列+headlineTEXT,-- 辅助列(+前缀):非索引,仅返回+urlTEXT-- 辅助列);-- 带元数据过滤的KNN查询SELECTarticle_id,headline,url,distanceFROMvec_articlesWHEREheadline_embeddingMATCH:query_embeddingANDk=10ANDyear=2025ANDcategoryIN('Technology','Science')ANDword_count>=500ORDERBYdistance;分区键是性能优化的利器,按分区值对分块进行分片,KNN查询过滤分区键时可直接跳过不匹配的分块,实测可带来3倍以上加速。元数据列支持=、!=、>、>=、<、<=、IN (...)操作符,在KNN搜索过程中通过位图进行高效预过滤。辅助列存储在独立表中,适合放置大文本或URL等不参与搜索的数据。
检索算法与距离计算
穷举暴力(brute-force)KNN搜索,查询执行流程:
- SQLite查询规划器调用
xBestIndex,检测到向量列上的MATCH约束,选择KNN查询计划 xFilter/xNext遍历所有分块,如果存在分区键约束,跳过不匹配的分块- 对每个分块:读取向量BLOB→应用元数据过滤位图→计算查询向量与每个有效向量的距离
- 维护top-K最小堆,最终返回距离最近的K个结果
执行向量相似性搜索时,遵循特定的处理流程:
距离度量
| 函数 | 度量 | 适用类型 | 说明 |
|---|---|---|---|
vec_distance_L2(a, b) | L2欧氏距离 | float32、int8 | vec0 KNN默认距离 |
vec_distance_cosine(a, b) | 余弦距离(1-余弦相似度) | float32、int8 | 0=相同,2=完全相反 |
vec_distance_hamming(a, b) | 汉明距离 | bit | bit向量默认距离 |
距离计算使用硬件加速:x86/x64平台上使用AVX/AVX2指令集(_mm256_loadu_ps等),每次循环处理16个float32元素;ARM平台使用NEON指令集。可通过SELECT vec_debug()查看编译时启用哪些SIMD特性。
量化与降维
当数据集超过10万向量时,暴力搜索的延迟会线性增长,提供两种核心优化手段。
二进制量化(Binary Quantization)将每个float32维度压缩为1bit(正值→1,非正值→0),实现32倍存储压缩和约10倍查询加速,代价是5-10%的召回率损失。某些嵌入模型(如mxbai-embed-large-v1、nomic-embed-text-v1.5)专门针对二进制量化训练,损失更小。
-- 将float32向量二进制量化SELECTvec_quantize_binary('[0.3, -0.1, 0.8, -0.5, 0.2, -0.9, 0.1, 0.7]');-- 返回X'A5'(10100101)-- 创建bit向量表CREATEVIRTUALTABLEvec_binaryUSINGvec0(embeddingbit[768]);Matryoshka降维利用支持截断的嵌入模型(如OpenAI的text-embedding-3-small),将高维向量截断到较低维度:
-- 将 1536 维截断到 512 维,再归一化INSERTINTOvec_short(rowid,embedding)SELECTrowid,vec_normalize(vec_slice(embedding,0,512))FROMvec_full;两种技术可叠加使用:先截断到512维,再二进制量化到bit[512],实现极致压缩。
| 数据规模 | 推荐策略 |
|---|---|
| 万向量 | 直接使用float32,任意维度,查询近乎即时 |
| 1-10万向量 | float32+分区键用于过滤查询 |
| 10-50万向量 | 二进制量化或Matryoshka降维,配合分区键 |
| 50-100万向量 | 二进制量化必选,评估延迟是否可接受 |
>100万向量 | 建议迁移到PGVector或专用向量数据库 |
关键调优手段包括:
- 启用WAL模式(
PRAGMA journal_mode=WAL)提升写入吞吐; - 设置
PRAGMA mmap_size将数据库文件内存映射以获得接近内存的读取速度; - 批量插入务必包裹在单一事务中,SQLite每秒仅处理几十次事务提交但可处理数万次插入。
陷阱:KNN搜索在vec0虚拟表内先于JOIN和外部WHERE条件执行。如果你用JOIN关联外部表并在WHERE中过滤,这些过滤不会减少KNN的搜索范围。正确做法是将过滤条件放入vec0表的元数据列中,或设置较大k值进行过量检索后在应用层做二次过滤。
当前已知限制:
- 元数据列不支持
LIKE、REGEXP、NULL值; - 频繁删除插入后数据库文件膨胀,
VACUUM无法完全回收空间; - 继承SQLite的单写者限制。
实战
提供多语言SDK
pipinstallsqlite-vecnpminstallsqlite-vec# Rubygeminstallsqlite-veccargoaddsqlite-vec go get-ugithub.com/asg017/sqlite-vec-go-bindings/cgoPython
Python SDK示例:
importsqlite3importstructimportsqlite_vecdefserialize_f32(vector:list[float])->bytes:"""将浮点数列表序列化为紧凑的二进制格式"""returnstruct.pack("%sf"%len(vector),*vector)# 初始化数据库并加载扩展db=sqlite3.connect(":memory:")db.enable_load_extension(True)sqlite_vec.load(db)db.enable_load_extension(False)# 打印版本信息sqlite_version,vec_version=db.execute("select sqlite_version(), vec_version()").fetchone()print(f"sqlite_version={sqlite_version}, vec_version={vec_version}")# 创建vec0虚拟表,4维float32向量db.execute("CREATE VIRTUAL TABLE vec_items USING vec0(embedding float[4])")# 插入向量数据items=[(1,[0.1,0.1,0.1,0.1]),(2,[0.2,0.2,0.2,0.2]),(3,[0.3,0.3,0.3,0.3]),(4,[0.4,0.4,0.4,0.4]),(5,[0.5,0.5,0.5,0.5]),]withdb:forrow_id,vecinitems:db.execute("INSERT INTO vec_items(rowid, embedding) VALUES (?, ?)",[row_id,serialize_f32(vec)],)# KNN查询:找到最近的3个向量query=[0.3,0.3,0.3,0.3]rows=db.execute(""" SELECT rowid, distance FROM vec_items WHERE embedding MATCH ? ORDER BY distance LIMIT 3 """,[serialize_f32(query)]).fetchall()forrow_id,distinrows:print(f"rowid={row_id}, distance={dist}")语义搜索
importsqlite3,struct,sqlite_vecfromopenaiimportOpenAI client=OpenAI()defserialize_f32(v):returnstruct.pack("%sf"%len(v),*v)defget_embedding(text):returnclient.embeddings.create(input=text,model="text-embedding-3-small").data[0].embedding# 建库db=sqlite3.connect("semantic.db")db.enable_load_extension(True)sqlite_vec.load(db)db.execute("CREATE TABLE docs (id INTEGER PRIMARY KEY, content TEXT)")db.execute("CREATE VIRTUAL TABLE vec_docs USING vec0(embedding float[1536])")# 存入文档documents=["广义相对论描述引力是时空弯曲的几何效应","量子力学揭示微观世界的概率本质","深度学习通过多层神经网络学习数据的层次化表示",]fori,docinenumerate(documents):emb=get_embedding(doc)db.execute("INSERT INTO docs VALUES (?, ?)",[i+1,doc])db.execute("INSERT INTO vec_docs(rowid, embedding) VALUES (?, ?)",[i+1,serialize_f32(emb)])db.commit()# 语义搜索query_emb=get_embedding("什么是引力?")results=db.execute(""" SELECT d.content, v.distance FROM vec_docs v JOIN docs d ON d.id = v.rowid WHERE v.embedding MATCH ? ORDER BY v.distance LIMIT 3 """,[serialize_f32(query_emb)]).fetchall()forcontent,distanceinresults:print(f"[{distance:.4f}]{content}")Java
以Windows开发环境为例,在GitHub Release页面下载稳定版或alpha版本,我下载的是sqlite-vec-0.1.10-alpha.4-loadable-windows-x86_64.tar.gz,打开Git Bash,执行命令tar zxvf sqlite-vec-0.1.10-alpha.4-loadable-windows-x86_64.tar.gz,得到vec0.dll文件。
引入最新版依赖:
<dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.53.2.0</version><scope>compile</scope></dependency>直接给出demo源码:
publicclassDemoSQLiteVec{publicstaticvoidmain(String[]args){// SQLite连接字符串,建立向量数据库Stringurl="jdbc:sqlite:.\\sqlite_vec.db";// 创建向量表StringcreateTableSql="create virtual table IF NOT EXISTS vec_demo using vec0(sample_embedding float[8])";StringinsertDataSql="insert into vec_demo(rowId, sample_embedding) "+"values"+"(1, '[-0.200, 0.250, 0.341, -0.211, 0.645, 0.935, -0.316, -0.924]'),"+"(2, '[0.443, -0.501, 0.355, -0.771, 0.707, -0.708, -0.185, 0.362]'),"+"(3, '[0.716, -0.927, 0.134, 0.052, -0.669, 0.793, -0.634, -0.162]'),"+"(4, '[-0.710, 0.330, 0.656, 0.041, -0.990, 0.726, 0.385, -0.958]')";StringselectSql="select rowId, distance from vec_demo "+"where sample_embedding match '[0.890, 0.544, 0.825, 0.961, 0.358, 0.0196, 0.521, 0.175]' "+"order by distance limit 2";// 数据内容表,只能通过rowId来进行关联StringcreateTableSql2="CREATE TABLE IF NOT EXISTS vec_metadata (rowId INTEGER PRIMARY KEY, describe TEXT, label TEXT)";StringinsertDataSql2="insert into vec_metadata(rowId, describe, label) "+"values"+"(1,'数据描述1','数据标签1'),"+"(2,'数据描述2','数据标签2'),"+"(3,'数据描述3','数据标签3'),"+"(4,'数据描述4','数据标签4')";// 数据内容表,只能通过rowId来进行关联查询。vec_demo的查询必须是独立子查询,否则总是会报错// [SQLITE_ERROR] SQL error or missing database (A LIMIT or 'k = ?' constraint is required on vec0 knn queries.)StringselectSql2="SELECT ve.rowId, ve.sample_embedding, vm.describe, vm.label "+"FROM (SELECT rowId, sample_embedding, distance "+" FROM vec_demo "+" WHERE sample_embedding MATCH '[0.890, 0.544, 0.825, 0.961, 0.358, 0.0196, 0.521, 0.175]' "+" ORDER BY distance LIMIT 2) ve "+"JOIN vec_metadata vm ON ve.rowId = vm.rowId ";// 创建sqlite配置对象,启用加载扩展功能SQLiteConfigconfig=newSQLiteConfig();config.enableLoadExtension(true);// 使用配置初始化数据库连接try(Connectionconn=DriverManager.getConnection(url,config.toProperties());Statementstmt=conn.createStatement()){// 加载sqlite-vec扩展库,load_extension方法只能识别绝对路径stmt.execute("SELECT load_extension('D:\\Program\\vec0.dll')");// 建表stmt.execute(createTableSql);// 插入数据stmt.execute(insertDataSql);// 查询数据ResultSetrs=stmt.executeQuery(selectSql);// 打印结果while(rs.next()){intid=rs.getInt("rowId");floata=rs.getFloat("distance");System.out.println("Row ID: "+id+" distance: "+a);}// 创建关联信息表、插入数据并查询stmt.execute(createTableSql2);stmt.execute(insertDataSql2);ResultSetrs2=stmt.executeQuery(selectSql2);while(rs2.next()){intid=rs2.getInt("rowId");Stringdescribe=rs2.getString("describe");Stringlabel=rs2.getString("label");System.out.println("Row ID: "+id+" describe: "+describe+" label: "+label);}}catch(Exceptione){log.error("error",e);}}}解读:
- 使用
sqlite-vec,最少需创建2张表。1张虚拟向量表,只存储rowid和向量信息。另一张表,存储rowid和文本信息,或其它标签信息。检索时,需要进行双表关联检索才能得到想要的信息。 - 双表关联的语法要求:查询虚拟向量表时,必须是单表查询,且必须指定
limit记录数。
拓展
项目作者还提供两个用于生成文本嵌入(Text Embedding)的项目,配合sqlite-vec使用:
- sqlite-rembed:基于远程API(OpenAI、Nomic、Ollama等)生成文本嵌入;
- sqlite-lembed:基于
.gguf格式的本地嵌入模型生成文本嵌入。