自己搭个人笔记系统这件事,前前后后折腾了快两年,数据库选型换来换去也踩了不少坑。最开始的直觉是“数据量大了该上重型数据库”,后来发现个人笔记的场景跟企业应用根本是两码事。这篇就当是我的选型总结,把每个候选库的真实表现、取舍逻辑、以及最后留下的方案都摊开来讲。如果你也在纠结个人项目到底该选什么数据库,这篇能帮你省掉不少试错时间。
1. 先说结论:个人笔记选库,看的不是数据库名气,是你自己的数据形态
1.1 我服务的笔记系统到底长什么样
我搭建的是一个自用的个人笔记系统,核心功能包括:笔记正文的增删改查、标签系统、全文检索、附件文件管理、简单的双链引用(就是笔记之间互相链接)。最初我天真地以为,一个笔记系统该有的功能就这么多,随便选个数据库都能撑住。但真正上手之后才发现,个人笔记的数据形态才是最影响选型的因素。
笔记数据基本分成三类:
- 结构化元数据:标题、创建时间、更新时间、标签列表、笔记ID、是否归档等,这类数据规律性强,很适合用关系型表结构存储。
- 非结构化正文:笔记的Markdown内容、代码块、长文摘录,长短差异极大,有的几十个字,有的几十KB。
- 文件类附件:图片、PDF、音频,这类东西根本不适合直接塞进数据库字段里,应该走“数据库存路径 + 文件系统存实体”的方案。
我把当时的需求拉成一个清单后发现,它对数据库的要求是这样的:单机运行、离线可用、备份简单、查询以单表过滤和关键词检索为主、没有复杂的多表关联统计、几乎没有并发写。这组条件其实已经把候选方向收窄了很多。
1.2 三条硬约束:单机优先、离线可用、备份简单
个人笔记有一个特点经常被忽略:它必须离线可用。我去过信号不好的地方,也经历过所在网络完全断掉的情况,如果笔记系统因为连不上数据库或者服务挂了就打不开,那这种笔记系统压根不合格。所以当时定了三条硬约束:
- 单机优先:应用和数据库跑在同一台设备上,不引入独立的数据库服务进程,除非万不得已。
- 离线优先:所有核心数据必须本地化,云端同步只能是加分项,不能是本地的依赖项。
- 备份即拷目录:备份方案必须简单到“一条命令、一个文件”,不能要求我每次备份都做一次数据库导出再压缩。
这三条约束出来之后,很多数据库选手还没下场就已经被淘汰了。你会发现,个人笔记这种极轻量的场景,真正需要的不是一台服务器的强悍,而是低摩擦、可靠、免维护。这也决定了后面所有对比的基调。
2. 候选方案逐个过筛:四个数据库的真实表现和取舍逻辑
2.1 SQLite:个人笔记的默认答案
SQLite 是我最后留下来当主力库的选手,也是我最想先聊的。它是嵌入式关系型数据库,整个库就是单个文件,不需要独立的服务器进程,Python 自带驱动,跨平台,零配置。对个人笔记这种单机、低并发、数据量几百MB以内的场景,它几乎是量身定做的。
在实际搭建中,SQLite 的关键优势是事务可靠性和文件格式的稳定性。它支持完整的 ACID 事务,笔记写入过程中断电,重启后不会出现半截数据。这一点看起来基础,但真出事的时候才知道严嵩。而且 SQLite 数据库文件本身就是一个标准的单文件,备份只需要安全地把这个文件复制走就行。
很多人担心 SQLite 的性能不够用,老实说,个人笔记数据量到几十万条记录时,只要索引设计合理,查询依然是毫秒级。我自己测试过,一张十多万行的笔记表加上全文索引,关键词搜索的耗时在一百毫秒以内,完全感受不到延迟。除非你要做的是千万级数据分析,否则 SQLite 的性能不是瓶颈。
2.2 PostgreSQL:功能最全,但部署成本被低估了
PostgreSQL 是我最初以为应该选的方案,毕竟它几乎是开源关系库的顶配:支持 JSONB、丰富的索引类型、完整的全文搜索、强大的扩展能力。但真在一台普通设备上部署之后,我才意识到问题的本质——功能再强,你也得先把它跑起来、养得好。
PostgreSQL 需要独立装服务端、管理用户权限、维护端口、处理连接数、配置内存参数,还要考虑它作为常驻进程的资源占用。我有一台老设备内存才 4GB,跑着系统加浏览器已经很吃力,再挂一个 PostgreSQL 服务,内存直接紧张。个人笔记工具如果打开之前还要先确认数据库服务活着,那就已经完全背离了“随手记录”的初衷。
我并不是说 PostgreSQL 不好,它非常好。只是它适合的场景是:数据量到了 SQLite 撑不住的程度、需要复杂查询、需要多用户并发写入、或者你本来就要跑一台服务器顺手带起来。对个人笔记来说,上 PostgreSQL 属于典型的能力过剩,光部署维护成本就足以抵消它的功能优势。
2.3 MySQL:适合团队习惯,不适合个人轻量化
MySQL 给我的感觉一直是“大家都在用,所以我也准备用”。真正对比之后发现,它作为独立服务器和 PostgreSQL 一样存在部署维护成本,而在个人笔记场景下它的优势项(主从复制、存储过程、慢查询日志、用户权限体系)一个都用不上。MySQL 的强项是团队协作下的标准化运维,个人笔记如果选它,等于背着一台服务器的运维压力去写几行笔记查询。
还有一个问题是 MySQL 默认的配置文件需要针对不同环境做一堆调整,字符集、时区、事务隔离级别、连接数,每一项都可能出幺蛾子。SQLite 那边什么都不用配,MySQL 这边光把字符集设置成 UTF-8MB4 以便正确存表情字符就要多几步操作。所以 MySQL 在个人笔记选型里排得很靠后。
有人会说 MySQL 生态成熟、网上问题答案多,但这个问题我当时也反思过:那不是 MySQL 的优势,而是“大家都用 MySQL 所以文档多”的结果。换个思路,如果 SQLite 有同样多的人在用,你在遇到问题时得到的帮助一点不会少。我的结论是:MySQL 更适合要在已有团队技术栈中保持一致的情况,而不是个人项目从零搭建时的最优解。
2.4 MongoDB 与文档型数据库:模式灵活,但查询能力是短板
MongoDB 这类文档型数据库我其实认真考虑过,理由是笔记正文的格式不固定,每个人记笔记的结构千差万别,文档型数据库可以不定义表结构直接塞 JSON。实际操作之后发现,前期是省事了,后期查询很快就不顺手。
我的笔记系统需要频繁按标签过滤、按日期范围统计、做全文检索。在 MongoDB 里做这些要么写聚合管道,要么引入单独的搜索服务,都比我熟悉的 SQL 写法绕圈子。尤其是“笔记列表 + 分页 + 按标签筛选”这种我每天都会用的操作,SQL 一行语句搞定,MongoDB 的聚合框架得绕好几层。虽然文档型数据库对模式灵活是真友好,但个人笔记里的字段其实非常稳定——我记录、标签、时间,几乎不变化。灵活性在这里根本不算需求,反而是瞎子戴眼镜,多此一举。
2.5 键值存储和内存型数据库:只配当缓存,不配当主库
Redis 这类内存型键值存储,我一开始就没把它当候选主库,但还是在最终对比表里加了一行,因为确实有人这么用。键值存储读写的速度确实快到离谱,但它有两个硬毛病:内存数据需要持久化策略、复杂查询能力基本为零。个人笔记如果哪天正文写到一万字,键值存储只能当作一整块字符串存进去,想按内容检索就只能一条一条取出来遍历,数据量一上去就完蛋。
在最终方案里,我给 Redis 留了一个位置:做全文搜索的缓存层。比如热门标签的计数、当天笔记的聚合统计,这类结果短时间内不会变,先查缓存,没命中再查主库,能省下不少不必要的查询。所以你要问我的态度,我的态度是:键值存储是优秀的下手,但不能当正房。
2.6 选型横向对照表
| 数据库 | 部署维护成本 | 查询能力 | 离线可用 | 备份难度 | 适合场景 | | --- | --- | --- | --- | --- | | SQLite | 极低(嵌入式,零配置) | 强(标准SQL + FTS全文搜索) | 天然离线 | 低(单文件复制) | 个人笔记、桌面应用、嵌入式系统 | | PostgreSQL | 高(独立服务、内存占用大) | 极强(JSONB、复杂SQL、扩展) | 需要服务运行 | 中(pg_dump等) | 数据量大、并发用户多、复杂分析 | | MySQL | 中高(独立服务、需调优) | 强(但个人场景用不到) | 需要服务运行 | 中(mysqldump等) | 团队系统、已有技术栈是MySQL | | MongoDB | 中(独立服务、模式自由) | 中(聚合框架绕来绕去) | 需要服务运行 | 中 | 数据结构极不稳定、海量文档 | | Redis | 中(虽快但持久化要管理) | 弱(复杂查询很受限制) | 需要服务运行 | 中(RDB/AOF文件) | 缓存、热数据、临时状态 |
看完对照表,结论其实已经出来了:个人笔记选 SQLite 不是妥协,而是需求匹配的结果。后面我就把当时的搭建细节展开讲讲,包括每个配置参数的用意和踩过的坑。
3. 我最终搭出来的库:SQLite 当主力库的完整配置和索引设计
3.1 建表结构与索引:先想清楚怎么查,再定怎么存
数据库选型落定之后,表结构设计就是另一场仗。我的笔记系统核心表就一张notes表,这张表的结构决定了我所有功能写起来顺不顺手。当时建表的 SQL 大致长这样:
CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL UNIQUE, title TEXT NOT NULL DEFAULT '', content TEXT NOT NULL DEFAULT '', tags TEXT NOT NULL DEFAULT '[]', parent_id INTEGER, created_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')), updated_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime')), archived INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at); CREATE INDEX IF NOT EXISTS idx_notes_updated_at ON notes(updated_at); CREATE INDEX IF NOT EXISTS idx_notes_archived ON notes(archived);有几个字段设计我当时纠结了很久,这里重复说一下:
- uuid 字段:虽然
id已经是自增主键,但以后如果要同步到多端,id在多个设备上很可能冲突,所以额外设了一个全局唯一的uuid,这种设计叫“应用层主键”。以后哪怕是两台设备各自离线新建笔记,也能通过uuid合并数据。 - tags 字段:用 JSON 文本存储标签列表,而不是单独建一张标签表。个人笔记的标签数量少、查询简单,用 JSON 存储让我筛选和写入都少一次 JOIN。缺点是没法用索引快速统计某个标签出现的次数,但以我的数据量,全表扫描也不慢。
- archived 字段:这是归档功能的关键索引,默认查询都会带上
WHERE archived = 0,所以单独建了一个索引。
索引不是越多越好。我之前犯过给content字段建普通索引的错,结果发现 Markdown 长文本字段的普通索引不仅不生效,还白白增加了写操作的负担。索引一定要走查询条件的字段,正文搜索另外用全文索引解决,这一点后面细说。
3.2 写入性能的关键开关:WAL 模式和总线超时
SQLite 默认的日志模式是 DELETE,也就是说每次写操作都直接把旧数据所在页覆盖掉。对于个人笔记这种偶尔写一次的场景其实够用,但页面切换卡顿的问题还是出现了,原因是我在写入时偶尔会遇到“database is locked”的报错。
踩了这个坑之后我研究了一下 SQLite 并发模型,默认情况下它允许一个写者、多个读者,但如果写操作太频繁,读操作会被短暂阻塞,反过来读操作持续占用时写操作也进不去。这个问题解决起来其实很简单,两行配置:
PRAGMA journal_mode=WAL; PRAGMA busy_timeout=3000;journal_mode=WAL会把日志模式切换成“预写式日志”。写操作先追加到 WAL 文件,再在合适时机合并回主数据库文件。这个模式下读和写可以并行,写阻塞大幅减少。我自己实测,开启后连续插入几十条笔记,再没遇到锁错误。busy_timeout=3000是指如果数据库正忙就最多等 3000 毫秒,而不是立刻报错,给冲突的写操作一点排队空间。
如果你用的是 Python 内置的sqlite3模块,连接时把这两条 PRAGMA 通过cursor执行一遍就行。而且我自己测试下来,用单个长连接并开启 WAL,比每次新建连接的方案要稳定得多。WAL 模式有一个细节要注意:数据库目录里会多出-wal和-shm文件,备份时这几兄弟文件必须一起处理,否则数据不完整。
3.3 全文搜索:FTS5 用来解决“搜正文”的老大难
笔记系统的搜索和普通查询不一样,它要求按关键词搜 Markdown 正文内容,而且速度要快。SQLite 本身提供了LIKE '%关键词%'的写法,但这意味着全表扫描,笔记量大了之后延迟直线飙升。我最终选择了 SQLite 自带的全文搜索扩展 FTS5,它的原理是建一张倒排索引的虚拟表,把关键词和文档位置的映射提前算好,搜索时就快了。
建立全文索引的步骤大概是:主表notes上新增一个触发器,在title和content更新时,自动同步内容到一张notes_fts的虚拟表。查询时用MATCH语法:
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(title, content, tokenize='unicode61'); -- 两个触发器保持同步 CREATE TRIGGER IF NOT EXISTS notes_ai AFTER INSERT ON notes BEGIN INSERT INTO notes_fts(rowid, title, content) VALUES (new.id, new.title, new.content); END; CREATE TRIGGER IF NOT EXISTS notes_ad AFTER DELETE ON notes BEGIN INSERT INTO notes_fts(notes_fts, rowid, title, content) VALUES ('delete', old.id, old.title, old.content); END; -- 查询示例 SELECT id, title, snippet(notes_fts, 1, '[', ']', '...', 12) FROM notes_fts WHERE notes_fts MATCH '数据库' ORDER BY rank;中文搜索在这套方案里算是一个隐藏坑。FTS5 默认的unicode61分词器是按空格和标点分词的,英文没问题,中文整段话会被当成一个超长词,搜索单个字时出不来预料的结果。我最后用的折中方案是:在写入title和content到全文索引之前,先用代码把中文文本做一次粗切分,在字与字之间插空格,再存进 FTS 表。方法不是最优雅,但在个人笔记场景下实测搜索效果已经接近理想状态。
3.4 定期维护:VACUUM 和完整性检查不能省
SQLite 用得越久,数据库文件越容易“虚胖”。原因是频繁的增删改会在数据库文件里留下空闲页,文件体积比实际数据量大很多。我当时有一个笔记表长期反复编辑,数据库文件从 3MB 涨到 27MB,里面其实大部分是碎片。
解决办法很简单,定期执行VACUUM,它会重建整个数据库文件,把空闲页回收掉。我配置了一个每月一次的定时任务,专门跑如下命令:
sqlite3 /path/to/notes.db "PRAGMA integrity_check; VACUUM;"integrity_check是查数据库物理完整性的命令,如果返回ok就说明文件没问题。我亲测过,27MB 的库VACUUM之后回到了 4MB,效果立竿见影。但注意VACUUM会短暂锁定数据库,所以尽量挑不发生写入的时刻跑,比如凌晨三点。
3.5 备份方案:单文件数据库的专属福利
前面说过,SQLite 最大的优势是“备份即拷文件”。但这句话有个前提:不能直接在数据库正在运行、正在写入的时候复制文件,否则得到的是逻辑上不一致的备份。我当时刚开始就是这么干的,后来恢复测试时发现少了几条笔记,才明白这里面的风险。
安全备份有三种做法:
- 先执行
VACUUM INTO命令:这是 SQLite 自带的功能,把当前库的一致性快照导出到新文件,全程不影响正在运行的库。 - 用备份接口:Python 的
sqlite3模块提供了备份函数,一句conn.backup(backup_conn)就能完成在线热备。 - 停写复制:先把应用暂停写操作,再直接复制文件。个人笔记单个文件很小,这个过程十秒内完成,完全可接受。
我最终选择的是 Python 的备份函数加一个定时任务,每天晚上自动把数据库备份到外部目录并保留最近七个版本,再加一步用压缩工具打个包含时间戳的包。这套方案用了这么久,一次都没出过问题,是真正的省心之选。
4. 踩过的坑和我的对应解法:这些坑不踩一遍真的记不住
4.1 外键约束默认不生效
SQLite 和传统数据库有一个很大的差异:它的外键约束默认是关闭的。我在笔记系统里设计了parent_id字段用于双链引用,本意是引用不存在的笔记时数据库要报错,结果第一次测试时发现,随手插了一个指向不存在笔记的parent_id,数据库毫无反应地接受了。
排查之后才发现,SQLite 只有在每条连接执行PRAGMA foreign_keys = ON时才会启用外键约束。这个设置不像其他配置那样能全局生效,而是每个连接独立控制的。所以在创建数据库连接的地方,这句 PRAGMA 必须作为连接初始化的一部分执行,不然你写再多REFERENCES都是摆设。如果你用其他语言的驱动,同样要在连接建立时手动开启。
4.2 并发写锁死:busy_timeout 救回来一半
个人笔记看起来没有并发,但 Web 后台的多线程请求会让多个线程同时操作数据库。有一次我导入大量笔记时,程序直接抛出了database is locked。我一开始以为是数据库文件损坏,魂都要吓飞了,后来通过错误信息定位才发现只是并发写冲突。
解决办法分两层:
- 连接层设置
PRAGMA busy_timeout = 3000,让竞争写入的线程排队等待而不是立刻失败。 - 应用层引入一个简单的写锁:所有写操作通过同一个线程调度,从根上避免两个线程同时申请写锁。
第二层虽然听起来“重”,但实现起来其实很轻。我用一个后台队列串行处理所有写请求,读请求不受影响。因为个人笔记本来写入频率就不高,串行写入完全不损失体验,反而让稳定性提升了一大截。
4.3 中途改表结构:迁移脚本比删表重建靠谱一万倍
笔记系统迭代过程中改表结构是不可避免的。我早期图省事,遇到字段变化直接删掉表重建,结果就是丢了两次标签数据,痛定思痛之后认真做了迁移机制。现在任何表结构变更都走迁移脚本,每一步操作都是事务化的,核心原则是“先加新字段、再迁移旧数据、最后删旧字段”。
ALTER TABLE notes ADD COLUMN source TEXT NOT NULL DEFAULT ''; UPDATE notes SET source = 'legacy' WHERE source = ''; -- 确认数据无误后再考虑是否删掉旧字段这套流程跑多了以后,我养成了一个习惯:每个数据库文件的头部都记录schema_version,应用启动时检查版本号,低于目标版本就按脚本逐个升级。这个习惯在 SQLite 单文件场景下尤其好用,因为它让“库文件可以跨版本降级使用”这种需求变得可管理。
4.4 文件级备份在写入瞬间拷贝会损坏
还是备份的坑。刚开始我用系统自带的定时复制任务,每天把notes.db复制到备份目录。第一次测试恢复时发现数据对不上,日志里还出现了database disk image is malformed。排查结果是备份命令执行的时候正好有写操作在进行,复制出来的文件处于中间状态。
后来我把备份改成VACUUM INTO和 Python 的备份接口,再没遇到这种问题。所以我对所有用 SQLite 的朋友都有一个强烈建议:永远不要对正在使用的库文件做普通文件复制,要么停写再复制,要么用官方备份机制。这个问题不出则已,一出就是你所有历史数据全军覆没,输不起。
4.5 全文搜索的中文分词:看似能搜,实际搜不全
FTS5 在个人笔记场景下还有一个大坑,就是中文分词。默认情况下整句话被视为一个 token,我搜“数据库”的时候,明明一篇笔记里写了“数据库”三个字,结果就是搜不到,因为索引里存的是“数据库选型经验记”这整个长字符串。
我后来的处理方案是在写入 FTS 索引之前,先用一个简单分词函数把中文字符之间插入空格再存入。这个方法虽然笨,但对个人笔记这种量级完全够用,实测中文搜索的召回率大幅提高。如果你需要更专业的中文分词,可以考虑接第三方分词库,但那就得增加依赖,我个人觉得个人笔记没必要上这么重的方案。
5. 什么时候才应该从 SQLite 切到 PostgreSQL:三个信号和一条迁移路线
5.1 触发迁移的三个信号
SQLite 作为个人笔记的主力库很称职,但它也有极限。用了一年多以后,我总结出三种场景就应该考虑换 PostgreSQL 了:
- 多端并发写入需求出现:比如手机端和电脑端同时在两个地方离线写笔记,然后通过增量同步合并数据。SQLite 本身没有服务端同步机制,你只能把库文件传来传去,但两个文件版的冲突合并会非常痛苦。PostgreSQL 能提供真正的多用户并发控制。
- 数据量超过单文件能轻松处理的范围:我个人的实践经验是,SQLite 数据库文件超过 1GB 时,日常维护如备份、
VACUUM的时间会明显变长,虽然还能工作但摩擦已经起来了。到这个时候,PostgreSQL 的独立服务架构优势才开始体现。 - 需要复杂查询和统计分析:比如按月份统计每天新增笔记数、按标签聚合文章字数、跨表多条件关联,这类分析查询在 SQLite 里能写,但 PostgreSQL 的统计函数和索引类型丰富得多。
这三个信号出现的时机因人而异。我见过数据量 3GB 还在坚持 SQLite 的,也见过笔记只有几万条就早早切 PostgreSQL 的。最重要的判断标准永远是“维护摩擦是否已经超过迁移成本”。
5.2 迁移路径:不是无痛,但可控
如果决定要迁移,SQLite 到 PostgreSQL 没有一个傻瓜化的自动工具,但路线是清晰的。我当时做的步骤大致是:
- 先把 SQLite 的数据导出成通用格式,我用的是 CSV 加 JSON 双保险。
- 在 PostgreSQL 里建好对应的表结构,字段类型要做一次映射。SQLite 的类型比较动态,PostgreSQL 需要明确指定。
- 写一个导入脚本,把 CSV 数据分批插入 PostgreSQL,并即时验证行数是否匹配。
- 业务代码切换到 PostgreSQL 驱动,把 SQL 里 SQLite 特有的写法(比如
datetime('now'))改成 PostgreSQL 的NOW()。 - 跑一遍完整性测试:笔记数量一致、标题内容一致、标签字段解析正常、全文搜索功能等价。
整个迁移在数据量不大时一个下午就能完成。真正的成本不是导入数据,而是把你习惯的思维模式调整到“有一个数据库服务需要被管理”的状态。
5.3 迁移后需要改变的习惯
PostgreSQL 上位之后,你不可能再用“复制文件夹”的方式做备份了,标准做法是定时执行pg_dump。权限管理也从“没有权限”变成了“创建用户、分配表权限”。内存占用和进程维护变成常态,这意味着你的设备需要多留出一部分资源给数据库服务。如果这些对你都不算麻烦,那 PostgreSQL 完全是值得升级的选择。
5.4 多端同步的另一条思路:数据库只做底座,同步放在应用层
我在思考迁移时意外发现,其实个人笔记的多端同步问题不一定要靠“数据库支持多端写入”来解决。另一条思路是:每个终端各用一个 SQLite 库做本地底座,应用层记录操作日志,通过自己的同步协议把增删改事件互相传输并合并。这相当于把数据库服务端的同步需求上移到了应用层,数据库本身保持单机模式。
这条路的好处是,每个端都保留 SQLite 的离线优势,网络只是传事件而不是传文件。缺点是需要自己处理冲突合并逻辑,比如同一篇笔记在两个端各改了标题,到底听谁的。我用过一段时间这套方案,最终因为精力有限还是回到了单机加周期同步的路线,但这条思路确实值得有同步需求的人研究。
6. 我整理的选型清单:以后再做类似项目,我会先问自己这五个问题
6.1 五步选型清单
经历了这一轮完整的选型、搭建、避坑、迁移思考之后,我把方法论沉淀成了五个问题。以后再遇到类似的项目,我会先过一遍这五个问题再决定用哪个库:
- 数据形态是什么:是结构化表格、文档型文本、还是有大量附件?不同类型对应的合适数据库完全不同。
- 并发写入有多高:如果只是个人单机使用,并发上限可能到个位数,嵌入式库完全够用;如果预期有几十人同时写入,就该上独立服务。
- 部署环境允许你装服务吗:如果是为了轻量、便携、离线使用,独立服务进程本身就是一种负担,SQLite 是正解。
- 备份和恢复能力要求多高:单文件复制这种备份方式极度适合个人项目,反过来企业级项目通常需要按时间点恢复,PostgreSQL 这类方案更合适。
- 有没有多端同步需求:如果只是单端使用无脑 SQLite,如果未来确定要同步,也未必直接上 PostgreSQL,可以先存
uuid和操作日志,把同步放到应用层。
这套清单帮我在很多项目上都规避了“拿大炮打蚊子”的尴尬。数据库选型不是选最强的,是选摩擦最小的。
6.2 个人笔记项目的最终结论
回到我自己的个人笔记系统,最终留下的架构是:应用层用 Python 写,数据层用 SQLite,文件附件单独存目录,数据库里存路径。全文搜索靠 FTS5,缓存顺手用一下 SQLite 自己的内存表。整条链路只有一个数据库文件,所有数据都在本地,备份靠一个脚本,恢复靠一个文件。
这套架构在我这台性能不高的设备上跑得非常稳健,开机自启,零维护,每天自动备份。我真实感受是:做完选型清单之后,心里踏实了,因为每一个选择都能说得出理由,而不是“因为大家都在用”。
6.3 最后再分享两个小技巧
第一个是定期检查库文件大小,发现异常变大会顺手VACUUM。第二个是重要数据永远保持冗余备份,SQLite 的单文件特性让这件事便宜又省力。个人笔记的核心是记录本身,数据库只是身后的账本,账本越安静,笔记越安心。