
用 SQLite 搭一个不焦虑的知识库
笔记散在五个地方
Notion 一个库,Obsidian 一个 vault,手机备忘录里几十条,微信收藏里更多,还有两个项目的 README。想找「FTS5 分词的坑」,我知道写过,但不知道写在哪。
整理冲动每半年发作一次。上半年我试过把全部内容迁进某个云笔记,导了四百多篇,两个月后又长回去了。问题不在工具,在于我没有一个稳定的检索入口。
为什么是 SQLite
云笔记都很好用,好用到我不再关心文件放在哪。但有三件事让我一直不舒服:导出永远缺格式,附件要重新下载,服务商改一次客户端我就得重新适应一遍。更怕的是那种「内容还在,但不再属于我」的感觉。
本地文件没有这个问题。一个目录,几百个 .md,我可以用 grep 找到任何一句话,也能在十年后打开它。但纯文件搜索太慢,笔记一多,grep 的结果就没法看。
SQLite 适合做索引:单文件、不用起服务、任何语言都能读、FTS5 内置。它不替代 Markdown 文件,只是给文件建了一层可以查询的壳。我的结构因此是:Markdown 原文按月存放,SQLite 里存标题、正文、标签的副本和索引,随时可以整个重建。
代价也直说:没有移动端,没有协作,搜索不如云服务聪明。这些我都不太需要。
表、索引和写入脚本
create table if not exists notes (
id integer primary key,
title text not null,
body text not null,
tags text not null default '',
created_at text not null,
updated_at text not null
);
create virtual table if not exists notes_fts using fts5(
title, body, tags,
content = 'notes',
content_rowid = 'id',
tokenize = 'unicode61 remove_diacritics 2'
);
日期我存成 YYYY-MM-DD 的文本。SQLite 没有专门的日期类型,文本反而好排序、好比较,导出到别处也省事。
中文要单独处理:unicode61 会把一串连续的汉字当成一个 token,搜「检索」匹配不到「全文检索」。我的做法是入库前用 Intl.Segmenter 粗切一遍,把切好的文本写进索引列,原样的正文仍存在原表里。体积大一点,换来能用的检索。
import Database from 'better-sqlite3';
const db = new Database('notes.db');
const insert = db.prepare(`
insert into notes (title, body, tags, created_at, updated_at)
values (@title, @body, @tags, @date, @date)
`);
const index = db.prepare(`
insert into notes_fts (rowid, title, body, tags)
values (?, ?, ?, ?)
`);
export function addNote(note) {
db.transaction(() => {
const info = insert.run(note);
index.run(info.lastInsertRowid, note.title, note.body, note.tags);
})();
}
查一下
select n.title,
snippet(notes_fts, 1, '[', ']', '…', 12) as excerpt,
bm25(notes_fts) as score
from notes_fts
join notes n on n.id = notes_fts.rowid
where notes_fts match ?
order by score
limit 20;
bm25() 数值越小越相关,所以升序。snippet() 只截命中词附近的正文。一次取二十条,在我这台用了四年的笔记本上不到十毫秒。四万字的笔记,没人会觉得这个速度有问题。
想找原句就加引号:where notes_fts match '"容器查询"',FTS5 会按短语匹配,而不是把两个词分开算。
备份,以及它解决不了什么
每天早上一句 sqlite3 notes.db ".backup 'backup/notes-$(date +%F).db'",只留最近三十份。这个命令先写临时副本再替换,不会在写入过程中把主库弄坏,比直接复制文件安全。
每季度再导一份全量 Markdown 到网盘。换机器时把 .db 拷过去就行,不需要导出向导,也不需要等服务器开机。
代价是它不会主动同步。我在两台机器上各自维护主库,靠同步盘合并,冲突时以文件修改时间为准。一年里只出过一次问题,丢了两条笔记,可以接受。
它解决不了「记了不看」。库里三千多条,真正重读过的可能几十条。它也解决不了写的时候不动脑:检索能把碎笔记找回来,不能替我把它们连成一篇。
它也没有富文本。公式、图片、流程图都得另外想办法,我的做法是把图片放在笔记同目录下,正文里用相对路径引用,数据库只存文字。简单,但够用。
我现在每周日晚上花半小时,扫一遍这周新建的条目,删掉重复的,给值得留的补一行批注。这半小时比工具本身有用得多。