两个数据库,两种不同的形态
SQLite 和 PostgreSQL 都使用 SQL,都存储关系型数据,也都能支撑真实的应用。除此之外,它们是为不同的场景而打造的。
- SQLite 是一个库。它运行在你的应用进程内部,从磁盘上的单个
.db文件中读取数据。没有服务器,没有端口,也不需要配置用户。 - PostgreSQL 是一个服务器。它作为独立的进程运行,监听一个网络端口,你的应用作为客户端连接它。
它们之间几乎所有其他差异,包括并发、部署、类型严格程度、性能,都来自这一个架构上的分野。接下来的内容中请记住这一点。
架构:进程内与客户端/服务器
打开一个 SQLite 数据库,就是打开一个文件:
不需要启动守护进程,不需要编辑 pg_hba.conf,也不需要开放端口。你的应用加载 SQLite 库,打开 notes.db,就可以开始运行查询了。部署就是“复制文件”。
Postgres 则更像这样:
# Start the server (once, as an admin):
sudo systemctl start postgresql
# Then connect from your app:
psql -h localhost -U alice -d mydb
你的应用要和另一个独立的进程通信,通常通过 TCP,有时通过 Unix 套接字。多出来的这一层会花掉你一些配置时间,每次查询还要多一次连接往返,但换来的是网络访问、多用户认证和真正的并发写入。
并发是最关键的区别
这通常是决定性的因素。SQLite 会把写操作串行化:任何时刻都只有一个写入者持有数据库文件的锁,其他写入者需要等待。读操作可以并行进行(在 WAL 模式下尤其如此),但写操作只能一个接一个。
Postgres 使用 MVCC(多版本并发控制)和行级锁。许多事务可以同时写入不同的行,彼此不会阻塞。
实际情况是:
- 一个每秒有 50 位读者、只有一位作者偶尔写文章的博客?SQLite 足够了。
- 成百上千个用户同时更新库存的电商结账流程?用 Postgres。
- 移动应用的本地缓存?毫无疑问是 SQLite。
- 带有几十个后台任务进程的多租户 SaaS 后端?用 Postgres。
WAL 模式(PRAGMA journal_mode = WAL;)能大大改善 SQLite 的并发表现,让读操作不再阻塞写操作,但它改变不了同一时间只能有一个写入者的规则。
类型系统:宽松与严格
Postgres 很严格。声明为 INTEGER 的列就是不接受字符串:
-- Postgres
CREATE TABLE t (n INTEGER);
INSERT INTO t (n) VALUES ('not a number');
-- ERROR: invalid input syntax for type integer
SQLite 默认使用类型亲和性(type affinity),它只是一个建议,而不是规则。同样的插入会成功:
字符串就这样待在一个 INTEGER 列里。SQLite 把它存成了文本。这种灵活性是有意为之的设计选择:做快速原型时很有用,用于需要长期维护的表结构时则很危险。
现代 SQLite(3.37 及以上)支持 STRICT 表,行为更接近 Postgres:
如果你要开始一个新的 SQLite 项目,就用 STRICT。它能消除一整类“为什么我的数字列里有个字符串”的意外。
功能范围
几乎在每个方面,Postgres 的功能都更多:数据类型(数组、范围、几何、网络、自定义枚举)、过程语言(PL/pgSQL、PL/Python)、带排名的全文搜索、物化视图、表分区、复制、基于角色的安全机制,以及丰富的扩展生态(PostGIS、TimescaleDB、pgvector)。
SQLite 涵盖了核心功能,还针对它的规模加入了一些不错的特性:JSON 函数、通过 FTS5 实现的全文搜索、R-Tree 索引、窗口函数、CTE、生成列。它略去的是一切以服务器为前提的功能:用户、角色、复制、网络访问。
一个粗略的判断方法:
- 需要 GIS、向量搜索或复制?用 Postgres。
- 需要把数据库打包进 iOS 应用?用 SQLite。
- 两者都需要?很多团队用 SQLite 开发和测试,再部署到 Postgres 上,不过这种混搭可能会在语法差异上让你吃亏(见下文)。
你真正会遇到的语法差异
大部分日常 SQL 是完全一样的。差异主要集中在表结构、类型和少数几个内置函数上:
-- Auto-incrementing primary key
-- SQLite:
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
-- Postgres:
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
-- or, modern Postgres:
CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT);
-- Current timestamp
-- SQLite: CURRENT_TIMESTAMP (returns text)
-- Postgres: NOW() (returns timestamp)
-- Boolean type
-- SQLite: no real BOOLEAN; use INTEGER 0/1
-- Postgres: BOOLEAN with TRUE/FALSE
如果你在 SQLite 上开发、在 Postgres 上部署,最好在你和原始 SQL 之间加一层 ORM 或迁移工具,否则这些差异会渗透到你的应用中。
实话实说:性能
“更快”取决于问的是什么。对于一个只做读取和少量写入的单进程,SQLite 很难被超越:没有网络跳转,没有协议解析,也没有客户端连接。在只有一个客户端的基准测试中,SQLite 在简单查询上往往比 Postgres 更快。
一旦加入并发写入者、需要并行执行查询的大型数据集,或者能从 Postgres 成熟的查询规划器中受益的复杂查询计划,Postgres 就会领先。Postgres 还能纵向扩展(更大的机器、更多的核心),而 SQLite 根本不是为此设计的。
实话总结:SQLite 在它擅长的场景下很快,Postgres 在它擅长的场景下也很快。根据工作负载的类型来选,而不是看基准测试的标题。
快速选择指南
以下情况选 SQLite:
- 数据和某一个应用放在一起:桌面应用、移动应用、嵌入式设备、命令行工具。
- 写操作来自单个进程或少量进程。
- 你想要零配置部署。
- 你在做原型,想把精力放在表结构上,而不是基础设施上。
以下情况选 Postgres:
- 有多个应用服务器或后台任务进程写入数据库。
- 你需要让许多客户端通过网络访问。
- 你需要高级功能:角色、复制、GIS、自定义类型、存储过程。
- 这些数据是一个生产服务持久的中心存储。
一条常见的路径:小项目先用 SQLite 起步,等到流量形态真的需要时再切换到 Postgres。迁移不是没有成本,但它是一项成熟的操作,而且大多数项目永远都不需要迁移。
下一步:什么时候 SQLite 是正确的选择
上面的对比给出了各自的取舍。下一页会更深入地讲讲 SQLite 的优势场景:在哪些工作负载下它不只是够用,而是真正更好的工具,以及哪些警示信号说明你已经超出了它的能力范围。
常见问题
SQLite 和 Postgres 的主要区别是什么?
SQLite 是一个嵌入式库,在你的应用进程中读写单个文件。PostgreSQL 是一个独立的服务器,你需要通过网络连接它。这一个架构上的差异几乎决定了其他所有方面的对比:并发、部署、类型和工具都由此而来。
SQLite 比 Postgres 快吗?
对于单进程的读取和少量写入,往往是的:SQLite 没有网络往返,也没有客户端/服务器协议的开销。对于来自许多客户端的并发写入,Postgres 凭借行级锁和 MVCC 会胜出。“更快”真正取决于工作负载,而不是引擎本身。
生产环境中可以用 SQLite 吗?
可以,只要工作负载类型合适。SQLite 在生产环境中可以很好地支撑网站、桌面应用和嵌入式设备。分界线在于并发写入者:如果很多进程需要同时写入,Postgres 原生就能处理,而 SQLite 会把写操作串行化。WAL 模式有帮助,但无法消除这个限制。
怎么从 SQLite 迁移到 Postgres?
用 sqlite3 mydb.db .dump 导出表结构和数据,然后调整 SQL:AUTOINCREMENT 改成 SERIAL 或 GENERATED AS IDENTITY,类型名称要改,还要清理一些 SQLite 的特殊行为,比如宽松的类型。pgloader 这样的工具能自动完成大部分工作。凡是依赖 SQLite 灵活类型的地方,都要做好重写的准备。