讓歷史人物搜索快 500 倍
從 1500 毫秒到 3 毫秒:倒排索引在 CBDB 的落地
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× 提升。
對研究者而言,搜尋結果幾乎是「瞬間出現」。
使用者無需改變任何搜尋習慣
現在皆可毫秒級完成:
- ✅ 「王安石」
- ✅ 「安石」
- ✅ 「石」
- ✅ 「蘇軾」 / 「苏轼」
- ✅ 「介甫」
- ✅ 「歐陽」等複姓
搜尋邏輯與過去完全一致,但速度提升數百倍。
技術選擇的原則
- 資料組織決定性能上限:倒排索引以空間換時間
- 可移植性:不依賴特定資料庫功能
- 長期可維護:十年後仍能重建與更新
- 繁簡互查在資料層解決,而非外掛補丁
結語:毫秒級查詢的 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 聯繫我們