前阵子我写了个小程序,想实现个图片搜索功能——就是那种上传一张狗的照片,能从图库里找出所有长得像的狗。我以为这事很简单,毕竟我数据库里存了图片的标签,比如“金毛”、“拉布拉多”,用SQL查一下不就完事了?
结果发现我天真了。
如果我给每张图打10个标签,比如“黄色”、“长毛”、“大耳朵”、“吐舌头”,那我搜“黄色长毛狗”还行,但搜“长得像我家那只混血土狗”就彻底抓瞎。因为我家那只狗耳朵是半立半塌,毛色是黄里带黑,尾巴卷成螺旋状——这些特征根本没法用几个标签说清楚。更别提搜“和这张图氛围相似的图”这种抽象需求,传统数据库直接原地去世。
这就引出了一个问题:为什么我们明明有MySQL、PostgreSQL这些成熟的数据库,还要搞什么向量数据库?它们到底有什么区别?
先说说普通数据库是怎么干活的。你可以把普通数据库想象成一个大号的Excel表格。每一行是一份数据,每一列是数据的属性。比如我要存储水果,我会建一个表,里面有“名字”、“颜色”、“价格”、“产地”这些列。当我查“红色、价格低于10块的水果”时,数据库就在表里一行行扫,把所有颜色=红色且价格<10的行挑出来。这事它能干得飞快,因为我在这些列上建了索引,相当于给Excel加了目录。
但问题来了——如果我问“哪种水果吃起来和苹果差不多”?苹果是什么味道?脆、甜、带点酸、有果香。这怎么用SQL表达?我总不能写WHERE 味道=‘脆甜微酸’吧?就算我能写,数据库也根本不懂“脆”、“甜”、“酸”这些概念之间的关联。它只知道字符串匹配,不知道“脆”和“酥”是近义词,更不知道“微酸”和“酸甜”不是一回事。
这就是普通数据库的底层逻辑:它只处理精确的、结构化的数据。你必须把一切量化成具体的数字或字符串,它才能工作。而现实世界里的东西——声音、图像、气味、人的脸、文章的情感——全是模糊的、连续的、非结构化的。你没法用几个标签把它们描述清楚。
向量数据库就是来解决这个问题的。
它的核心理念是:别用标签描述东西,用数字列表描述。这个数字列表就叫“向量”。比如一张狗的照片,经过某个模型处理后,会变成一个由几百个数字组成的列表,比如[0.23, 0.87, -0.12, 0.45, ...]。这些数字代表这张图片的“特征”——比如第1个数字可能代表“毛发的蓬松程度”,第2个代表“耳朵的直立程度”,第3个代表“鼻子的长度”,等等。当然实际上模型并不会告诉我们每个数字对应什么含义,它们只是一堆抽象的数值,但合在一起就构成了这张图片的“指纹”。
两张图片越像,它们的向量就越接近。模型会把“相似的东西”在数学上映射到相近的位置。比如金毛和拉布拉多的向量可能距离很近,而金毛和哈士奇的向量就远一些。金毛和一台洗衣机的向量?那基本是十万八千里。
所以向量数据库干的事就是:你给我一个向量,我帮你找出库里所有离它最近的向量。这个“距离”可以是欧几里得距离、余弦相似度等等,但本质上就是在做数学运算——算两个向量之间的差异大小。
那它和普通数据库到底有啥不一样?我总结了几点最核心的区别。
第一,查询方式完全不同。普通数据库查的是“等于”、“大于”、“小于”、“包含”这些布尔条件。你问它“有没有颜色是红色且价格小于10的水果”,它回答“有”或“没有”。向量数据库查的是“相似度”。你问它“有没有和这个苹果吃起来像的水果”,它不会给你布尔答案,而是返回一个排名列表:“最像的是梨,相似度98%;其次是海棠果,相似度85%;再是桃子,相似度60%”。它根本不在乎精确匹配,它只在乎“多像”。
第二,索引结构天差地别。普通数据库用B+树、哈希表这些结构。它们擅长处理一维的、有序的数据。比如你按价格排序,B+树就能飞快找到所有价格在某个区间的数据。但向量是几百维的,你没法给几百维的数据建一个简单的B+树。向量数据库用的是HNSW(分层可导航小世界图)、IVF(倒排文件索引)这些听起来就很中二的结构。简单说,HNSW就是把所有向量点建成了一个多层的图网络,每个点只连接离它近的邻居,搜索时从顶层快速跳到底层,就像在社交网络里找人一样——先找到大圈子里的名人,再通过名人找到你想找的人。这个算法在100万条数据里搜最近邻,只需要几百次计算,而如果用暴力遍历,得算100万次。
第三,写入方式不同。普通数据库讲究ACID事务,写入必须严格一致。你往银行系统里转账,必须保证“扣钱”和“加钱”同时成功或同时失败。向量数据库在这块通常比较宽松,因为它面向的场景大多是推荐、搜索、去重——偶尔丢几条相似结果也不会出人命。所以很多向量数据库用近似的算法来加速搜索,牺牲一点点精度换取几十倍的性能提升。比如你用Milvus 2.3版本建索引时,可以选择“IVF_FLAT”这个参数,设定nprobe=10,意思就是只搜索10个最近的聚类区域,而不是全量扫描。这会导致召回率从100%降到95%左右,但查询速度能快20倍。
第四,数据模型不同。普通数据库支持复杂的关联查询,可以JOIN多个表,可以做事务。向量数据库基本上只干一件事:向量搜索。它虽然也支持存一些元数据,比如给每个向量附加一个“类别”字段,然后搜索时加上过滤条件“只搜类别=狗”,但这个“混合查询”能力在业内还是个老大难问题。Pinecone、Weaviate、Qdrant这些产品在这块做得参差不齐。我试过用Weaviate 1.19版本做过滤搜索,当元数据字段超过10个时,查询延迟直接翻倍。而用PostgreSQL加pgvector插件,虽然向量搜索性能只有专门向量数据库的1/5,但它的过滤查询稳定得像老黄牛。
说到pgvector,这玩意是个很好的例子。它是在PostgreSQL上加了一个向量类型和索引支持,让你能在普通数据库里存向量、做相似度搜索。但我实际测试过,用pgvector的IVFFlat索引在10万条数据上搜最近邻,延迟大概在30毫秒左右,而同等数据量在Milvus上只有5毫秒。更关键的是,pgvector的索引构建是离线的,你插入新数据后必须手动执行`CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)`,这个过程会把表锁住,生产环境里简直要命。所以如果你数据量超过100万条,或者需要实时的增删改查,老老实实用专门的向量数据库。
那什么时候用向量数据库?我举几个真实的例子。
比如做图片搜索。你用CLIP模型把每张图片转成512维向量,存进Milvus。用户上传一张图片,也转成向量,然后在库里搜最近的100个向量,返回对应的图片ID。整个过程不需要任何标签,也不需要人工标注。我去年用这个思路做了个套壳应用,用的模型是ViT-B-32,OpenAI开源的clip-vit-base-patch32,在HuggingFace上直接下载。把图片resize到224x224,过一遍模型,取最后一层池化层的输出,得到768维向量。存进Milvus 2.2,搜索时设置metric_type=‘IP’和params={“nprobe”: 16},召回率大概在92%左右,单次查询10ms以内。
再比如做文本去重。新闻网站每天抓取几十万条新闻,很多是重复的或者高度相似的。你可以用Sentence-BERT模型把每篇文章转成向量,然后搜相似度超过95%的文章,直接丢弃。我试过用all-MiniLM-L6-v2这个模型,384维向量,在Qdrant 1.7上建索引,100万篇文章的去重耗时大约40分钟,对比传统MinHash算法,准确率从85%提升到96%。
还有推荐系统。你看完一部电影,系统找出和你刚看的电影向量最接近的10部推荐给你。这事用普通数据库也能做,但需要你手动打好标签——比如“动作”、“科幻”、“太空”、“外星人”,然后写一堆JOIN和子查询。用向量数据库,你只需要把电影海报或者简介转成向量,让模型替你理解“太空科幻片”和“星际冒险片”其实是同一类东西。
不过向量数据库也不是万能的。它的短板很明显。
第一,解释性差。普通数据库告诉你“为什么这条数据被查出来”,因为它的价格小于10块,逻辑清清楚楚。向量数据库只能告诉你“这条数据和你的查询最像”,但说不清“哪里像”。你没法追问“是颜色像还是形状像”,因为向量是百维空间的整体表示,没法拆解成人类可读的特征。这在医疗、金融等需要可解释性的场景里是个大问题。
第二,精度问题。所有的近似最近邻搜索算法都是“近似”的,意味着你永远有可能漏掉真正最相似的那条数据。虽然你可以通过调参把召回率提到99.9%,但代价是查询速度下降。Milvus里有个参数叫`ef`,控制搜索时的动态列表大小,数值越大越精确但越慢。我试过在100万条数据上,ef=64时召回率98%,查询时间8ms;ef=256时召回率99.7%,查询时间暴涨到35ms。你得自己权衡。
第三,冷启动难。向量数据库依赖模型来生成向量,但模型本身需要训练。如果你用开源的通用模型,比如CLIP或者Sentence-BERT,对特定领域的效果可能很差。比如你用CLIP搜医用X光片,它会把“白色斑点”和“云朵”搞混,因为训练数据里没有医学影像。你得自己微调模型,这又需要大量标注数据。我有个朋友做电商图片搜索,直接用通用模型,搜“红色连衣裙”时出来一堆红苹果,因为模型觉得“红色”和“圆润”的特征匹配上了。
第四,成本高。向量索引占用内存巨大。一个768维的向量,用float32存就是3072字节。1000万条数据就是30GB内存,加上索引开销,轻松超过50GB。如果用SSD存储,查询速度会掉到机械硬盘水平。我算过一笔账,用AWS的r6i.2xlarge实例(64GB内存),托管1000万条向量,月费大概800美元。而同样数量级的普通数据库,用r6i.large(16GB内存)就够,月费200美元。
所以现在很多公司用的是混合方案。普通数据库存结构化数据——用户信息、订单记录、权限控制。向量数据库存非结构化数据的特征。两个库之间通过ID关联。比如我在PostgreSQL里存用户表,里面有user_id、name、age。在Milvus里存用户的头像向量,字段是user_id和embedding。当我要搜“长得像张三的人”时,先把张三的头像转成向量,去Milvus里搜最近的100个user_id,再回PostgreSQL查这些user_id的详细信息。这种模式虽然麻烦,但既享受了向量搜索的便利,又保留了关系数据库的可靠性。
最后说一句,如果你只是做个小Demo,数据量在几千条以内,完全没必要上向量数据库。用Python的scikit-learn算一下余弦相似度,或者用faiss的CPU版本暴力搜索,几毫秒就出结果。向量数据库是为百万、千万级数据量准备的武器,杀鸡用牛刀纯属给自己找麻烦。我当初就是贪新鲜,在只有2000张图片的项目里上了Milvus,结果光是部署Kubernetes集群就折腾了一周,最后发现faiss一把梭就搞定了。
📎 延伸阅读
看完这篇,如果你想:
- 直接拿工具 → 回复"13",我把跨境获客工具包发给你
- 系统学习 → 点击菜单"AI训练营",从0开始跑通AI变现
本文由猫哥AI助手自动发布 🐱
夜雨聆风