关于运行 SQLite 学到的几件事
你好!最近我在做一个 Django 网站,决定使用 SQLite 作为数据库。刚开始为网站选择 SQLite 时,我读了一堆博客文章,都说在小网站的生产环境中使用 SQLite 完全没问题,我也觉得确实没问题,但我当时没有充分意识到,SQLite 终究还是一个数据库,数据库是很复杂的,而我对运维数据库其实了解不多。
所以这里分享一下我在运行 SQLite 过程中学到的几件小事。这是我第 4 个使用 SQLite 的网站,我觉得这一次更难一些,因为借助 Django ORM 的强大功能,我让数据库承担了比以前不用 Django 时更多的工作。
一开始我就像所有博客文章建议的那样开启了 WAL 模式,然后就听天由命了。
ANALYZE 显然很重要
今天我在一张有 4000 行数据的表上执行了一条查询(使用SQLite 的 FTS5 做全文搜索),结果花了 5 秒。这在我看来很不对劲:计算机应该很快才对!
结果发现我需要做的就是运行 ANALYZE!那条有问题的查询立刻从 5 秒降到了大约 0.05 秒(或者某个小到我懒得再去深究的数字)。我至今仍不完全清楚查询计划到底哪里出了问题,但我猜测可能是某种意外二次方的问题。
ANALYZE 会生成“统计信息”(我猜是关于每张表有多少行?可能还有其他信息?),以便查询规划器能做出更好的选择。
也许有一天我会学会怎么看查询计划。
清理数据库很棘手
偶尔我会遇到这样的情况:不小心往数据库里塞了一堆本不该存在的数据(比如来自 django-tasks-db 的已完成任务),想要清理掉它们。
在这种情况下,我遇到过好几次这样的情形:
- 我运行某种命令来清理这些数据
- 由于数据量很大,命令执行超过了 5 秒(说实话,我对这些 DELETE 语句为什么这么慢仍有疑问,也许是有大量 Python 代码在事务中执行,不太确定)
- 在此期间,另一个 worker 尝试写入数据库,5 秒后超时(我设置的超时时间就是 5 秒)
- 该 worker 因为无法写入数据库而崩溃,虚拟机随之关闭
到目前为止,我的做法就是把这些清理操作分小批量进行,这样就不需要执行超过 5 秒的数据库查询。不过,这段经历也让我更能理解,为什么有人会想用像 Postgres 这样能同时支持多个写入者的“真正的”数据库。
也许以后需要做这类操作时,我会直接让网站停机进行计划内维护,不过我还没想好具体的操作流程。
暂时还没有关于 ORM 查询性能的笔记
到目前为止,我一直用 Django 的 ORM 随心所欲地写各种查询,完全没有关注过查询性能,除了 ANALYZE 那件事之外,总体还算顺利。数据库本身很小(大概 10000 行?),而且我预计它会一直保持很小,所以希望这种做法能继续行得通。
备份 SQLite
我尝试过几种备份 SQLite 的方法。我好像还没真正测试过从备份中恢复,但我通常会用死信开关来监控备份是否正常。
方法 1:restic
sqlite3 /data/calendar.db "VACUUM INTO '/tmp/calendar.sqlite'"
gzip /tmp/calendar.sqlite
# Upload backup to S3
# Sometimes the backup gets OOM killed and so it stays locked, do an unlock
restic -r s3://s3.amazonaws.com/some_bucket/ unlock
# Do the backup & prune old backups
restic -r s3://s3.amazonaws.com/some_bucket/ backup /tmp/calendar.sqlite.gz
restic -r s3://s3.amazonaws.com/some_bucket/ snapshots
restic -r s3://s3.amazonaws.com/some_bucket/ forget -l 1 -H 6 -d 2 -w 2 -m 2 -y 2
restic -r s3://s3.amazonaws.com/some_bucket/ prune
方法 2:litestream
我最近开始尝试 Litestream,因为我觉得做增量备份可能会更高效:我的 restic 备份有时会因为内存不足(OOM)被终止,我有点厌倦了。基本上我只需要写一个配置文件,然后运行:
litestream replicate -config litestream.yml
我在配置文件里设置了 retention: 400h,试图保留一部分数据库的历史记录,但我完全不确定它是否真的生效。
我一直备份到 AWS,这总是很麻烦,因为在 AWS 控制台里找地方生成凭证很让人头疼。也许有一天我会换成其他兼容 S3 的替代方案。
你可以使用多个数据库
我当前的项目只有一个数据库,但我在 Mess with DNS 中用过一个技巧:把表拆分到三个独立的数据库文件中,因为我的这些表其实并不需要放在同一个数据库里。我觉得这很有帮助。
Mess with DNS 使用 SQLite 已经运行了 4 年(从 2022 年开始),一直表现很好,我觉得从 Postgres 迁移过来对这个项目来说是个非常正确的选择。
就这些了!
看看自己要花多久才能学会所用技术中那些相当基础的东西,总是件挺有意思的事。我想我第一次在网页项目中使用 SQLite 是在 2022 年,而直到今天我才知道 ANALYZE 的存在!我想再过一两年,我又会学到其他某个非常基础的功能。
一些参考资料
除了官方文档之外,我参考过的一些博客文章:
随机一篇博客