存在于内存中的数据库
SQLite 有一个特殊的文件名::memory:。用这个名字打开数据库,SQLite 就完全不碰磁盘:整个数据库都存放在内存中。表、索引、事务、外键,所有功能都完全一样。唯一的区别是,连接一关闭,数据库就没了。
在命令行中:
sqlite3 :memory:
现在你进入了 SQLite 提示符,面前是一个全新的空数据库,它只存在于内存中。创建表、插入几行、查询它们,一切照常:
退出会话后,这些数据就消失了。不会留下任何文件,因为从来就没有创建过文件。
为什么会需要它
一个重启就丢失的数据库,听起来更像 bug 而不是功能。但它在三种情况下其实很有用。
测试。 每个测试都能在几毫秒内得到一个干净的数据库。不用清理临时文件,没有上一次运行留下的状态,也不会有共享的测试文件被锁住。正因为如此,大多数使用 SQLite 的 Python、Node 和 Go 测试套件都会打开 :memory:。
一次性分析。 加载一个 CSV,跑几条查询,然后扔掉。比搭建一个真正的数据库更快,也比每次都在代码里解析文件更省事。
缓存与临时空间。 在长时间运行的程序中,对于已经加载好的数据,内存中的 SQLite 数据库是一个出奇好用的临时查询引擎。
共同点是:你想要 SQL,但不需要持久化。
性能:更快,但并不神奇
内存数据库跳过了磁盘,所以原本要写入文件系统的操作只是内存更新。受 I/O 限制的工作负载会明显变快。受 CPU 限制的工作负载(复杂的查询规划、大量排序)几乎没有变化,因为 SQLite 本来就会把常用的页缓存在内存中。
快速演示一下,语法完全一样:
这段代码运行在内存数据库上,但它和你在文件数据库上运行的 SQL 一模一样。数据库引擎并不在乎。
内存数据库还是文件数据库:怎么选
取舍很简单,值得说清楚:
- 文件数据库(
mydata.db):重启后数据仍在。多个进程可以打开它。能在崩溃后保住数据(使用 WAL 模式时大体如此)。凡是需要记住东西的场景,都用它。 - 内存数据库(
:memory:):关闭即消失。默认只属于打开它的那个连接。对于写入频繁的一次性工作更快。用于测试、临时工作和短期缓存。
如果拿不准,你需要的是文件数据库。内存数据库是特例。
每个连接都有自己的数据库
有个细节常常让人中招:打开两次 :memory:,你会得到两个独立的数据库。它们不共享表,不共享数据,彼此完全看不到对方。
-- Terminal 1
sqlite3 :memory:
sqlite> CREATE TABLE t (x); INSERT INTO t VALUES (1);
-- Terminal 2
sqlite3 :memory:
sqlite> SELECT * FROM t;
Error: no such table: t
这不是 bug,而是设计如此。:memory: 的意思是“这个连接的私有数据库”。在同一个程序中也是一样:如果你的代码打开了两个到 :memory: 的连接,它们会各自得到一个隔离的数据库。
在多个连接间共享内存数据库
如果确实需要多个连接看到同一个内存数据库,SQLite 通过 URI 文件名和共享缓存来支持。关键的字符串是 file::memory:?cache=shared:
sqlite3 'file::memory:?cache=shared'
同一进程中任何打开这个 URI 的连接都会加入同一个数据库。把它们全部关闭,数据库就没了。
你还可以给内存数据库起个名字,当你想要几个不同的共享数据库时很有用:
sqlite3 'file:mydb?mode=memory&cache=shared'
这里的名字 mydb 只是一个标签,依然没有文件。打开 file:mydb?mode=memory&cache=shared 的两个连接共享同一个数据库;打开 file:other?mode=memory&cache=shared 的连接则得到另一个数据库。
把内存数据库保存到磁盘
有时你在内存中完成了整个流程,然后决定要保留结果。命令行工具为此提供了 .backup 点命令:
sqlite3 :memory:
sqlite> CREATE TABLE results (id INTEGER, score REAL);
sqlite> INSERT INTO results VALUES (1, 0.91), (2, 0.87);
sqlite> .backup snapshot.db
sqlite> .quit
snapshot.db 现在是一个普通的文件数据库,内容完全相同。以后你可以用 sqlite3 snapshot.db 打开它,从上次停下的地方继续。
反过来也可以:.restore 把一个文件数据库加载到当前连接的内存中:
sqlite3 :memory:
sqlite> .restore snapshot.db
sqlite> SELECT * FROM results;
在应用代码中,SQLite 的 C API 通过 sqlite3_backup_init 提供了同样的机制,大多数语言绑定都对它做了封装。例如 Python 的 sqlite3 模块就有 Connection.backup()。
一个常见的坑
有时人们会试图通过附加一个文件再复制数据来“保存”内存数据库:
对于简单的表复制,这样可以,但它不会原样保留索引、触发器、视图或外键。要完整地复制整个数据库,请使用 .backup(或备份 API):它在页级别做逐字节精确的复制。
要点总结
:memory:是 SQLite 的一个特殊文件名,它在内存中创建数据库,没有对应的文件。- SQL 和文件数据库完全一样:同样的表、同样的查询、同样的约束。
- 每个到
:memory:的连接都是私有的;当多个连接需要共享时,使用共享缓存 URI(file::memory:?cache=shared)。 - 它适合测试、一次性分析和短期缓存,不适合任何需要在重启后保留的东西。
- 当你决定要保留内存数据库时,用
.backup把它保存到磁盘。
下一篇:创建表
你已经在几个例子中见过 CREATE TABLE 一闪而过。下一页会放慢脚步,好好讲解它:列定义、类型、可以附加的约束,以及让表结构用起来顺手的那些小选择。
常见问题
如何创建 SQLite 内存数据库?
打开 SQLite 时用特殊的文件名 :memory: 代替路径。在命令行中是 sqlite3 :memory:;在库中就是调用对应的连接函数,并把 :memory: 作为文件名传入。数据库存在于内存中,连接关闭时就会消失。
SQLite 中的 :memory: 是什么?
:memory: 是 SQLite 识别的一个特殊文件名,意思是“不使用文件,所有内容都放在内存里”。你得到的是一个完整的 SQLite 数据库,表、索引、事务一应俱全,但永远不会有任何内容写入磁盘。每个打开 :memory: 的连接都会得到一个自己私有的数据库。
两个连接可以共享同一个 SQLite 内存数据库吗?
默认不行:每个 :memory: 连接都是相互隔离的。要共享,就用 file::memory:?cache=shared 这样的 URI 打开它,并启用共享缓存。在同一个进程中,所有打开这个 URI 的连接都会看到同一个数据库。
SQLite 内存数据库可以保存到磁盘吗?
可以。在命令行中用 .backup 命令,或者在库中用备份 API,把内存数据库复制到一个文件中。你也可以 ATTACH 一个文件数据库,再用 INSERT INTO file.table SELECT * FROM main.table 把数据复制过去。