讓歷史人物搜索快 500 倍

從 1500 毫秒到 3 毫秒:倒排索引在 CBDB 的落地

English Version


CBDB 目前收錄約 65 萬名歷史人物、100 萬條姓名記錄。過去搜尋如「蘇軾」「安石」等名稱,常需要 1~2 秒;在大量查詢中,延遲會累積成顯著的研究成本。

CBDB 技術團隊重新設計了姓名資料的索引方式,將查詢速度由 1500 毫秒縮減至 3 毫秒,提升約 500 倍。核心方法是依中文姓名特性構建倒排索引(Inverted Index),並支援繁簡互查


為什麼原本的搜尋會慢?

傳統查詢使用:

WHERE name LIKE '%蘇軾%'

這種「包含式」查詢無法使用資料庫的 B-Tree 索引,只能逐條掃描 100 萬條記錄,因此需要 1~1.5 秒。

實測數據:

查詢耗時
「安石」
1374 ms
「蘇軾」
1540 ms

瓶頸並非計算能力,而是資料組織方式不適合中文姓名搜尋。


為何不直接用全文檢索?

雖然 Elasticsearch、PostgreSQL FTS、MySQL ngram 都能加速中文搜尋,但面臨 CBDB 的限制:

  • MariaDB 10.3 無 ngram(相容性問題)
  • PostgreSQL FTS 會綁定單一資料庫
  • Elasticsearch 額外維護成本高

CBDB 的原則:必須跨資料庫可用、長期可維護、使用標準 SQL。

因此選擇自行構建倒排索引


什麼是倒排索引?

傳統方式(正排):

人物 ID → 姓名

倒排方式

姓名的部分 → 多個人物 ID

例如根據 CBDB 真實資料:

安石 → [陳安石, 裴安石, 李安石, 盧安石, 楊安石, 吳安石, 邵安石, ...]       (共 165 人) 軾   → [蘇軾, 蔣繼軾, 張軾, 林軾, 周軾, ...]

搜尋時,資料庫可直接查詢這份「反向關係表」,完全不需掃描全部姓名記錄。


中文姓名的拆分方式

採用後綴拆分(suffix splitting):

王安石 → [王安石, 安石, 石] 介甫   → [介甫, 甫] 半山   → [半山, 山]

因此原本的:

LIKE '%石%'  -- 無法使用索引

轉換為可利用索引的:

LIKE '石%'   -- 完整使用 B-Tree 索引

這是效能提升的關鍵。


支援繁簡互查

以 OpenCC 單字映射建立簡體後綴:

蘇軾 → [蘇軾, 軾]  (繁體) 苏轼 → [苏轼, 轼]  (簡體)

查詢效果

  • ✅ 輸入「蘇軾」→ 命中
  • ✅ 輸入「苏轼」→ 也能命中
  • ✅ 輸入「軾」 / 「轼」→ 同樣命中

不需切換輸入法,繁簡一致處理。


實作與資料規模

倒排索引表 CBDB__NAME_FTS 以標準 SQL 建構:

CREATE TABLE CBDB__NAME_FTS (    search_term VARCHAR(100),  -- 後綴    c_personid INT,            -- 人物 ID    full_name VARCHAR(100),    is_simplified TINYINT(1),    INDEX (search_term, c_personid) );

資料量

項目數值
人物
65 萬
姓名
100 萬
倒排索引
約 300 萬
空間需求
250–350 MB
重建時間
15–30 分鐘

相容 MySQL、MariaDB、PostgreSQL、SQLite。


性能效果

查詢類型優化前優化後提升
「石」
~1500 ms
5 ms
300×
「安石」
~1374 ms
3 ms
458×
「王安石」
~1540 ms
3 ms
513×
人物 ID
~1500 ms
2 ms
750×

平均約 500× 提升。

對研究者而言,搜尋結果幾乎是「瞬間出現」。


使用者無需改變任何搜尋習慣

現在皆可毫秒級完成:

  • ✅ 「王安石」
  • ✅ 「安石」
  • ✅ 「石」
  • ✅ 「蘇軾」 / 「苏轼」
  • ✅ 「介甫」
  • ✅ 「歐陽」等複姓

搜尋邏輯與過去完全一致,但速度提升數百倍。


技術選擇的原則

  1. 資料組織決定性能上限:倒排索引以空間換時間
  2. 可移植性:不依賴特定資料庫功能
  3. 長期可維護:十年後仍能重建與更新
  4. 繁簡互查在資料層解決,而非外掛補丁

結語:毫秒級查詢的 CBDB

倒排索引讓 CBDB 姓名搜尋從秒級進入毫秒級,極大提升研究體驗。

無論研究者輸入繁體、簡體、全名或片段,系統都能在瞬間返回結果。

在更快速的系統基礎上,未來也可探索拼音搜尋、異體字處理與語義查詢等功能。


附錄:深入閱讀

技術文檔

詳細的設計原理、實作細節與代碼,請參閱:

核心代碼

  • app/Console/Commands/RebuildNameSearchIndex.php - 索引重建命令
  • app/Repositories/BiogMainRepository.php - 搜尋查詢邏輯
  • database/migrations/*_create_internal_name_search_tables.php - 表結構定義

關鍵指標

指標數值
查詢時間(優化前)
1500ms
查詢時間(優化後)
3ms
性能提升
500×
倒排記錄數
300萬條
索引空間
250-350 MB
覆蓋人物數
65萬
覆蓋名字數
100萬
繁簡映射
7000+ 字符

邊界條件說明

  • 複姓處理:對於歐陽、司馬等複姓,索引不做姓氏識別,視為一般字串拆分(詳見完整文檔)
  • 多義字:採用 OpenCC 逐字映射,不做語境消歧(如「干/幹/乾」使用默認對應)
  • 外文名:暫不拆分,整詞存儲
  • 單字後綴:允許產生單字後綴(如「甫」),不特別排除

本文由 Frank Lin 撰寫,2025年11月

技術問題或建議歡迎通過 GitHub Issues 聯繫我們