TiDB 向量搜尋實戰(2026):VECTOR 索引、混合檢索與 RAG 落地

数据库(更新於 2026年7月20日)

你為什麼需要看這篇文章

LLM 時代,向量檢索已經從「可選的實驗性功能」變成了「每個應用的基礎設施」。但把專門的向量資料庫(Milvus、Pinecone、Weaviate)和業務資料庫分開維護,會帶來三重痛苦:

  • 雙寫:插入一條業務記錄,需要同時寫業務庫和向量庫,資料一致性用兩階段提交/try-catch 也未必可靠。
  • 資料同步延遲:業務資料更新後,向量庫的索引重建和同步有視窗延遲,導致檢索結果過時。
  • 維運成本倍增:兩套系統意味著雙倍的部署、監控、備份和容量規劃。

TiDB 從 7.5 版本開始,把向量功能原生整合到分散式 SQL 資料庫中。你不用再同步兩套系統——在 TiDB 裡你就是同時擁有 OLTP、OLAP 和向量檢索能力。

本文涵蓋:VECTOR 型別的使用、兩種向量索引(HNSW / IVF)的原理與選型、混合查詢實戰、RAG 整合流水線、效能調教要點。


核心概念

VECTOR 資料型別

TiDB 引入了一個新的資料型別 VECTOR(n),其中 n 是向量的維度(最大 16384)。向量值以 JSON 陣列字面量表示。

CREATE TABLE products (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(256),
  description TEXT,
  embedding VECTOR(1536),
  category VARCHAR(64),
  price DECIMAL(10,2),
  created_at DATETIME DEFAULT NOW()
);

插入向量就像普通欄位一樣簡單:

INSERT INTO products (name, description, embedding, category, price)
VALUES (
  '藍牙降噪耳機',
  '40mm 驅動單元,主動降噪,續航 30 小時',
  '[0.0123, -0.0456, 0.0789, ...]',
  '電子產品',
  299.00
);

向量距離函數

TiDB 內建了四種常用的向量距離函數:

函數 含義 適用場景
VEC_COSINE_DISTANCE(a, b) 餘弦距離(1 − 餘弦相似度) 文字語意檢索(最常用)
VEC_L2_DISTANCE(a, b) 歐幾里得距離(平方) 圖片/幾何特徵
VEC_INNER_PRODUCT(a, b) 負內積(−⟨a,b⟩) 正規化後的餘弦等價
VEC_L1_DISTANCE(a, b) 曼哈頓距離 稀疏向量 / 特定場景

所有距離函數都可以在 ORDER BYWHERE 中使用,與普通欄位無異。


向量索引:HNSW 與 IVF

如果沒有索引,每次相似度檢索都需要全表掃描並計算距離——百萬級資料時延遲不可接受。TiDB 提供兩種近似最近鄰(ANN)索引:

HNSW

HNSW 是當前學術界和工業界公認的低延遲 ANN 索引結構。其核心思想是構建一個多層的「跳表圖」:

  • 底層(Layer 0):包含所有向量點,邊稠密,負責高召回。
  • 上層:按機率隨機抽樣進入,邊稀疏,負責快速跳躍、縮小搜尋範圍。
  • 查詢時:從最高層開始貪婪搜尋,逐漸下降到 Layer 0,每一層都透過「區域鄰居圖」的邊快速逼近最近鄰。

HNSW 的特點:

  • 查得快:通常在 1–3 ms 內完成 top-10 ANN 查詢(百萬級資料)。
  • 記憶體大:需要儲存圖結構(每個節點約 50–100 條邊),額外記憶體常為原始向量資料量的 2–3 倍。
  • 建構慢:逐一插入並生成鄰居邊,時間複雜度 O(N·logN·M)。
  • 適合高 QPS 線上服務
ALTER TABLE products
ADD VECTOR INDEX idx_embedding (embedding)
WITH (
  distance = 'cosine',
  type = 'hnsw',
  m = 16,
  ef_construction = 64
);

IVF

IVF 使用聚類思想:把所有向量用 k-means 聚為若干簇,查詢時先找最近的若干簇,然後僅在候選簇內計算精確距離。

  • 建構快:k-means 聚類 + 分配,時間複雜度遠小於 HNSW。
  • 記憶體小:只存聚類中心(幾十 KB)加倒排列表(id 陣列),幾乎沒有額外記憶體開銷。
  • 查得稍慢:需要掃多個簇,延遲通常比 HNSW 高 2–5 倍。
  • 適合插入頻繁的寫密集場景
ALTER TABLE products
ADD VECTOR INDEX idx_embedding (embedding)
WITH (
  distance = 'cosine',
  type = 'ivf',
  nlist = 256
);

HNSW vs IVF 決策表

維度 HNSW IVF
查詢延遲(p50) 1–3 ms 5–15 ms
索引建構時間 慢(數倍於 IVF)
額外記憶體 2–3× 向量資料 極小(聚類中心 + 倒排列表)
召回率(top-10) 95–99% 90–97%(取決於 nlist)
增量插入 支援,但圖品質可能退化 支援,聚類分佈可能漂移
推薦場景 線上服務、低延遲、高 QPS 批次寫入、記憶體緊張、原型階段

混合檢索:TiDB 的真正殺手級能力

絕大多數 RAG 場景並不只是「找到與問題最相似的 5 個文件片段」。真實需求往往是:

  • 「找到最近 7 天內發布的、屬於 '後端開發' 類別的、與使用者問題最相關的 5 篇文章」
  • 「找到價格在 ¥100–500 之間庫存大於 0 的、與使用者搜尋詞最相似的 10 件商品」

在這些場景中,你需要精確的 SQL 過濾 + 向量相似度排序,而 TiDB 的最佳化器可以把兩者在一條 SQL 裡統一排程。

SELECT id, name, description, price,
       VEC_COSINE_DISTANCE(embedding, :query_embedding) AS dist
FROM products
WHERE category = '電子產品'
  AND price BETWEEN 100 AND 500
  AND status = 'active'
ORDER BY dist
LIMIT 10;

執行計畫

  1. 最佳化器識別 VECTOR INDEX,使用索引做 ANN 召回(返回候選集)。
  2. 對候選集應用 WHERE 過濾——丟棄不滿足條件的行。
  3. 對剩餘行按 dist 排序,取前 10 行返回。

關鍵點:過濾發生在召回之後但在精確排序之前。也就是說,資料庫不會把全表的向量距離都算一遍再過濾,而是「先用索引快速撈出一批語意相似的候選,再用 WHERE 精確篩選,最後才排序」。這在百萬級資料上會把延遲從秒級降到毫秒級。


RAG 流水線整合:從零到一

步驟 1:文件切塊與向量化

from openai import OpenAI
import tiktoken

client = OpenAI()

def chunk_text(text: str, max_tokens: int = 512) -> list[str]:
    enc = tiktoken.get_encoding("cl100k_base")
    paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
    chunks, current, current_tokens = [], "", 0
    for p in paragraphs:
        tokens = len(enc.encode(p))
        if current_tokens + tokens > max_tokens:
            chunks.append(current)
            current, current_tokens = p, tokens
        else:
            current += "\n\n" + p
            current_tokens += tokens
    if current:
        chunks.append(current)
    return chunks

def embed(texts: list[str], model="text-embedding-3-small") -> list[list[float]]:
    resp = client.embeddings.create(input=texts, model=model)
    return [r.embedding for r in resp.data]

步驟 2:存入 TiDB

import pymysql

conn = pymysql.connect(host="127.0.0.1", user="root", database="rag_db")

for i, (chunk, emb) in enumerate(zip(chunks, embeddings)):
    emb_str = "[" + ",".join(f"{v:.6f}" for v in emb) + "]"
    conn.cursor().execute(
        "INSERT INTO documents (id, content, embedding, category) VALUES (%s, %s, %s, %s)",
        (i, chunk, emb_str, "tech_blog")
    )
conn.commit()

步驟 3:構建索引

ALTER TABLE documents
ADD VECTOR INDEX idx_doc_embedding (embedding)
WITH (distance = 'cosine', type = 'hnsw', m = 16, ef_construction = 64);

在大批量匯入時,建議先匯完資料再建索引,速度遠快於逐行插入 + 線上構建。

步驟 4:RAG 檢索

def rag_query(question: str, category: str | None = None, top_k: int = 5) -> str:
    q_emb = embed([question])[0]
    q_emb_str = "[" + ",".join(f"{v:.6f}" for v in q_emb) + "]"

    if category:
        rows = conn.cursor().execute(
            """SELECT content, VEC_COSINE_DISTANCE(embedding, %s) AS dist
               FROM documents WHERE category = %s ORDER BY dist LIMIT %s""",
            (q_emb_str, category, top_k)
        ).fetchall()
    else:
        rows = conn.cursor().execute(
            """SELECT content, VEC_COSINE_DISTANCE(embedding, %s) AS dist
               FROM documents ORDER BY dist LIMIT %s""",
            (q_emb_str, top_k)
        ).fetchall()

    return "\n\n---\n\n".join(r[0] for r in rows)

步驟 5:端到端查詢

context = rag_query("TiDB 的向量索引怎麼選?", category="tech_blog")
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {"role": "system", "content": f"使用以下材料回答:\n\n{context}"},
        {"role": "user", "content": "TiDB 的向量索引怎麼選?"}
    ]
)
print(response.choices[0].message.content)

這樣,從文件入庫到 RAG 問答,全程不需要額外的向量資料庫——TiDB 一手包辦。


效能與調教要點

1. 向量維度和儲存

  • VECTOR(1536) 每條佔 6 KB。百萬條 = 6 GB 純向量資料,加 HNSW 索引約為 15–20 GB。
  • 如果對精度要求不高,可以用 Matryoshka embedding 或 PCA 截斷為 256–512 維——儲存和索引記憶體減半以上。

2. 批次匯入最佳化

  • 使用 LOAD DATA 或 batch INSERT(每批 500–1000 行),關閉 autocommit,單交易提交。
  • 先載入資料,後建立向量索引(tidb_ddl_reorg_batch_size 可調大以加速索引構建)。

3. 查詢最佳化

  • LIMIT 配合索引:向量索引的召回數量受 LIMIT 影響,內部搜尋量約為 LIMIT 的 3–5 倍。
  • 利用覆蓋索引:如果查詢只涉及少量欄位,可以在向量索引被使用的同時走覆蓋索引。
  • 分割區表:如果資料天然可以按時間或類別分割區,向量索引 + 分割剪枝可以極大縮小搜尋空間。

4. 召回率與延遲的權衡

SET SESSION tidb_vector_index_ef_search = 40;
SELECT ... ORDER BY VEC_COSINE_DISTANCE(embedding, '[...]') LIMIT 10;

與其他向量資料庫的對比

維度 TiDB Milvus Pinecone pgvector
混合查詢(向量+SQL過濾) ✅ 原生一行 SQL ✅ 部分支援 ❌(需應用層) ✅ 原生
交易(ACID) ✅ 分散式交易 ✅ 單機
水平擴充 ✅ 原生 ✅(SaaS)
SQL 相容性 ✅ MySQL 相容 ✅ PostgreSQL
部署複雜度 中(分散式) 中-高 低(SaaS) 低(PG 擴充)
向量搜尋延遲(百萬級) 2–15 ms 2–10 ms 5–20 ms(網路) 3–10 ms

TiDB 的獨特優勢:你不需要在「交易一致性」和「向量搜尋能力」之間二選一。同一套叢集、同一條 SQL、同一個交易——這就是原生整合的真正價值。


常見問題(FAQ)

Q1:TiDB 的向量索引與 pgvector 有什麼本質區別?

兩者都支援 SQL + 向量的混合查詢,但 TiDB 是原生分散式設計——pgvector 的索引是單機方案,資料量和 QPS 增長到一定程度需要換分散式方案,TiDB 則原生支援水平擴充。

Q2:什麼時候應該用專用的向量資料庫而不是 TiDB?

純粹的「存向量、搜向量」——沒有任何 SQL 過濾需求、不需要交易、不需要 JOIN 業務表——專用的向量資料庫在純向量檢索微調上有更深度的最佳化。但一旦你需要「向量搜尋結果是業務檢視的一部分」或者「向量檢索+精確過濾+交易操作在同一個請求裡」,TiDB 的優勢就顯露出來了。

Q3:向量索引在分散式 TiDB 中如何工作?

每個 TiKV 節點上的 Region 內獨立構建本機向量索引。查詢時,TiDB 把 ANN 搜尋下推到各 TiKV 節點並行執行,最後在 TiDB 層聚合排序——這是 TiDB 的 MPP 架構的天然優勢。

Q4:遷移已有 pgvector 資料到 TiDB 複雜嗎?

不複雜。pgvector 和 TiDB 都使用 VECTOR(n) + JSON 陣列字面量格式,資料匯出為 CSV 後可直接 LOAD DATA 匯入。主要需要適配的是索引建立語法。

Q5:生產環境中 HNSW 索引會因大量插入而退化嗎?

會,但可以透過定期 ALTER TABLE ... REBUILD VECTOR INDEX 重建索引來恢復圖結構品質。如果業務是頻繁插入 + 即時查詢的場景,建議使用 IVF 索引(對插入更友好),或者設計定期重建策略。


總結

TiDB 的向量搜尋功能不是為了在 benchmark 上打敗專用的向量資料庫,而是為了消除「交易庫 + 向量庫」之間的同步鴻溝。當你需要一個系統裡同時完成「寫訂單、查庫存、搜商品」這些操作時,不再需要把向量檢索單獨抽出來交給另一個系統——一條 SQL,一次完成。

推薦你的學習 / 評估路徑

  1. 用 TiDB Playground 本機起一個單節點;
  2. 建立表 + VECTOR 欄位 + HNSW 索引;
  3. 匯入 10 萬條公開資料集;
  4. 分別測試純向量檢索、純 SQL 過濾、混合查詢的效能和召回率;
  5. 體驗 EXPLAIN ANALYZE 看執行計畫——感受最佳化器如何把向量索引和 SQL 過濾融合在一起。
#TiDB#向量搜索#向量索引#混合检索#HTAP#RAG