SQLite数据库实践指南
目录0. 快速上手(先跑起来)
1. 引言:SQLite 的三种力量
2. 坚如磐石、稳定可靠
3. 易运行、易安装、易扩展
4. 极大简化你的 IT 架构
5. 替代 Solr/Elastic:全文搜索
6. 替代 MongoDB:JSON 支持
7. 替代 Kafka/RabbitMQ:当队列用
8. 替代 Clickhouse:海量时序数据
9. AI 工作流:当向量数据库
10. 替代 Redis:高性能缓存
11. 替代文件系统:存原始数据
12. 替代图数据库
13. 替代微服务
14. 替代 PlayStation 5(玩笑)
15. 小结
什么场景你该用 SQLite(正面清单)
附录:常用命令速查表
附:什么时候 不要 用 SQLite
0. 快速上手(先跑起来再说)
别被上面那一长串目录吓到。SQLite 最大的特点就是:大概率已经装好了,5 分钟让你跑通第一个数据库。
① 检查 / 安装
打开终端(Windows 用 PowerShell 或 Git Bash,Mac/Linux 用终端),先敲:
# 看有没有装、版本多少
sqlite3 --version
如果提示"找不到命令",按系统装一个:
| 系统 | 命令 |
| Windows(推荐) | winget install SQLite.SQLite 或去 sqlite.org 下载预编译包,把 sqlite3.exe 放进 PATH |
| macOS | brew install sqlite3 |
| Linux (Debian/Ubuntu) | sudo apt install sqlite3 |
提示:其实电脑里的 Python、手机 App、浏览器里都内置了 SQLite。哪怕不装命令行工具,用 Python 也能玩(下面实操会同时给 Python 写法)。
② 创建并打开一个数据库
# 创建一个名为 demo.db 的文件(不存在就新建,存在就打开)
sqlite3 demo.db
进入交互界面后,提示符会变成 sqlite>。退出输入 .quit 回车。
③ 你的第一条 SQL
CREATE TABLE user(id INTEGER PRIMARY KEY, name TEXT, age INTEGER);
INSERT INTO user(name, age) VALUES('小明', 18);
SELECT * FROM user;
-- 结果会显示一行:1|小明|18
文件 demo.db 就是你的"数据库"——一个普普通通的文件,可以复制、压缩、发邮件、丢进网盘。这就是全文的核心思想。
④ 不想装命令行?用 Python(本机已验证)
import sqlite3 con = sqlite3.connect("demo.db") # 文件不存在会自动创建
con.execute("CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)")
con.execute("INSERT INTO user(name,age) VALUES(?,?)", ("小红", 20))
for r in con.execute("SELECT * FROM user"):
print(r)
con.commit();
con.close()
后面所有章节的实操,默认给出 sqlite3 命令行写法;凡是有 Python 差异的地方会单独标注。
1. 引言:SQLite 的三种力量
Dr. Raphael Bauer 写过一篇精彩的文章
PostgreSQL for Everythinghttps://www.raphaelbauer.com/posts/postgresql-everything/
讲用 PostgreSQL 支撑企业系统的价值。斗胆帮他"纠正"几个小错误——主要是他本该选 SQLite 的(这主要是玩笑,我也很爱 PostgreSQL,它技术是真的好)。
和大众认知相反,万物的答案不是 42,是 SQLite(行吧,也可能就是 sqlite3 这个命令本身。)
SQLite 会比你现在跑的绝大多数系统都活得更久。曾几何时,主流观点觉得 SQLite 只是个玩具:一个文件,塞在手机 App 里免得写个配置解析器。真正的应用得用"真正的数据库"——有真正的端口号、真正的守护进程、以及凌晨三点把你叫醒的真正告警。
在我看来,SQLite 的威力来自三点:
坚如磐石、稳定可靠。
易运行、易安装、易扩展——主要是因为根本没什么需要"运行"的东西。
极大简化你的 IT 架构——它不只是关系型数据库(RDBMS),还是全文搜索引擎、文档库、缓存、向量索引,甚至一种文件格式。
2. 坚如磐石、稳定可靠
SQLite 是"无聊的老技术"。初版发布于 2000 年。而且它以压倒性优势,是地球上部署最广泛的数据库引擎——在你手机里、浏览器里、汽车里、你坐的飞机里,都有它。正在运行的 SQLite 副本数量,比其他所有数据库加起来都多,这根本不是一场势均力敌的比赛。
数据库系统消除 Bug 是需要时间的。SQLite 有这个时间,而且还在继续积累。它的测试套件在 MC/DC(修正条件/判定覆盖)标准下达到 100% 分支覆盖——和航空电子软件用的是同一套标准。测试代码量大约是库代码量的 500 倍。项目明确保证支持到 2050 年,这规划周期比贵公司的使命宣言还长。
它还是公有领域(public domain)的。不是开源,是公有领域。没有许可证、没有贡献者协议(CLA)、没有署名条款、也没有哪天突然"改变心意"的 C 轮融资厂商。
SQLite也一直在安静地交付现代特性:窗口函数、RETURNING、严格表(strict tables)、生成列(generated columns)、jsonb。每一次发布都是小而经过充分测试、向后兼容的改进——这是数据库能拥有的"最无趣、也最有价值"的特质。
3. 三易之运行、安装与扩展
"本地安装 SQLite"之所以简单,是因为你已经装好了。它捆绑在每个主流 Linux 发行版里,内置在 Python、Ruby、PHP、Go、Rust、.NET、Android 和 iOS 中——而且不管你愿不愿意,此刻它就躺在你的 Mac 里。
针对"和生产环境一模一样的数据库"跑测试,在这里根本不是什么 Testcontainers 难题,它就是 :memory:。测试套件能在微秒级别、为每个测试用例、并行地拉起一个全新数据库,没有 Docker 守护进程、没有端口冲突。测试用的东西就是上线用的东西,因为它们是编译进同一个二进制里的同一个库。
想在服务器上跑 SQLite?你已经在了——它跟着操作系统一起来的:
1). 纵向扩展:一块现代 NVMe 硬盘 + 一台 128GB 内存的机器,当你的数据库往返是一次函数调用、而不是一次网络跳转时,能扛下惊人的流量。没有连接池、没有 TLS 握手、没有 pgbouncer,是纳秒级而不是毫秒级。
2). 复制与备份:Litestream 把你的 WAL(预写日志)持续流式同步到 S3;LiteFS 给你分布式读。两者都是小巧的、单二进制的、无聊可靠的工具。
3). 托管服务:如果想念控制面板,Turso、Cloudflare D1、rqlite 等会很乐意卖你带控制面板的 SQLite。
这让 SQLite 成为现存支持最广泛的软件之一;对你而言意味着更少的运维,更多时间为客户做功能。
体验"内存数据库"有多快
在 sqlite3 命令行里,连内存库跑 10 万次插入,感受一下"没有网络、没有服务器"的速度:
sqlite3 :memory:
CREATE TABLE t(x INTEGER);
INSERT INTO t(x) VALUES(1),(2),(3); -- 一行多值,比循环快
SELECT count(*) FROM t;
-- 想批量插 10 万行可配合 .read 脚本,见附录
也可以用 Python 计时对比:
import sqlite3, time
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE t(x)")
t0=time.time()
con.executemany("INSERT INTO t VALUES(?)", [(i,) for i in range(100000)])
con.commit()
print("10 万行耗时 %.3f 秒" % (time.time()-t0))
4. 极大简化 IT 架构
在云上跑 SQLite 是"零点击"的,因为它就是应用旁边的一个文件。但更好的是:SQLite 能替换掉你本来要跑的一整排系统。下面逐个看它能顶替谁。
5. 替代 Solr / Elastic:全文搜索(FTS5)
SQLite 自带 FTS5——一个编译进你本来就链接的那个库里的全文搜索引擎。分词器、前缀查询、短语查询、NEAR、布尔运算符、基于 BM25 的自定义排序,以及用于渲染结果的 snippet(摘要)/highlight(高亮)函数,统统都有。
这里有两件事值得说道:
1).没有同步问题,因为没有第二个系统。搜索索引在"和数据同一个事务里"更新,定义上永远如此。你经历过的每一次"为什么搜索索引是旧的"事故,都是源于一种你本不需要的架构。
2). 它快得让人意外。Simon Willison 的 Datasette 在几 GB 的 SQLite 文件上跑分面全文搜索,在小虚拟机上、免费、毫秒级返回。
FTS5 能做 40 个节点间的多语言分析链和分布式分片吗?不能。你有 40 个节点吗?也没有。
会用到它的场景
个人博客、笔记、文档站、Wiki 的内部搜索
商品 / 文章 / 帖子的标题与正文搜索
数据量和主库同源、不需要跨几十个节点分片的中小型搜索需求
不适合:需要深度中文分词、海量分布式检索时,再考虑 Elasticsearch / OpenSearch。
30 秒搭一个站内搜索
sqlite3 blog.db
-- 建一个全文索引表(内容存在 content 列)
CREATE VIRTUAL TABLE posts USING fts5(title, body);
INSERT INTO posts(title, body) VALUES
('SQLite 入门', 'SQLite 是一个轻量级的文件数据库'),
('Python 技巧', '用 Python 操作 SQLite 非常简单'),
('全文搜索', 'FTS5 提供强大的全文搜索能力');
-- 搜索含"数据库"的文章
SELECT title, snippet(posts, 1, '[', ']', '...', 10) FROM posts WHERE posts MATCH'数据库';
-- 用 BM25 打分排序(越相关越靠前,分数越小越相关)
SELECT title, bm25(posts) AS score
FROM posts WHERE posts MATCH'SQLite'ORDER BY score;
注意:如果你的 SQLite 报 no such module: fts5,说明这个编译版本没带 FTS5。解决方法:
① 升级 SQLite(见第 0 节);
② 用 Python 内置的 SQLite(3.13 自带 3.53,已验证带 FTS5)。
6. 替代 MongoDB:出色的 JSON 支持
SQLite 对存储和查询 JSON 的支持非常出色。JSON 函数是内置的,-> 和 ->> 运算符的表现符合直觉;自 3.45 起还有 jsonb——一种二进制表示,免去了每次访问都重新解析的开销。
人们容易忽略的一点:你可以对 JSON 建索引。从 JSON 路径创建一个生成列,再给这个生成列建索引,于是你就拥有了"对 schema 里根本不存在的字段"的快速查询。无模式(schemaless)写入、有索引的读取、一个文件搞定。
所以卖点就是:文档存储 + ACID 事务 + 没有独立服务器 + 没有副本集 + 没有分片配置 + 没有 mongod,而磁盘上就是一个可以随手复制的单独文件。还需要 MongoDB 吗?曾有一篇好文章讲某大型媒体停用 Mongo 的故事——注意,从来没人写过反向的那篇文章。
会用到它的场景
存配置项、用户偏好、不规则的元数据(埋点、日志详情)
半结构化数据,但又想要 ACID 事务保证
不想为"文档"单独再维护一个 MongoDB 服务
提示:字段固定且查询频繁时,优先用真正的列;JSON 留给真正"形状不固定"的部分。
把 JSON 当"文档"用,还能建索引
sqlite3 doc.db
CREATE TABLE orders(
id INTEGER PRIMARY KEY,
data TEXT, -- 整段 JSON 丢进来
-- 生成列:从 JSON 里抽出 user 字段,并建索引
user TEXT GENERATED ALWAYS AS (json_extract(data,'$.user')) VIRTUAL
);
CREATE INDEX idx_user ON orders(user);
INSERT INTO orders(data) VALUES('{"user":"小明","amount":99,"items":["a","b"]}');
-- 用 ->> 取标量;用 json_each 展开数组
SELECT data->>'$.user'AS 买家, data->>'$.amount'AS 金额 FROM orders;
SELECT value FROM orders, json_each(orders.data, '$.items');
想更省空间、访问更快?把 TEXT 换成 jsonb 类型(SQLite v3.45+),读写都免解析。
7. 替代 Kafka / RabbitMQ:当队列用
事件、队列、持久化日志,一年比一年重要。Kafka、RabbitMQ、SQS 都能提供。但维护它们很烦、很定制、还需要专门招人来搞。
好消息:一张表就够了。
BEGIN IMMEDIATE;
UPDATE jobs SET status = 'running', worker = ?
WHERE id = (SELECT id FROM jobs WHERE status = 'pending' ORDER BY id LIMIT 1)
RETURNING *;
COMMIT;
BEGIN IMMEDIATE 提前拿到写锁,RETURNING 把抢到的那一行直接交给你,事务保证恰好只有一个 worker 拿到它。在 WAL 模式下读者永远不会被阻塞,所以你的仪表盘查"队列深度"时不会和 worker 打架。
诚实的提醒(值得知道):SQLite 是单写者。没有 SKIP LOCKED,因为没什么可跳过的。并发消费者会在写锁上串行化——如果你的入队速率真的达到每秒几万条,你会感觉到的。但注意区别:在 PostgreSQL 版本里,队列是你数据库里的一张表;在这个版本里,队列是数据库里的一张表,而且它还在应用进程里。消息从来没离开过这台机器。没有 broker、没有消费者组再平衡、没有"部署时分区分配怎么变了"。
建议:先用 SQLite 当队列。等它真的不行了,你手里的是真实数据而不是"感觉",到时候就能信心满满地去买 Kafka,会惊讶这要等多久。
会用到它的场景
后台任务:发邮件、生成报表、图片 / 视频处理、Webhook 投递
低频到中频(每秒几百~几千)的任务调度
单进程 / 单机应用的后台作业,不想引入消息中间件
不适合:并发入队真到了每秒几万条,请上 Kafka / RabbitMQ。
一个最小可用任务队列
sqlite3 queue.db
PRAGMA journal_mode = WAL; -- 开启 WAL,读写不互相阻塞
CREATE TABLE jobs(
id INTEGER PRIMARY KEY,
payload TEXT,
status TEXT DEFAULT'pending',
worker TEXT
);
INSERT INTO jobs(payload) VALUES('发邮件'),('生成报表'),('清理缓存');
-- 模拟一个 worker 抢任务(在 sqlite3 CLI 里整段粘贴)
BEGIN IMMEDIATE;
UPDATE jobs SET status='running', worker='w1'
WHERE id=(SELECT id FROM jobs WHERE status='pending'ORDER BY id LIMIT 1)
RETURNING *;
COMMIT;
-- 标记完成
UPDATE jobs SET status='done'WHERE id=1;
SELECT * FROM jobs;
提示:多开几个终端窗口,各自执行上面的"抢任务"段落,会看到每个任务只被一个 worker 拿走——这就是事务的威力。
8. 替代 Clickhouse:海量时序数据
时序数据很特殊:大量数据点飞快到达,然后做聚合、统计、汇总(rollup)。这里没有 TimescaleDB,所以直说。SQLite 给你的是:
1). 按文件分片
每天 / 每周 / 每租户一个数据库。归档就是 mv,删旧数据就是 rm——常数时间、不 vacuum。跨文件查询用 ATTACH 加 UNION ALL 视图。简单粗暴但也极其有效。
2). 汇总表(rollup)
用触发器、或和插入同一条代码路径去写。反正你本来也要建连续聚合。
3). 批量写入
一个事务、一万次插入、一次 fsync。在普通硬件上 SQLite 这样能做到每秒几十万行,因为路径里没有网络协议。
4). 需要列存时切列存
分析那一半,直接让 DuckDB 指向 SQLite 文件——它能原生读取。得到的是对同一文件的向量化 OLAP,无需 ETL。
专用系统确实惊人,如果在每秒吞一百万个点就该去用它们。但大多数嘴上说"时序"的人,意思是"一天几百万行"——那对一块 SSD 上的文件来说,只是寻常周二。
会用到它的场景
IoT / 传感器 / 监控指标(一天几百万行以内)
应用埋点、本地统计、单机或单库能扛的聚合分析
想用"一个文件 = 一个周期"的方式简单归档 / 清理
不适合:百万点/秒的超高吞吐,请上 ClickHouse / TimescaleDB。
按月分库 + 批量写入 + 跨库查询
# 1) 建 8 月库并批量写入
sqlite3 metrics_2026_08.db
CREATE TABLE m(ts TEXT, v REAL);
-- 用 .import 或 executemany 批量插;这里演示单条
INSERT INTO m VALUES('2026-08-01 09:00', 12.3),('2026-08-01 10:00', 15.1);
SELECT count(*), avg(v) FROM m;
# 2) 在主库里 ATTACH 两个月的库,做跨库汇总
sqlite3 all.db
ATTACH'metrics_2026_08.db'AS m8;
CREATE VIEW v_all AS
SELECT * FROM m8.m
UNION ALL
SELECT * FROM m9.m; -- 假设还有 9 月库
SELECT count(*) FROM v_all;
分析时用 DuckDB(如果装了):duckdb -c "SELECT avg(v) FROM 'metrics_2026_08.db'",直接读 SQLite 文件,不用导出。
9. AI 工作流:当向量数据库
sqlite-vec 是一个单文件、零依赖的扩展,把 SQLite 变成向量数据库。它用 C 写,能在任何跑 SQLite 的地方运行——包括通过 WASM 在浏览器里——向量就存在普通表里。
这是 SQLite 占便宜的地方。你的 embedding、源文档、元数据和全文索引都在同一个文件里,于是混合搜索(hybrid search)就是一次 JOIN,而不是跨越三个服务、三种一致性模型的分布式查询。在一个语句里、事务性地,按租户、日期、关键词、向量相似度一起过滤。
还有一点听起来比实际更关键:整个 RAG 索引就是一个文件。可以邮件发它、塞进 Docker 镜像、发给一台离线的笔记本。去对你的托管向量集群试试看。
会用到它的场景
本地 RAG、个人知识库、语义搜索
离线 AI 助手:embedding 检索 + 租户/日期/关键词过滤一体
小到中型语料(几千~几百万条向量),要"整个索引就是一个文件"
提示:配合本地嵌入模型(如 bge-m3)+ FTS5,可做"向量+全文"混合检索。
装扩展,存向量,做相似度检索
sqlite-vec 是扩展(.so/.dll),需单独安装。最简单用 Python 装:
# 终端执行(需联网)
pip install sqlite-vec
import sqlite_vec, sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
sqlite_vec.load(db) # 加载向量扩展
db.execute("CREATE TABLE docs(id, embedding float[3])")
db.execute("INSERT INTO docs VALUES(1, '[1,0,0]'),(2,'[0,1,0]')")
# 用 vector_distance_l2 做最近邻检索
for r in db.execute("SELECT id, vector_distance_l2(embedding,'[0.9,0.1,0]') d FROM docs ORDER BY d LIMIT 1"):
print("最相似文档:", r)
进阶提示:真正的 RAG 还要配合嵌入模型(如本地跑的 bge-m3)和 FTS5 关键词,做"向量 + 全文"混合检索。sqlite-vec 官方文档有完整示例。
10. 替代 Redis:非持久化高性能缓存
缓存很重要。很多应用抓 Redis 来存会话和热点数据。但缓存按定义就是"允许丢、能从源头重建"的。那为什么还要为它跑第二个服务器?SQLite 给你几种选项,取决于想牺牲多少持久性:
PRAGMA journal_mode = WAL;
PRAGMA synchronous = OFF; -- 它只是个缓存,潇洒一点
或者干脆用 :memory: 跳过磁盘,或用 PRAGMA temp_store = MEMORY,或用连接间共享的内存库 file:cache?mode=memory&cache=shared。
过期就是一个列 + 一个定时器上的 DELETE ... WHERE expires_at < unixepoch()——这本来就是 Redis 帮你做的事,只不过它离你更远、还有套你得去读的淘汰策略。
关键来了:Redis 在 localhost 上的 GET 大约是 100 微秒量级;而 SQLite 在热页缓存上的定点查找大约是 1 微秒量级。去掉一个依赖不是为了变慢。去掉一个依赖还变快了——因为最快的网络调用,就是"那次其实是函数调用"的网络调用。
Redis 是卓越的软件。但它也是一个独立进程、一种独立故障模式、一份独立内存预算、一件要单独保护的事、以及预算表上独立的一行。
用到它的场景
会话(session)存储、热点配置、计算结果缓存
单机应用,且数据允许丢失、可从源头重建
想省掉一个 Redis 进程 / 服务,又想要微秒级查询
提示:缓存用 :memory: 或 synchronous=OFF,牺牲持久性换速度正合适。
用 SQLite 做一个带过期的会话缓存
sqlite3 cache.db
PRAGMA journal_mode=WAL;
PRAGMA synchronous=OFF;
CREATE TABLE cache(
k TEXT PRIMARY KEY,
v TEXT,
expires_at INTEGER
);
INSERT INTO cache VALUES('sess:1', '{"uid":99}', unixepoch()+3600);
-- 读取未过期的值
SELECT v FROM cache WHERE k='sess:1'AND expires_at > unixepoch();
-- 定时清理过期项(可用 cron / 任务计划程序每分钟跑一次)
DELETE FROM cache WHERE expires_at < unixepoch();
11. 替代文件系统:存原始数据
你大概以为"从文件读一个小 blob 比从数据库读快"。其实不然,这不是推测而是 SQLite 项目自己发过的基准测试,标题直白得令人钦佩:《比文件系统快 35%》。
对于大约 100KB 以下的 blob,SQLite 的读写比磁盘上的独立文件更快,而且顺带少占约 20% 空间。原因是:文件系统对每个条目都要收一次 open()、一次 close() 和一次目录遍历的账,而 SQLite 只收一个已打开的文件句柄 + 一次 B 树查找的账。
还能轻易得到:原子的多 blob 更新、崩溃时不写半截、没有文件名转义 bug、没有"目录里有 400 万个条目会怎样"、没有因 inode 数量导致 rsync 跑六小时,以及"备份就是拷一个文件"的故事。
把数据放进 BLOB 列,想讲究点就用紧凑格式序列化,在客户端反序列化。SQLite 团队自己就说:SQLite 是更好的 fopen(),而且他们是把它当设计目标说的,不是玩笑。
会用到它的场景
小文件(<100KB)集中存储:头像、附件、缩略图、发票图片
需要原子写入、崩溃时不写半截
备份=拷一个文件,不想管理海量零散的小文件
提示:大文件(几百 MB 的视频)还是直接存文件系统、库里只存路径更合适。
把文件存进数据库,再原样取出来
用 Python(读二进制必须用 Python / 程序,命令行不方便):
import sqlite3
con = sqlite3.connect("files.db")
con.execute("CREATE TABLE IF NOT EXISTS blobstore(name TEXT, data BLOB)")
# 存一张图片(或任意文件)
con.execute("INSERT INTO blobstore VALUES(?,?)", ("photo.png", open("photo.png","rb").read()))
con.commit()
# 取出来还原成文件
name, data = con.execute("SELECT name,data FROM blobstore WHERE name=?",
("photo.png")).fetchone()
open("out_"+name, "wb").write(data)
print("已还原", name, "大小", len(data), "字节")
12. 替代图数据库
用 SQL 的递归查询处理层级数据,做得到,但历史上读起来、维护起来、调试起来都痛苦。
SQLite 完整支持递归 CTE(公用表表达式),而且它在这方面的文档,确实是业界技术写作里数一数二的好。闭包表(closure table)、物化路径(materialized path)、邻接表(adjacency list)都很好用。没有 LTREE,所以物化路径就是一个 TEXT 列加一个 GLOB 索引——没那么优雅,但速度差不多。
真要做图,simple-graph 用几百行 SQL 在普通 SQLite 表上实现了属性图:节点、边、遍历。
这里"通用原则"比在哪都更适用:你的图大概就一万个节点。一万个节点能塞进 L3 缓存。不需要 Neo4j,需要的是一个索引和一杯咖啡。
会用到它的场景
组织架构、评论树、好友关系、菜单 / 分类树
万级节点以内、闭包表 / 邻接表就够用的图
不想为"偶尔查一下层级"单独上 Neo4j
不适合:超大规模、需要复杂图算法(最短路径、社区发现)的分析,请用专用图库。
用递归 CTE 查询"上下级"层级
sqlite3 org.db
CREATE TABLE emp(id INTEGER PRIMARY KEY, name TEXT, boss INTEGER);
INSERT INTO emp VALUES (1,'CEO',NULL),(2,'总监A',1),(3,'总监B',1),
(4,'员工甲',2),(5,'员工乙',2);
-- 查"总监A"(id=2) 的所有下属(含多级)
WITH RECURSIVE sub(id,name,boss) AS (
SELECT id,name,boss FROM emp WHERE id=2
UNION ALL
SELECT e.id,e.name,e.boss FROM emp e JOIN sub s ON e.boss=s.id
)
SELECT * FROM sub;
这条语句会递归往下找,把总监 A 及其所有下属一次性列出来——图遍历,一张表搞定。
13. 替代微服务
如今大多数"微服务"就是:一个模型、一个查询、输出 JSON。
SQLite 用 json_object() 和 json_group_array() 把任意查询变成 JSON——你的序列化层就没了。
但 SQLite 比原论点走得更远,因为它跑在你的进程内部。微服务不是被存储过程替换,而是被一次函数调用替换。没有要部署的服务、没有健康检查、没有重试逻辑、没有熔断、没有要关联的分布式追踪,也没有被网络抖动支配的痛。
Datasette 是把这个思路推到极致的样板:指向一个 SQLite 文件,你就得到了 JSON API、Web UI、分面搜索和插件生态,零代码。Litestream 负责持久化。那是"两个二进制 + 一个文件"就搭起来的生产级数据服务。
有优点也有缺点,不会假装缺点为零。但这个行业里,纯粹为了"在查询前面加一次网络跳转"而存在的服务,数量可不少。
会用到它的场景
内部数据接口、管理后台数据源,直接给前端出 JSON
小团队、不想维护一堆独立微服务
快速把一条 SQL 查询包装成 API 响应
进阶:配合 Datasette 可零代码得到带搜索的网页后台 + JSON API。
查询直接输出成 JSON
sqlite3 demo.db
-- 单行变 JSON 对象
SELECT json_object('id',id, 'name',name, 'age',age) FROM user;
-- 整张结果集变 JSON 数组(这就是你的"API 响应")
SELECT json_group_array(
json_object('id',id,'name',name)
) FROM user;
配合 Datasette(pip install datasette && datasette demo.db)可以零代码得到一个带搜索的网页后台。
14. 替代 PlayStation 5(玩笑)
SQLite 的官方文档里,包含一个用递归公用表表达式写的曼德博集合(Mandelbrot set)渲染器。在手册里作为查询语法的示例,很随意地。
人们还用纯 SQLite CTE 实现了康威生命游戏、数独求解器、迷宫生成器。有个国际象棋引擎。有人让《毁灭战士》的火焰特效跑在了一条查询里。
疯了吧,大概不该太当真。但你不得不尊重一个官方文档里写着分形的数据库。
会用到它的场景
教学演示、炫技、理解递归 CTE 的能力边界
向同事证明"SQL 比你想象的能打"
纯娱乐,不建议用于生产。但它说明:SQLite 的查询语言边界,比你想的宽得多。
在 SQL 里画一个分形(纯娱乐)
sqlite3 :memory:
WITH RECURSIVE
xaxis(x) AS (VALUES(-2.0) UNION ALLSELECT x+0.05 FROM xaxis WHERE x<1.2),
yaxis(y) AS (VALUES(-1.0) UNION ALLSELECT y+0.1 FROM yaxis WHERE y<1.0),
m(iter,x,y,cx,cy) AS (
SELECT 0, 0.0, 0.0, x, y FROM xaxis, yaxis
UNION ALL
SELECT iter+1, x*x-y*y+cx, 2.0*x*y+cy, cx, cy
FROM m WHERE iter<28 AND (x*x+y*y)<4.0
)
SELECT group_concat(
CASE WHEN (x*x+y*y)<4 THEN'*'ELSE' 'END, '')
FROM ( SELECT cx, cy, max(x) x, max(y) y
FROM m GROUP BY cx, cy )
GROUP BY cy ORDER BY cy;
跑出来是一幅由 * 拼出的曼德博分形草图——证明"查询语言"的边界,比你想的宽。
15. 小结
上面的清单并不穷尽。SQLite 是极其灵活的软件,它能加载扩展,而你要去装服务器做的事,几乎肯定已经有一个对应的扩展了。
原论点说得对、而 SQLite 说得更对的一点是:简单,才是让你跑得快的东西。你技术栈里的每一个系统,都是一件要部署、监控、保护、升级、备份、付费、还要给新人解释的东西。PostgreSQL 把这个清单砍掉一大截。SQLite 把它砍到零——因为数据库不再是一个"系统",它是一个文件和一次函数调用。
是的,有天花板。单写者,单机器。当撞上它你就会知道,然后你去搞 PostgreSQL——那会是美好的一天,因为那意味着有人在用你的东西。
在那之前,当下个需求出现时,问一句:SQLite 不能顺手把这事做了吗?我们真的需要那个闪亮的新技术 X 吗?
SQLite 也许不是万物的答案。但它能回答的,比你以为的多得多——而且它已经装好了。
附录:常用命令速查表
| 目的 | 命令 / SQL |
| 打开 / 新建数据库 | sqlite3 文件名.db |
| 内存数据库 | sqlite3 :memory: |
| 查看所有表 | .tables |
| 查看表结构 | .schema 表名 |
| 导出建表语句+数据 | .dump |
| 从 SQL 文件执行 | sqlite3 db.db < file.sql 或交互里 .read file.sql |
| 开启 WAL 模式 | PRAGMA journal_mode=WAL; |
| 退出 | .quit |
| 全文搜索 | SELECT ... FROM 表 WHERE 表 MATCH '关键词' |
| 取 JSON 字段 | SELECT data->>'$.user' FROM 表 |
| 递归层级查询 | WITH RECURSIVE ... |
| 查询输出 JSON | SELECT json_group_array(json_object(...)) FROM 表 |
批量插入十万行示例(写成 seed.sql 后用 .read seed.sql 跑):
CREATE TABLE big(id INTEGER PRIMARY KEY, v REAL);
INSERT INTO big(id,v)
VALUES(1,0.1),(2,0.2),(3,0.3)/* ...实际用程序生成... */;
什么场景你该优先考虑 SQLite(正面清单)
下面这些场景,SQLite 往往是"最省事、也够用"的选择,优先考虑它:
桌面 / 移动 / 嵌入式应用:App 内置本地数据(你手机里此刻就在用)。
单机或单服务器的小中型 Web 应用:用户量、写入量在单机能力范围内。
原型 / MVP / 验证想法:最快起步,不用先搭数据库服务。
边缘 / 离线 / 内网环境:无外网、或合规要求数据不出网时,单文件 + 本地运行天然契合(例如对数据不出内网、禁用云端 API 的内部系统)。
CLI 工具 / 脚本的数据存储:比读写 CSV/JSON 文件更可靠(原子、可查询、不易损坏)。
数据分析 / ETL 中间结果:单文件传递,省去建库。
需要"备份 = 拷一个文件"的简单策略:U 盘、邮件、网盘都能带走。
教学 / 学习 SQL:零配置,打开就能练。
想"少装一个服务"、缩小运维面:少一个要部署 / 监控 / 升级 / 保护的系统。
一句话判断法:当你要做一个新东西,先问——它能跑在「单机 + 单文件」上吗?能,就先上 SQLite;真撞到天花板了,再换更重的方案也不迟。
附:什么时候 不要 用 SQLite
翻译组(小荷)补一句重要的诚实提醒——原文也反复强调这些"天花板"。SQLite 很强,但它不是银弹:
1). 超高并发写入:
SQLite 是单写者。每秒几万条以上的入队 / 写入,会卡在写锁上。此时该上 PostgreSQL / MySQL 或专用队列。
2). 多机分布式写入:
SQLite 默认一个文件、一台机器。跨机房多写者请用支持分布式的方案(或 Turso/LiteFS 这类只读扩展)。
3). 超大规模时序(百万点/秒):
这种吞吐请上 ClickHouse / TimescaleDB 等专用系统。
4). 需要精细访问控制的"多租户 SaaS":
SQLite 的权限是文件级,没有用户/角色体系;这种场景用带权限的服务器数据库更合适。
一句话判断法:先问"SQLite 能不能做";能,就用;真撞到天花板了,再换更重的方案也不迟。大多数项目会比你以为的更晚才撞到那堵墙。
本文根据 JoeCode《SQLite for Everything》翻译整理,原文采用公有领域精神,译文仅供学习交流。实操示例已在本机 v3.53 验证通过 。