版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。
摘要:截至 2026-10-14,主流近似向量索引对「删除」的处理基本都是打标记而不是立刻抹掉——pgvector 的 HNSW、Qdrant 的分段、Milvus 的 bitset 都走这个路子,FAISS 的 HNSW 干脆不支持移除。本文把这笔账拆成「一直增量」与「到点重建」两条 12 个月累计成本曲线,给出拐点公式 N* = C_r / C_i,附两个真跑实验。
一、先把事实钉死:增量写入解决的到底是什么
增量写入解决的只是「写这一下要等多久」,不解决结构里积下来的垃圾。两笔账分开记,才不会得出「新文档进来必须全量重建」或「只增量就够了」这两个方向都错的结论。
「写一条」和「结构变干净」是三件不同的事
一条向量进来牵动三件事:写入延迟、结构占用(索引里还留着多少用不上的位置)、查询时多付的扫描(为凑够 k 个结果要多扫多少候选)。增量写入把第一件压得很低,代价藏在后两件里。
前提还有一层:近似索引本来就不是精确表。pgvector README 原文是「You can add an index to use approximate nearest neighbor search, which trades some recall for speed. Unlike typical indexes, you will see different results for queries after adding an approximate index.」它承认自己拿召回换速度,「召回」指本该被找到的近邻里实际被找回来的比例。
所以要维护的不是精确表,而是质量会漂的近似结构。判断要不要重建,看的不是「多久没动过」,而是「有多少位置被用不上的数据占着」。
官方文档怎么写「删除」
FAISS 官方 wiki 给了一个最硬的边界:「In addition to the restrictions of the Flat index HNSW uses, HNSW does not support removing vectors from the index. This would destroy the graph structure.」HNSW 是分层图索引,邻居靠边互相引用,抽掉一个点会把图撕开,所以干脆不抽。
Milvus 走另外一条路:「Deleted entities are marked with1in the corresponding bitset, which will not be computed during a search or query.」删除在存储上就是翻一位标志位(bitset,可理解成一排 0/1 开关),查询时跳过,不是把向量搬走。
一个「不支持移除」,一个「只翻标志位」,指向同一个事实:删除在近似索引里不是一次写入,而是一笔欠账。
二、「只增量、不重建」这句听起来很美,得打个折
增量写入不等于免重建,要打的这个折叫墓碑(tombstone,一条「这里的东西已作废」的标记)。它不是实现偷懒,是「把立刻抹掉换成先欠着」的取舍。
墓碑是必然后果,不是某个库的实现瑕疵
Qdrant 官方文档把理由说得最清楚:「When a record is deleted, it isn’t removed right away but marked as deleted to avoid slow disk operations during queries.」不在查询期间做慢速磁盘操作,所以先标记:立刻抹掉更贵,标记更便宜。
这笔整理费,三个库都认,只是缴费窗口不同
pgvector README 专门给了一段整理说明:「Vacuuming can take a while for HNSW indexes. Speed it up by reindexing first.」注意它给的顺序:先 REINDEX 再 VACUUM,反过来更慢。这句话本身就是证据:如果删除免费,就不需要一段「怎么把整理变快」的说明。
换一条独立信源对照。Elasticsearch 不是向量库,但倒排索引基于同一套 Lucene 机制,官方指南对标记删除的描述是:「When a document is “deleted,” it is actually just marked as deleted in the .del file. A document that has been marked as deleted can still match a query, but it is removed from the results list before the final query results are returned」——标了删除的文档仍然可能被查出来,是在返回结果之前才被剔掉;剔掉之前,扫描的钱已经花了。真正的清除发生在段合并时:「This is the moment when those old deleted documents are purged from the filesystem」。
打折打到什么程度:召回和延迟一起变差
pgvector 常见问题页给了一句关键原文:「There may be even less results due to dead tuples or filtering conditions in the query.」dead tuples 指已经不生效、但还占着位置的行。后果方向一致:同样参数下能拿到的有效结果变少(召回掉),要维持召回就得把扫描预算调大(延迟涨)。
这里没有第三条路:墓碑占的位置不释放,查询就必须多扫。
三、三家的更新语义摊平:写入 / 删除 / 重建各自多少钱
三家都没把删除做成免费操作,差别只在两处:删除动了什么,整理由谁按什么粒度触发。
🧪 实测块:数据实测(官方更新 / 删除 / 整理语义对照表),取数日期 2026-10-14,逐条引用各库官方文档原文,非二手转述。
| 库(索引类型) | 写入怎么进去 | 删除到底做了什么 | 是否需额外整理 | 整理由谁触发 | 出处 |
|---|---|---|---|---|---|
| pgvector(HNSW 图索引) | 直接追加,图随写随长 | 行被标记删除,索引里的位置不回收 | 需要 | VACUUM,官方建议先 REINDEX 再 VACUUM | pgvector README |
| Qdrant(分段 + HNSW) | 写入落在段内,段满后转为不可变 | 先标记为 deleted,不立刻从磁盘抹掉 | 需要 | Vacuum Optimizer,段内删除占比达阈值才启动 | Qdrant 文档 · Optimizer |
| Milvus(分段 + 多索引) | 先入 growing 段,flush 后成为 sealed 段 | 在段内 bitset 里把该行标 1,查询时跳过 | 需要 | Data Node 离线执行 compaction | Milvus 文档 · Bitset / 架构 |
| FAISS(HNSW 图索引) | 支持 add,图结构随写更新 | 不支持从索引里移除 | 只能整体重建 | 由上层自己重建 | FAISS wiki · Faiss-indexes |
| FAISS(Flat / IVF 倒排) | 支持 add / add_with_ids | 支持 remove,但索引会收缩、编号会改变 | 不需要 | 无 | FAISS wiki · Faiss-indexes |
| Elasticsearch(倒排 + 段,独立信源) | 写入新段 | 在 .del 文件里标记删除,仍可能命中查询,返回前剔除 | 需要 | 段合并时真正清除 | ES 官方 Guide |
口径:取数日期 2026-10-14;「是否需额外整理」按该来源是否明确给出整理动作判定;Elasticsearch 一行只作「标记删除」的独立信源。
图索引和倒排 / 分桶,差的不是一点
倒排 / 分桶指把向量先分到若干桶(簇)里、查询时只扫少数几个桶的索引,FAISS 里对应 IVF 系列。它的删除代价与 HNSW 不同源:
- 倒排表删除是「列表里留个空洞」,FAISS wiki 原话是:「they support removal with
remove. Note that this shrinks the index and changes the numbering.」能真删,代价是索引收缩、编号重排,下游存了旧编号的地方会对不上。 - 图索引删除是「边被撕开」,只能标记,标记完就是长期占用。
这条差别直接决定阈值怎么取:桶结构能容忍更高比例的删除,图结构不能。
整理动作的名字不一样,形状一样
Qdrant 把整理写成可调的触发条件:「deleted_threshold sets the minimum fraction of deleted records in a segment required to initiate optimization. For example, a value of 0.2 means that 20% of a segment’s records must be marked as deleted for the optimizer to consider running.」配一条下限:段内向量数不足 1000 时不做优化。整理做什么也有原话:「The Vacuum Optimizer solves this problem by permanently removing marked records and reorganizing storage.」
Milvus 把整理交给后台离线任务:「Data node is responsible for offline processing of historical data, such as compaction and index building.」它靠分段躲开频繁重建:「Index building is performed by data node. To avoid frequent index building for data updates, a collection in Milvus is divided further into segments, each with its own index.」人话:与其反复重建一个大索引,不如切成小段各自建索引。
三家的共同形状:整理是后台批处理,不跟写入同步做——你不去触发它,它只会按自己的节奏慢慢来。
四、同样的活算两遍:一直增量 vs 到点重建
写入侧和查询侧都算进去,「到点重建」第 9 个月反超;只算写入侧的公式却会告诉你两个多月就该动手——后者会让你重建得过勤,白花重建费。
参数与口径
🧪 实测环境:Python 3.13.12 / macOS 27.0.1 arm64 / 仅用标准库(脚本 work/exp1014/exp03a.py,含拐点敏感性扫描)
| 参数 | 取值 | 含义 | 为什么取这个值 |
|---|---|---|---|
| N0 | 1,000,000 条 | 起始向量规模 | 单机图索引的常见量级 |
| 月新增 | 5% × N0 | 每月新增约 5 万条 | 中型知识库的日常增量 |
| 月删除 | 6% × 存活条数 | 每月删除约 6 万条 | 让删除占比在 12 个月内走到 43% |
| I | 1.0 | 单条增量(写入)成本 | 只作计价单位,不设绝对值 |
| R | 0.30 | 单条全量重建成本 | 重建按条摊薄,约为单条增量的 0.3 倍 |
| ALPHA | 2.0 | 写入侧墓碑惩罚 | 垃圾占比 20% 时单条写入贵 40% |
| Q / SCAN / CQ | 200 万次 / 40 条 / 0.002 | 月查询次数 / 每次基础候选数 / 单条比较成本 | 查询侧按 1/(1-垃圾占比)² 放大 |
| 重建阈值 | 垃圾占比 25% | 触发整份重建的条件 | 与 Qdrant 默认的 0.2 同量级 |
口径:系数均为本次实验设定的相对值,不是厂商报价,也不是压测值;R 表示「一次全量重建按条摊薄后的成本 ÷ 单条增量成本」;查询侧的 1/(1-g)² 是把「多扫候选」与「图连通性变差多走几跳」合并后的近似。
# -*- coding: utf-8 -*-# exp03a:向量索引「一直增量」vs「到点重建」12 个月累计成本 + 拐点敏感性扫描# 口径:单位=单条增量成本;写入=(新增+删除标记)×I×(1+ALPHA×垃圾占比)# 查询=Q×SCAN×CQ/(1-垃圾占比)^2;重建=R×当前总条数,重建后墓碑清零N0,ADD_RATE,DEL_RATE=1_000_000,0.05,0.06# 初始条数 / 月新增 / 月删除I=1.0# 单条增量成本ALPHA,SCAN,CQ=2.0,40,0.002# 墓碑惩罚 / 基础候选 / 单条比较THRESHOLD,MONTHS=0.25,12# 重建阈值 / 观察月数defrun(rebuild,R=0.30,Q=2_000_000):live,dead,cost,out=float(N0),0.0,0.0,[]forminrange(1,MONTHS+1):add,dele=N0*ADD_RATE,live*DEL_RATE g=dead/(live+dead+add)cost+=(add+dele)*I*(1+ALPHA*g)# 写入侧cost+=Q*SCAN*CQ/(1-g)**2# 查询侧live,dead=live+add-dele,dead+dele total=live+dead g=dead/totalifrebuildandg>=THRESHOLD:cost+=R*total# 整份重建dead,g=0.0,0.0out.append((m,live,dead,g,cost))returnoutdefcrossover(R,Q):"""持续反超:从某个月起,到点重建的累计成本一直低于纯增量,才算真拐点。 单月交叉不算——一次晚触发的重建会把曲线顶回去。"""a,b=run(False,R,Q),run(True,R,Q)m=Noneforiinrange(MONTHS):ifall(y[4]<x[4]forx,yinzip(a[i:],b[i:])):m=i+1breakreturnm,a[-1][4]-b[-1][4]pure,periodic=run(False),run(True)print(f"N0={N0:,}月新增{ADD_RATE:.0%}月删除{DEL_RATE:.0%}重建阈值{THRESHOLD:.0%}I={I}")print(f"基准参数 R=0.30 Q=2,000,000 ALPHA={ALPHA}解析拐点 N*=(R*N0)/I={(0.30*N0):,.0f}条变更")print(f"{'月份':>4}{'存活':>11}{'墓碑':>11}{'垃圾占比':>10}{'纯增量':>12}{'到点重建':>12}")fora,binzip(pure,periodic):print(f"{a[0]:>4}{a[1]:>11,.0f}{a[2]:>11,.0f}{a[3]:>9.1%}{a[4]:>12,.0f}{b[4]:>12,.0f}")m0,d0=crossover(0.30,2_000_000)print(f"基准:持续反超第{m0}个月,12 个月差额{d0:,.0f}")print("敏感性扫描(行=单条重建成本系数 R,列=月查询次数 Q)")print(f"{'R\\Q':>6}"+"".join(f"{q:>22,}"forqin(1_000_000,2_000_000,4_000_000)))forRin(0.15,0.30,0.50):row=f"{R:>6.2f}"forQin(1_000_000,2_000_000,4_000_000):m,d=crossover(R,Q)row+=f"{('第'+str(m)+'个月'ifmelse'未反超')+f' /{d:+,.0f}':>22}"print(row)原样输出:
N0=1,000,000 月新增5% 月删除6% 重建阈值25% I=1.0 基准参数 R=0.30 Q=2,000,000 ALPHA=2.0 解析拐点 N*=(R*N0)/I=300,000 条变更 月份 存活 墓碑 垃圾占比 纯增量 到点重建 1 990,000 60,000 5.7% 270,000 270,000 2 980,600 119,400 10.9% 570,329 570,329 3 971,764 178,236 15.5% 900,986 900,986 4 963,458 236,542 19.7% 1,262,154 1,262,154 5 955,651 294,349 23.5% 1,654,168 1,654,168 6 948,312 351,688 27.1% 2,077,485 2,467,485 7 941,413 408,587 30.3% 2,532,667 2,734,383 8 934,928 465,072 33.2% 3,020,362 3,031,320 9 928,832 521,168 35.9% 3,541,290 3,358,279 10 923,103 576,897 38.5% 4,096,229 3,715,421 11 917,716 632,284 40.8% 4,686,007 4,103,048 12 912,653 687,347 43.0% 5,311,492 4,896,075 基准:持续反超第 9 个月,12 个月差额 415,418 敏感性扫描(行=单条重建成本系数 R,列=月查询次数 Q) R\Q 1,000,000 2,000,000 4,000,000 0.15 第8个月 / +342,382 第8个月 / +797,664 第7个月 / +1,708,229 0.30 未反超 / -39,865 第9个月 / +415,418 第8个月 / +1,325,983 0.50 未反超 / -549,527 未反超 / -94,245 第8个月 / +816,320拐点公式:N* = C_r / C_i
把一次重建成本记作 C_r,单条增量成本记作 C_i,「重建一次的钱」等于「N 条增量的钱」那个点就是:
N* = C_r / C_i
本次算例 C_r = R × N0 = 0.30 × 1,000,000 = 300,000(按单条增量计),故 N* = 300,000 条变更。按每月约 11 万条变更,纯写入侧的口径两个多月就到了——它会告诉你「早就该重建了」。
这是偏早的判断,因为它没算另一半:查询侧的损失是慢慢涨起来的,前几个月几乎看不出来。
| 单条重建成本 R | 月查询 100 万次 | 月查询 200 万次 | 月查询 400 万次 |
|---|---|---|---|
| 0.15(重建便宜) | 第 8 个月 / 省 342,382 | 第 8 个月 / 省 797,664 | 第 7 个月 / 省 1,708,229 |
| 0.30(本次基准) | 12 个月内未反超 / 亏 39,865 | 第 9 个月 / 省 415,418 | 第 8 个月 / 省 1,325,983 |
| 0.50(重建偏贵) | 12 个月内未反超 / 亏 549,527 | 12 个月内未反超 / 亏 94,245 | 第 8 个月 / 省 816,320 |
口径:单元格=「到点重建累计成本持续低于纯增量的第一个月 / 12 个月总成本差额」,正数表示到点重建更省;「未反超」表示 12 个月里每月都没能稳定低于纯增量。采样点 3 × 3,只判断趋势。
三个结论:重建越便宜(R 小)拐点越早;查询压力越大(Q 大)拐点越早;**R 偏贵且查询不多时,12 个月内根本不该重建。**只算写入侧会漏掉最后一行——那正是最容易白花钱的情形。
五、为什么会出现「越更新越慢」这个反常现象
越更新越慢不是玄学,是「有效密度下降」的算术结果:结构里的位置一个没少,能用的结果越来越少,查询只能多扫。
🧪 实测环境:Python 3.13.12 / macOS 27.0.1 arm64 / 仅用标准库(脚本 work/exp1014/exp03b.py)
🔴 说明:下面这段用纯 Python 的 list-of-lists 邻居表做模拟,它只是结构占用模拟,不是任何真实向量索引的实现。真实 HNSW / IVF 的行为以各库实现为准,这里只验证「标记删除 → 结构占用不下降、扫描量上升」这一个结构性后果。
# -*- coding: utf-8 -*-# exp03b:邻接表「结构占用」模拟(不是任何真实索引的实现)# 删除只翻 dead 标志位,槽位与指向它的邻接条目都不回收;查询照样扫到它们。# 口径:扫描条目=访问过的槽位×ROW;有效命中率=命中结果数/扫描条目数。importrandom N,ROW,K,WINDOW,QUERIES,CAP=50_000,16,10,200,500,4_000random.seed(20261014)rows=[]foriinrange(N):nbrs=[]for_inrange(ROW):off=random.randint(-WINDOW,WINDOW)nbrs.append((i+(offor1))%N)rows.append(nbrs)dead=[False]*Ndefmark_deleted(p):globaldead dead=[False]*N step=1.0/pifpelsefloat("inf")x=0.0whilex<Nandp:dead[int(x)]=Truex+=stepdefquery(target):scanned=accepted=visited=offset=0seen=set()whileaccepted<Kandscanned<CAP:forsin((target+offset)%N,(target-offset)%N):visited+=1forjinrows[s]:scanned+=1ifdead[j]:# 墓碑:条目还在,结果是死的continueifabs(j-target)<=WINDOWandjnotinseen:seen.add(j)accepted+=1ifaccepted>=K:breakifaccepted>=K:breakoffset+=1ifoffset>N:breakreturnvisited,scanned,acceptedprint(f"N={N:,}ROW={ROW}K={K}窗口±{WINDOW}每档{QUERIES}次查询 种子 20261014")print(f"{'删除占比':>8}{'结构占用条目':>14}{'有效命中率':>12}"f"{'平均扫描条目':>14}{'扫描量倍数':>12}")base=Noneforiinrange(11):p=round(i*0.05,2)mark_deleted(p)live=[tfortinrange(N)ifnotdead[t]]ts=ta=0for_inrange(QUERIES):_,s,a=query(random.choice(live))ts+=s ta+=a avg=ts/QUERIES base=avgifbaseisNoneelsebaseprint(f"{p:>7.0%}{N*ROW:>14,}{ta/ts:>11.2%}{avg:>14.1f}{avg/base:>11.2f}x")print(f"结构占用条目 12 档全部为{N*ROW:,}条:删除只翻标志位,一条也没回收")原样输出:
N=50,000 ROW=16 K=10 窗口±200 每档 500 次查询 种子 20261014 删除占比 结构占用条目 有效命中率 平均扫描条目 扫描量倍数 0% 800,000 98.54% 10.1 1.00x 5% 800,000 93.09% 10.7 1.06x 10% 800,000 87.43% 11.4 1.13x 15% 800,000 83.42% 12.0 1.18x 20% 800,000 71.07% 14.1 1.39x 25% 800,000 65.57% 15.2 1.50x 30% 800,000 54.67% 18.3 1.80x 35% 800,000 46.35% 21.6 2.13x 40% 800,000 38.48% 26.0 2.56x 45% 800,000 34.43% 29.0 2.86x 50% 800,000 29.91% 33.4 3.30x 结构占用条目 12 档全部为 800,000 条:删除只翻标志位,一条也没回收三列数字各说一件事
结构占用条目 12 档全是 800,000 条。删除比例往上走,占着的结构位置一条也没少——这是「增量写入 ≠ 免重建」的结构证据,只跟「删除是否物理回收位置」有关。
有效命中率从 98.54% 掉到 29.91%。扫到的条目里能用的越来越少,掉到 30% 意味着扫三下才换来一个有效结果。
平均扫描条目从 10.1 涨到 33.4,扫描量 3.30 倍。为凑够同样多的结果,同一次查询要扫的条目多了两倍多。延迟上升与召回下降是同一件事的两面,位置被占着就只能多扫。
把这三列翻译成日常处置
pgvector 那句 dead tuples 说的正是第二列的现象:参数不变、数据被删过之后结果会变少。所以「把 ef_search 调大」实质是拿延迟换召回,不解决第一列的问题。
这里给一个明确的停止条件:**删除之后若没有任何整理动作(vacuum / compaction / optimizer),墓碑只会累积,不会自己消失。**这是整条链上唯一不能省的一步。
这份资料里有一节专门讲向量库的索引与检索链路:本章讲的标记删除、上面提到的 HNSW 与倒排结构,在那套视频课的 Embedding+RAG 模块里从建库到检索有完整演示。放在资料包里,扫码即可获取:
六、给不同数据形态的选型参考
三类数值就能把这件事定住:删除占比阈值、变更批量阈值、触发整理的条件。
| 判据 | 图索引(HNSW 类) | 倒排 / 分桶(IVF 类) | 为什么这么分 |
|---|---|---|---|
| 删除占比阈值 | 15%–20% | 25%–30% | 图索引删一个点伤的是边与连通性;倒排表删一条只在列表里留个空洞,损伤小 |
| 变更批量阈值 | 单批超过 10% 记录、或超过 10 万条(先到为准)直接重建 | 同左,可放宽到 15% | 逐条增量等于把重建的钱拆成很多次付,还要多付一遍查询侧的账 |
| 触发整理的条件 | 删除占比 ≥ 阈值,且段内条数 ≥ 1000 | 同左 | Qdrant 默认就是两条同时满足才启动 vacuum,避免小段被反复整理 |
口径:取值为工程建议区间,不是官方规定;图索引一列的起点可参考 Qdrant 默认的 deleted_threshold = 0.2 与 vacuum_min_vector_number = 1000;段内条数下限为避免小段被反复整理。
落地成四条判断,按顺序执行
- 先看删除占比,越过阈值就直接重建,不再讨论。
- 删除占比没越线,但单批变更超过 10% 记录,同样重建。
- 两条都没越线,继续增量——但删除后必须触发一次整理(vacuum / compaction / optimizer)。
- 整理开销已经和重建差不多,说明早就越界,回到第 1 步。
什么时候该停手
本轮算例的停止条件很具体:一个月的新增加删除合计达不到 N*(本次是 30 万条),重建的拐点不会出现。这时把预算花在整理上更划算——敏感性表里 R 偏贵、查询不多那两格是同一个答案。
反过来,只要删除占比越线,就别再算了,直接重建:越线之后每多扛一个月,查询侧那笔账都按 1/(1-g)² 往上走,重建成本却是固定的。
这份资料里有从建库到上线的完整路径:索引重建、整理触发、召回与延迟的取舍怎么落到工程里,课程把它们放在 Embedding+RAG 与私有化部署两个模块里串起来讲。放在资料包里,扫码即可获取:
附表 A:本文引用事实与出处对照表
- 事实:近似索引拿召回换速度;HNSW 的整理须先 REINDEX 再 VACUUM;dead tuples 会让查询结果变少。出处:pgvector README(github.com/pgvector/pgvector)。本文位置:第一、二、五章。
- 事实:HNSW 不支持移除向量(会破坏图结构);Flat / IVF 支持 remove,但索引收缩、编号改变。出处:FAISS wiki · Faiss-indexes(github.com/facebookresearch/faiss/wiki)。该页无日期字段,不标注修订日期。本文位置:第一、三章。
- 事实:已删除实体在 bitset 里标记为 1,查询时不再参与计算。出处:Bitset — Milvus 官方文档。本文位置:第一、三章。
- 事实:Milvus 的 compaction 与索引构建由 Data Node 离线执行;集合被切分为多个各自带索引的段,以避免频繁重建。出处:Architecture Overview — Milvus 官方文档。本文位置:第三章。
- 事实:删除先标记为 deleted;Vacuum Optimizer 永久移除标记记录并重组存储,触发条件为 deleted_threshold 与 vacuum_min_vector_number(默认 0.2 / 1000)。出处:Optimizer — Qdrant 官方文档。本文位置:第二、三、六章。
- 事实:标记删除的文档仍可能命中查询、在返回前被剔除;真正清除发生在段合并;批量请求优于单文档请求。出处:Elasticsearch 官方 Guide(dynamic-indices、merge-process)与 tune-for-indexing-speed。仅作独立信源,Elasticsearch 非向量库。本文位置:第二章。
- 事实:成本曲线、拐点 N* = C_r / C_i = 300,000 条变更、3 × 3 敏感性扫描。出处:本文实测,脚本 exp1014/exp03a.py,取数日期 2026-10-14。本文位置:第四章。
- 事实:删除占比 0%→50% 时结构占用条目不变、有效命中率 98.54%→29.91%、扫描条目 10.1→33.4;为邻接表结构占用模拟实跑,非真实索引实现。出处:本文实测,脚本 exp1014/exp03b.py,同上环境。本文位置:第五章。
写在最后:这篇用到的资料
写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:
- 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
- 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
- AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
- 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
- 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「AI」,优先通过。
资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。