SQLite 数据库越来越大时,人最容易盯住的是文件大小:十几 GB、几十 GB,接下来是不是就会变慢、损坏,或者必须迁移到 PostgreSQL?
最近我给一个长期运行的个人系统做容量体检,也遇到了这个问题。现场并没有出现查询失效,磁盘也还有余量,但数据库增长很快。继续只看文件总量,会得到两个同样草率的结论:要么“SQLite 上限很高,不用管”,要么“文件太大了,赶紧换数据库”。
真正有用的判断不是“多大算大”,而是回答四个问题:什么在增长、增长是否仍有价值、当前读写是否健康、出现故障后多久能恢复。
文件大小是报警器,不是诊断结果
从 SQLite 的实现上限看,普通的几十 GB 距硬上限还很远。SQLite 官方文档写明,在默认 4096 字节页面大小下,默认最大数据库约为 17.5 TB。[1]
但这不等于几十 GB 一定安全。硬上限只回答“格式还能不能装下”,不回答:
- 磁盘还剩多少可增长空间;
- 备份、复制、校验和恢复需要多久;
- 查询是否开始扫描更多页面;
- 写事务是否被长查询拖住;
- 全文索引、二级索引和重复载荷是否正在放大增长;
- 当前运行模式能否承受更长的维护窗口。
所以,我把文件大小当作启动调查的阈值,而不把它直接当作迁移阈值。
第一步:把“一个大文件”拆成四类占用
一次有效的容量审计,至少要分开看四类对象。
1. 业务载荷
先确认最大的表存的是什么:正文、历史事件、工具结果、二进制内容,还是可以重建的缓存。高增长未必是浪费;如果数据确实被检索、审计或恢复使用,它就是业务成本。
反过来,如果大量内容永远不会再被读取,继续保留只是把“舍不得删”伪装成数据治理。
2. 二级索引与全文索引
同一段正文进入主表后,还可能进入普通索引、全文索引、词项表和位置表。原始载荷增长一份,落盘占用却可能增长数份。
我这次排查里,最值得处理的不是主表本身,而是一个运行代码已经支持更紧凑布局、现有数据库却仍保留旧索引结构的问题。更新程序只改变了以后“应该怎样建”,没有自动把既有持久数据改成新结构。
这也是最容易被忽略的一层:代码版本已升级,不等于数据库布局已经迁移。
3. 空闲页面
删除记录后,数据库文件不一定缩小。SQLite 在未启用相应自动收缩模式时,会把不用的页面放入 freelist(空闲页列表),供后续写入复用,文件尺寸可以保持不变。[2]
因此,要同时查看页面大小、总页面数和空闲页面数,例如:
PRAGMA page_size;
PRAGMA page_count;
PRAGMA freelist_count;
freelist_count 返回数据库文件中的未使用页面数。[2] 只有“空闲页很多”成立时,讨论回收空间才有意义;如果页面仍被有效数据和索引占用,直接压缩不会凭空消除业务载荷。
4. WAL 与临时文件
使用 WAL(Write-Ahead Logging,预写日志)时,主数据库之外还要看 -wal 与 -shm。WAL 短时增长不一定是故障,但长期读事务可能阻止 checkpoint(检查点)继续推进。[3]
这就解释了为什么“主库没怎么变,磁盘却突然吃紧”有时不是索引膨胀,而是日志、检查点或长事务问题。主库、WAL、临时文件必须分开量,不能只报一个目录总数。
第二步:测真实使用,而不是只跑完整性命令
容量风险最终要落到用户可感知行为。我更关注下面五组结果:
- 高频查询与中文/全文搜索的实际延迟;
- 写入是否出现排队、锁等待或明显抖动;
- 一段固定观察窗内,主库、WAL、索引分别增长多少;
- 备份能否在可接受时间内完成;
- 恢复一份同量级副本需要多久、需要多少额外磁盘。
这次检查中,代表性搜索仍保持在毫秒级,WAL 也没有持续失控。这个结果说明当前不是“数据库已经不可用”的事故,但不能据此推导未来增长到任意规模都没问题。
最重要的未验证项反而是恢复时间。生产系统真正的容量边界,常常不是能否继续写入,而是出了问题还能不能在业务允许的窗口内恢复。
SQLite 的在线备份 API 可以增量复制正在使用的数据库,并只在读取页面的短时段持锁,让其他用户继续工作。[5] 对长期运行的系统,这比随手复制一个正在写入的数据库文件更适合作为受控恢复点。
第三步:按收益排序治理动作
我不会从 VACUUM 开始。更稳妥的次序是:
1. 先停掉没有价值的新增放大
如果新代码已经收窄索引范围,而旧库仍停在老布局,先做显式、可验证的索引迁移。迁移后观察新的写入放大,再讨论回收历史空间。
2. 再处理保留策略
区分三种数据:
- 必须长期保留的权威记录;
- 可以归档到冷存储、但偶尔要找回的历史;
- 可重建、可过期或重复的派生数据。
不要为了让文件变小,直接删除仍有唯一恢复价值的历史;也不要因为“以后可能有用”,让所有中间结果永久在线。
3. 最后才决定是否重建文件
VACUUM 会重建数据库并压实空闲空间,但官方文档提醒,它执行时可能需要多达原数据库两倍的额外可用磁盘。[4]
这意味着大库上的 VACUUM 不是一个随手优化命令,而是需要恢复点、独占条件、容量预算和中断窗口的写操作。若空闲页很少,它的收益也可能不值得承担这次维护。
什么时候才该迁移到客户端/服务器数据库
真正推动迁移的,通常不是某个整数 GB,而是工作负载改变:
- 多个进程或多台机器需要直接并发写同一个库;
- 写入竞争开始影响业务时延;
- 数据必须跨网络由多个客户端共享;
- 备份、恢复、复制和高可用已经超出单文件运维能力;
- 数据规模、查询模型或权限边界要求分片、只读副本或独立数据库服务。
SQLite 官方给出的边界也很直接:它可以同时服务很多读者,但任一时刻只允许一个写者;高写并发的系统可能需要别的方案。[8]
反过来,若数据库与应用仍在同一台机器、写入可串行、查询稳定、恢复时间可控,那么仅仅因为文件“看起来很大”就迁移,可能只会换来更多组件、网络故障面和维护成本。
结论
SQLite 数据库变大后,正确动作不是先问“它能不能扛到某个数字”,而是完成一张可验证的容量账:
- 主表、索引、全文索引、空闲页和 WAL 各占多少;
- 哪些增长有价值,哪些是旧布局或重复载荷造成的放大;
- 代表性读写是否仍健康;
- 备份与恢复能否落在允许窗口;
- 当前真正受限的是文件大小、磁盘、写并发,还是维护能力。
这次复盘最反直觉的发现是:程序已经升级,不代表旧数据库已经享受新存储策略。先确认持久布局有没有真正迁移,再谈压缩、清理或换库,往往比盯着文件大小做决定更可靠。
本文是 AI 辅助整理的原创工程实践内容,现场数据和系统身份已做匿名化与简化。
来源与核验
- SQLite 官方:Implementation Limits For SQLite(核验于 2026-09-05)
- SQLite 官方:PRAGMA statements(核验于 2026-09-05)
- SQLite 官方:Write-Ahead Logging(核验于 2026-09-05)
- SQLite 官方:Backup API(核验于 2026-09-05)
- SQLite 官方:VACUUM(核验于 2026-09-05)
- SQLite 官方:Appropriate Uses For SQLite(核验于 2026-09-05)