# 一个工具网站什么时候才需要数据库？

URL: https://caijiao.org/posts/135
Source: docs/posts/135.md
Description: 我的第一个工具站先装了 PostgreSQL，半年后最大的表里只有 300 条访问日志。真正需要落库的只有三种情况：数据要跨设备、要跨用户聚合、要参与收款。10015.io 的 50 多个工具全在客户端跑，一条都没踩中。

我的第一个工具站上线前先装了 PostgreSQL：配了连接池、写了备份脚本、建了 migrations 目录。半年后回头看，最大的那张表里躺着 300 条访问日志——这些东西本来一个文本文件就装得下，而那套备份脚本我维护了半年。

后来我给自己定了一条默认规则：不装库，直到出现一个必须由服务端记住的状态。

## 10015.io 的答案是：一直都不需要

Fatih Telis 的 10015.io 有 50 多个工具，几乎所有工具都在客户端运行，极少请求服务端。没有服务端状态，就没有要存的东西。

![10015.io 官网首页截图](/images/2026-09/10015.io.png)

*10015.io 首页没有价格入口，整页只讲一件事：把书签栏里那堆工具链接收进一个盒子。*

这带来的好处比"省了一台数据库"大得多：部署就是推静态文件，没有迁移要跑，没有备份要验证，也不存在数据泄露——你手上没有用户数据，就不存在丢。

TinyWow 走的是同一条路：250 多个工具，月访问在 180 万到 230 万之间（第三方估算，2026 年上半年），依然能承诺上传的文件 1 小时后自动删除。

## 只有这三种情况我会真的装库

**用户要跨设备拿到同一份结果。** 注意是"要"，不是"你以为他要"。Cakedesk 是 Max Schmitt 一个人做的桌面端发票应用，Electron + React + Node.js，数据落在用户本机。这也是一种解法——干脆不跨设备，把数据交给用户自己保管。它一次性卖 69 欧元，2024 年约 170 份，2025 年 239 份新授权，说明相当一部分用户完全可以接受"数据只在我这台机器上"。

**你要跨用户做聚合。** 排行榜、全站统计、公共榜单。只要一次计算需要同时读到别人的数据，就必须有一个中心化存储，没有例外。

**你要收周期性的钱。** Andy Cloak 的 Data Fetcher 有 600 个付费客户、MRR 23000 美元，订阅状态、到期时间、用量计数必须落在服务端。这条几乎没有替代方案。

三条都不成立的时候，数据库只是你给自己找的一份长期工作。

顺便说一个量级上的误解：很多人以为 SQLite 是"玩具数据库"，其实它能撑到的规模比多数人以为的大得多，个人项目几乎不可能先撞到它的天花板，先撞到的通常是写并发和多实例部署这两个结构性限制。所以"以后可能会变大"从来不该成为一上来就上 Postgres 的理由——真到了那天，迁移的活也就一两天，而提前付出的运维成本是按月计的。

## 真要装，先从 SQLite 开始，别一上来就 Postgres

个人项目第二个常见错误，是跳过最轻的那一档。SQLite 是一个文件：备份就是复制文件，它跑在应用进程里，不额外占内存，也不用单独维护一个容器。用 [SQLite](/sqlite/) 起手，等真遇到并发写冲突、或者需要多实例同时读写时再迁移，代价远小于一开始就养一套独立数据库服务。

部署上有个坑值得单独说：容器化之后，数据库文件必须挂到持久化的 volume 上，否则重新部署一次就清空了。这件事在 [Docker](/docker/) 上特别容易踩——镜像里写的文件在容器重建后不保留，而本地测试时一切正常，直到线上第一次滚动更新才发现数据没了。

上手 SQLite 有几个开关要提前知道，它们都不会报错，只是会安静地咬人。外键约束默认是关闭的，得每次连接时显式打开，否则你写的级联删除一条都不会生效；默认的回滚日志模式下读写会互相阻塞，改成 WAL 之后读不阻塞写、写不阻塞读，对一个小站来说基本就够了；还有备份——开了 WAL 之后直接复制文件可能拿到一个不一致的快照，正确做法是用 `sqlite3` 的 `.backup` 命令或者 VACUUM INTO 生成一个稳定副本，再把副本拿去归档。

我自己的迁移触发条件很具体：连续两周观测到写锁等待，或者需要在两个进程之间共享写入。到那一步再换 Postgres，理由就不再是"以后可能会大"，而是有个明确的瓶颈摆在眼前。

另一条减少数据库压力的路是把重计算挪走。图片压缩、格式转换、音视频转码这类活，放进浏览器用 [WebAssembly](/webassembly/) 跑，服务端只发静态文件——既省了存储，也省了排队和清理临时文件的整套机制。

这条路的边界也要说清楚：文件超过几百 MB、处理要跑几十秒、或者结果必须留在服务端供别人下载时，浏览器就不合适了。这时候你要设计的是一套"上传—排队—处理—通知—清理"的机制，而这套机制里的每个环节都需要一个地方记录状态——于是数据库才真正变得必要。换句话说，需要数据库的往往不是"工具"本身，是任务队列。

## 数据库的成本不在机器，在改动

一台最便宜的数据库实例一年几百块，这不是成本。成本是：改一个字段要写迁移、要顾及线上已有数据；升一个大版本要演练回滚；备份要定期验证能不能真的恢复。表结构设计错了改起来比改代码疼得多，因为代码可以重写，数据不能。

我现在给新项目的默认假设是"不装库"，直到出现一个必须由服务端记住的状态。这个假设如果错了，代价是某天重写一层存储；装错方向的代价是，你在项目还没证明自己值得投入的时候，就开始为它做长期维护。

---

*文中 Cakedesk 数据来自作者 Max Schmitt 的年度复盘；TinyWow 流量为第三方估算工具 2026 年上半年数据。*
