如果我们将Postgres当作SQLite使用会怎样?

Postgres的扩展功能开箱即用。而在SQLite中,每次使用数据库时都需重新加载扩展,且必须在客户端系统上编译并安装。Postgres显然不存在此类问题,因其采用服务器-客户端架构,所有必要扩展均预装在服务器端。


我钟爱SQLite。它就是好用。由于仅涉及本地文件访问,其速度极快。操作逻辑简单明了,运行稳定且经过充分测试。然而多年使用中,我始终怀念Postgres世界里的某些特性。那么,如果将本地Postgres当作SQLite使用会怎样?这意味着什么?本文将深入探讨这个想法。

我曾多次撰文详述SQLite的优势。我曾撰文探讨[云端环境下Go语言与SQLite的最佳实践],并通过基准测试证明它绝非玩具数据库。我想你们可以称我为狂热粉丝。:D

元素周期表

但我确实有些羡慕Postgres的生态系统和社区。首先,其社区规模更大且更具多样性。PostgreSQL作为产品无疑与SQLite同样成熟,且持续获得改进。但最令我倾心的是其庞大的扩展生态系统。

Postgres中的扩展功能

pgvector为例,这个向量数据库扩展使嵌入向量处理成为可能。SQLite中存在类似的sqlite-vec,但pgvector扩展的成熟度似乎远超sqlite-vec,以及SQLite生态中的其他同类扩展。(甚至sqlite-vec似乎已不再积极维护,这虽情有可原,但仍令人遗憾。)

Postgres的扩展功能开箱即用。而在SQLite中,每次使用数据库时都需重新加载扩展,且必须在客户端系统上编译并安装。Postgres显然不存在此类问题,因其采用服务器-客户端架构,所有必要扩展均预装在服务器端。但当你因无法远程访问而需将SQLite生产数据库文件复制到笔记本调试时,若忘记如何在本地系统编译正确版本的扩展,且生产系统架构与本地不同,又赶时间的话,事情很快就会变得令人抓狂。

将Postgres视为仅限本地的数据库

传统智慧告诉我们,为了实现运维稳健性和容错性,应该部署一组Postgres实例,通过某种配置实现相互复制。但这并非放之四海皆准的真理。你完全可以只在单台强大应用服务器上安装Postgres(毕竟使用SQLite时只需一台服务器,采用纵向扩展),并将应用程序直接部署在其旁边运行。

若同时禁用网络访问,仅通过Unix套接字调用Postgres,性能将相当出色。我认为只要配置得当,Postgres甚至能在Docker容器中运行而不造成显著性能损失。

当然,你仍需将所有数据备份至其他位置,以防硬件故障或其他灾难性失效——最好采用增量在线备份方案。这与SQLite完全相同。我使用Litestream实现此功能,相信Postgres生态中也存在类似解决方案。

当然,现在你还得额外操心另一个组件(数据库服务器),它有自己的配置参数、调优等事项。但这和SQLite中那些需要了解并处理的各种配置选项、编译时参数以及运行时陷阱相比,真的有那么大的区别吗?(迄今为止我最“钟爱”的是出于向后兼容性考虑,引用完整性(即外键)默认未启用这一特性。)

我对此仍在深入思考。或许这并非良策,但我仍想尝试实践。

共有 72 条讨论

  1. PostgreSQL的数据目录格式并不稳定且缺乏可移植性。您无法简单地将其压缩后迁移至其他机器,除非新机器采用相同架构且安装了“足够”相似的PostgreSQL二进制文件。

    理论上数据目录可兼容同一主版本的PostgreSQL二进制文件,但实际中存在失败案例——例如二进制文件虽属相同主版本,却因编译选项差异导致兼容性问题。

    1. 确实,升级PostgreSQL版本本身就颇为棘手。若数据量_庞大_,导出/导入方案实在不够理想。虽然可通过gzip/bzip压缩节省空间,但终究不够高效。

      我常希望Firebird能采用更友好的许可协议,它在技术层面本是嵌入式到独立服务器的完美选择。不过目前PostgreSQL显然已全面超越它。

      1. 另一方面,特别是从SQLite迁移时,数据量通常不会大到那种程度。即使是数百GB应该也无妨?如今硬盘速度快得很。做个基准测试应该很有意思。

        1. 这话倒有道理…但每次更新应用时,总要绕过重重障碍才能完成,这种体验实在令人头疼。相比之下,在Docker Compose或K8s配置里直接升级PostgreSQL版本就简单多了。

          如果PostgreSQL能在启动时自动处理旧版本数据库的更新就太好了。

          1. 完全同意。我得研究下这个方案。SQLite的稳定性确实令人满意,连文件格式都设计得相当稳健。

    2. 没错,这正是SQLite的优势所在。

      我好奇是否通过将所有组件打包到Docker容器(包括通过哈希值等标识的特定Postgres容器),并在相同架构上部署就能解决这个问题?

      1. 我负责一个“复杂”的Web应用堆栈,目标是实现自主托管。

        我们的docker-compose.yaml包含Postgres、Redis、OPA、Traefik以及四个服务。运行效果极佳。

        尚未解决的问题是:当镜像进行重大版本更新时,如何实现PG的升级。值得庆幸的是16版本还能长期使用。

    3. 我绝不会将PostgreSQL数据目录压缩后期望在别处恢复。正确做法是执行规范导出。若导出速度过慢,可借助流式复制生成中间文件,再将这些文件迁移至备份位置。

      即便是SQLite,要确保文件复制可靠,也必须设置以下三项参数:

        “_pragma=synchronous(FULL)”,
        “_pragma=checkpoint_fullfsync(ON)”,
        “_pragma=fullfsync(ON)”,
      
      1. 关于Postgres导出方案完全赞同!

        若SQLite支持,我更推荐使用“sqlite3 app.db ‘.backup backup.db’”命令,可确保数据一致性!

    4. 我也遇到过这种情况,确实存在风险。文件权限还曾被破坏过,调试起来简直要命

    5. 那替代方案就是直接用 pg_dump / pg_restore 导出内容?这样有问题吗?

      1. 如果超过10GB,这操作会耗费_相当长的时间_。

        我喜欢pg_*命令,但它们确实算不上最快的工具。

      2. 逻辑复制是个不错的选择,可以临时增加硬件资源。

  2. 部分应用确实如此,我所知最常用的当属Blackmagic的DaVinci Resolve——这款视频编辑器提供功能相对完整的免费版。我认为这与其源于高端网络环境有关,不过本地桌面版仍会安装Postgres。

    1. 哦,有意思!但现在它更偏向桌面应用了吧?写这篇文章时我其实在考虑Web服务器场景,不过现在看来表述确实不够明确。:-)

    1. 这正是https://pglite.dev的核心价值所在

      它是一款可嵌入的Postgres数据库,能在进程内作为本地客户端数据库运行,就像SQLite那样,但本质上是Postgres。

      1. 确实如此。好奇是否有用户在生产环境中将其作为SQLite替代方案使用?若有,其性能表现和使用体验如何?

      2. 希望Go语言也能支持这个方案,或许通过Wazero实现?

        1. 我也这么想。人生时间有限,若有人先做出来我定当尝试一番。:)

          1. 啊,我记得在Github链接里见过你的用户名。:D 感谢你在SQLite与Go领域的贡献!

    2. 有意思!我会去研究的。:-) 感谢分享。

  3. 我觉得这是个绝佳的测试方案。MSSQL的LocalDB在.NET生态中应用广泛:

    https://learn.microsoft.com/en-us/sql/database-engine/config

    对于重负荷生产环境(即挑战硬件极限的场景),我更倾向于采用SQLite方案。虽然Unix套接字速度快,但仍需跨越进程边界。而SQLite可完全在应用程序主线程中运行,这能显著降低内存带宽消耗等资源开销。

    1. 内存带宽我倒不太担心——多数情况下只需为测试搭建小型数据库,存放刚好够用的数据,这样速度应该很快。但套接字和进程确实令人忧心,因为存在与测试无关的故障点,导致测试结果不稳定,最终没人会信任这些测试。

      1. 注:最后一段指的是生产环境而非测试环境。:-)

    2. 我真想再做些基准测试…:D 要是能得到具体数据就太棒了,这话题超级有趣。

    1. 虽然我确实喜欢DuckDB,但不会用它处理OLTP工作负载。

      我很好奇,你究竟在什么场景下想把Postgres当SQLite用?:-) 这基本与我在文章中设想的场景相反。

      1. 我们的某个Web应用需要从Postgres中提取一小部分数据,为此我们采用了DuckDB WASM方案,使其更贴近前端运行。确实,我认同不应将其用于OLTP工作负载。但与博文中作者提到的安装方式不同,这套轻量级替代方案能完美满足应用中对Postgres类数据库的需求——只需添加新文件,执行INSTALL postgres, LOAD postgres,

        ATTACH 'dbname=mydb user=dbname host=25.25.25.25 password=aaaa AS db (TYPE postgres, READ_ONLY);

        即可创建表:
        CREATE TABLE my_duck_table_stream AS SELECT * FROM db.table_stream;

        纯粹好玩

        1. 没想到还能这么操作。遇到类似需求时我会记住这个方法。DuckDB太棒了。:D

    2. 你在什么场景下会从Postgres调用DuckDB?相比在本地部署Clickhouse,这种方案有什么优势?

    1. 哇哦,这看起来超级有趣!之前完全没听说过。感谢分享!

    1. 这只是将Postgres作为进程运行对吧?

      1. 虽然不是楼主,但我认为它确实将Postgres作为进程运行。不过依我之见,SQL的普遍用例是让外部主体(人类、机器)以结构化方式访问底层数据。因此,如果进程能暴露Postgres的TCP/IP端口5432等接口,那么真正实现Postgres的进程内嵌入确实有其价值。(例如将软件接入查询工具、报表界面等)

        除此之外,何必纠结“嵌入”是否涉及子进程?对于集成测试(我认为这是主要场景)以及专业数据分析软件而言,这种方式依然表现优异——毕竟子进程对它们而言无关紧要。

      2. 没错,但它将数据库嵌入可执行文件中,对终端用户完全透明。

        更新:实际上我看到它被下载(且似乎缓存了?)。记不清是否支持嵌入作为选项。

    2. 这使用外部二进制文件、CGO、Wazero(Wasm)还是用Go重写?

      对于SQLite,虽然所有方案都可行,但我最推荐使用https://github.com/ncruces/go-sqlite3——它基于Wazero实现。

      我尽量避免使用CGO,因为它增加了编译时复杂度,对用户编译不太友好。

      1. > 这是使用外部二进制文件、CGO、Wazero(Wasm)还是用Go重写?

        由于Postgres始终通过网络连接,我认为无需任何CGo支持。

        我尽量避免使用CGO,因为它增加了编译时的复杂性,使得用户编译变得不友好。

        使用zig作为C编译器基本能解决这个问题,但无法完全消除复杂性。不过我通过CGo用Zig cc交叉编译到Windows/Mac/Linux系统相当轻松。

  4. 我采用Docker方案实现,尤其适用于运行在单台虚拟机上的小型Web应用。相比SQLite有时奇怪的限制,我更青睐完整的Postgres功能。常规方案包含:Trafik容器负责SSL,Django+gunicorn作为Web应用,以及Postgres容器,所有组件都在单台虚拟机上以容器形式运行。Postgres使用卷存储,我会定期备份。测试时使用eatmydata禁用同步功能,可将测试周期加速2%左右。

    尚未尝试Unix套接字方案,或许该尝试下,不过现有方案性能已足够出色。基于此架构构建的项目曾登上Hacker News首页。关键在于“营销页面”采用CDN托管的静态内容,因此Web应用仅处理注册用户请求。

    1. 嗯,基本情况相同,只是前端用的是Caddy而非Traefik。

      所以你是定期备份,而不是每次写入都增量备份(类似读副本机制)?

      对我而言,尽可能避免已提交数据丢失至关重要。

      (测试时我通过在template1上运行迁移加速了流程,且每次测试都随机分配数据库名。效果惊人。)

  5. 我不记得具体实现细节了,但确实有人专门为单元测试打包过本地PG实例。

    安装前需要预先在机器上部署PG,但一旦就位,测试框架会自动启动并关闭实例。据我所知运行速度相当快。

    1. 这是我的实现版本:https://pkg.go.dev/github.com/sapcc/go-bits/easypg#WithTestD

      最令人恼火的是Postgres绝对拒绝以PID 0运行,这使得在Docker容器中执行make check变得毫无必要地令人沮丧。我理解Postgres默认拒绝PID 0的原因,但真希望它能识别strcmp(getenv(“I_AM_IN_DOCKER_AND_I_DONT_CARE”), “true”)之类的检测机制。

    2. 我认为最佳方案是用docker-compose之类工具在容器中运行Postgres

  6. 据我所知,DuckDB比较接近。它像sqlite那样使用普通文件,但具备Postgres的功能。

    1. 不,它并非“Postgres功能”。它接近SQL标准兼容性,但远不及Postgres的功能。此外还存在单次事务写入、进程内处理等限制。

    2. 若我没记错,DuckDB更适合列式分析查询,而非多列行提取。它具体提供了哪些类Postgres的功能?

      1. DuckDB 确实致力于在 SQL 语法层面兼容 Postgres,但您完全正确——它并未针对单个事务进行优化。我虽是 DuckDB 的忠实拥护者,但若您想将其作为事务型数据库使用,请务必慎重考虑人生抉择。

        1. 是否有类似 DuckDB 的数据库,既适合事务型场景又能兼容 Postgres?

          听起来可能有些混乱,但我的真实需求是先使用更简化的Postgres(基于sqlite?),后续如有需要再迁移到Postgres。

  7. > 你完全可以在单台强大应用服务器上安装Postgres(使用SQLite时通常只需一台服务器,采用纵向扩展),然后直接在旁边运行应用程序。

    我是不是老了?这对我来说很明显啊。

    1. 在云计算兴起之前,对于中等规模的Web应用,这本就是常规做法。

      1. 我始终在数据中心(非云环境)工作,数据库服务器与应用服务器始终分离。除了降低网络延迟,将HTTP服务器和数据库服务器共置于一台机器有何优势?

        看到专用的数据库服务器和应用服务器,总让我感到职责分离的安心感。况且它们在数据中心内相邻部署,似乎也没增加多少开销。

        此外,我们主要出于共享数据库许可证的考虑——让资源充足的多租户/应用数据库服务器为外围Web应用服务器提供服务。

        1. 最大的问题是延迟。网络延迟几乎总是比I/O延迟大几个数量级。当延迟变得非常小的时候,一类问题就会消失。

          1. 明白了,这证实了我的猜测。谢谢。

    2. 呵呵。是也不是。

      云端环境中,如你所知,当前主流做法是单独部署Postgres实例(RDS、Supabase、Planetscale、Crunchy Bridge等平台皆如此)。我们对此已习以为常,以致于很少考虑其他实现方式。

      但我认为工具链已取得长足进步,云计算领域也积累了大量经验,是时候让钟摆回摆,重新审视这些固有假设了!

    3. 没错,你老了。

      我遇到不少年轻开发者:

      – 觉得超媒体很新奇,只认JSON Web API

      – 声称使用云服务是为了“省钱”。
      – 因“太复杂”而依赖第三方服务处理身份验证等基础功能。
      – 将安全逻辑硬编码在前端代码中。
      这就是技术更迭的循环。

      1. 该死…看来我的饭碗还能保住25年。

  8. SQLite的巨大优势不在于它运行在同一台机器上,而在于它是_进程内_的,这使得部署和其他一切都变得更简单。

    1. 完全同意。嵌入式Postgres就很棒(参见评论区提到的pglite,看起来很有意思)。

  9. 你仍需为每次数据库查询承担客户端/服务器间进程间通信的开销,这将丧失使用SQLite的核心优势之一(即IDGAF查询——因几乎零成本而可反复轰炸表的查询模式)。

  10. SQLite更适用于资源受限环境。若我有大量内存可浪费,最初就会选择本地部署PostgreSQL。

    需注意:用于近似最近邻搜索的高级向量嵌入查询技术必然需要海量内存,因此在资源受限环境中使用这类技术本身就缺乏合理性。

    1. 我曾用过配备2MB闪存和8MB内存的SQLite方案。通过microSD卡作为交换空间来生成报告并通过HTTP提供服务。

      后续嵌入式型号升级为8MB闪存和64MB内存,软件架构保持不变。

  11. 简而言之:某人探讨是否可用Postgres替代SQLite。几乎毫无实用价值,未提供任何实际生产环境的经验教训。

    1. 我给这个点赞,因为虽然它提出了批评,但并不显得刻薄,而且事实准确。

      通读全文后我才明白,这其实并非针对SQLite的具体讨论,本质上是在探讨“若将Postgres作为服务器应用程序运行会怎样”。文章的核心观点已充分阐明,再深入阅读也无益处——这恰恰是Postgres近四十年来最基础的部署模式。

      1. 确实,你说得对!但仅通过这里的评论,我就了解了pglite和sqlite-vector。所以若读者既读了文章又看了评论,我希望对你而言仍是净收益——即便文章本身没给你带来新知。

        若真没收获,但愿你没花太多时间阅读。:-)

    2. 确实值得深思!这位仁兄。我认为这类短文往往能在这里和其他地方引发有趣的讨论。:-)

      1. 我同意。读完自己的评论后,我必须道歉,因为语气显得过于轻视。继续思考吧,老兄 🙂

        1. 别介意。:-) 感谢你的后续交流,网络上的好心人。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: