Menu

SQLite ATTACH DATABASE:跨多个数据库文件查询

ATTACH DATABASE 如何让你在一个连接中打开多个 SQLite 文件,用 schema 前缀跨文件查询,并在用完后分离。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

一个连接,多个文件

SQLite 连接并不局限于单个文件。使用 ATTACH DATABASE,你可以在最初打开的文件之外再打开其他 .db 文件,并像查询同一个数据库里的不同 schema 一样查询它们。这是 SQLite 中最接近“一台服务器上有多个数据库”的功能。

基本写法:

如果 archive.db 文件不存在,就会被创建,这一点和主数据库一样。在这个会话中,从现在开始,所有带 archive. 前缀的内容都位于第二个文件中。带 main. 前缀(或不带前缀)的内容则位于原来的文件中。

你的连接总是有两个隐式的 schema:main(你最先打开的文件)和 temp(存放临时表的临时空间)。附加会增加更多 schema。

语法以及别名的作用

ATTACH DATABASE 'path/to/file.db' AS alias_name;

别名就是你用来限定表名的 schema 名称。它只在当前连接中有效:另一个连接附加同一个文件时,可以用不同的别名。选一个简短又能说明用途的名字(archive、analytics、cache),因为你会经常输入它。

有几点值得了解:

  • 除非是绝对路径,否则路径相对于进程的工作目录。
  • 字符串 ':memory:' 会以该别名附加一个全新的内存数据库。
  • 别名不能和 main 或 temp 冲突,多次附加也不能重复使用同一个别名。

跨数据库联接

大多数人使用附加,就是为了这个功能。两个文件位于同一个连接中之后,你就可以在一条查询中联接它们的表:

查询规划器对待这两个 schema 的方式,和对待 main 中的表完全一样。附加表上的索引会被使用,EXPLAIN QUERY PLAN 也能跨 schema 工作。这里没有网络往返:两个文件都在同一个进程中打开。

这在很多场景下都很实用:把热数据和冷归档分开、按租户拆分文件,或者从只读的查找数据库中读取参考数据。

只读附加和内存附加

如果第二个数据库是你只想读取、永远不会修改的,比如随应用一起发布的参考数据集,就用 URI 以只读方式附加它:

URI 写法要求 SQLite 库启用了 SQLITE_OPEN_URI(命令行工具和大多数语言绑定都已启用)。之后,任何针对 ref.* 的 INSERT、UPDATE 或 DELETE 都会在触及文件之前报错。

内存附加同样适合用来暂存数据:

连接关闭时,scratch 就会消失。它和 temp 类似,但生命周期由你控制。

事务覆盖所有附加的数据库

一次 BEGIN/COMMIT 覆盖对 main 和所有附加 schema 的写入。要么全部提交,要么全部回滚,原子性在多个文件之间得到保证:

把行从在用的表移到归档文件,正是需要这种保证的操作。如果没有跨文件的原子性,中途崩溃就会留下重复的行,或者更糟,丢失数据行。

有一点需要注意:当一个事务中有多个附加的数据库被写入时,SQLite 会使用一种更谨慎的提交协议,需要一个临时日志。它比单文件提交慢,但依然安全。

分离

用完一个附加的数据库后,把它去掉:

DETACH DATABASE archive;

磁盘上的文件不会受到任何影响,DETACH 只是关闭当前连接中的句柄。需要记住两个限制:

  • 你不能分离 main 或 temp。
  • 你不能分离一个正处在事务中、或者还有针对它的未关闭语句的数据库。

如果忘了分离,也没什么大不了:关闭连接时一切都会被清理掉。

限制和常见错误

有几个实际的限制值得了解:

  • 默认上限是每个连接附加 10 个数据库(另外还有 main 和 temp)。编译时可设置的最大值是 125。超出限制时,你会看到 too many attached databases - max 10。
  • 每个附加的文件都会占用一块页缓存。附加十几个大型数据库是有代价的,内存占用会上升。
  • ATTACH 本身不能在事务中运行。请在 BEGIN 之前或 COMMIT 之后运行它。

你可能会遇到的几个错误:

-- File doesn't exist and the directory isn't writable:
Error: unable to open database: 'missing/path.db'

-- You tried to write to a read-only attachment:
Error: attempt to write a readonly database

-- You used the same alias twice:
Error: database archive is already in use

大多数错误一看就明白。“already in use”这个最容易让人犯错:ATTACH 不会替换已有的别名,你必须先 DETACH。

一个实际的模式:冷热分离

把这些结合起来:下面是一个小型归档流程,把一年以前的订单从主数据库中移出去:

旧的行移到 archive.orders,近期的行留在 main 中。需要历史数据的报表可以跨两者联接;日常针对 main.orders 的查询依然很快,因为这张表变小了。同一个连接,两个文件,一个事务。

下一步:预处理语句

ATTACH 讲的是让一个连接访问更多数据。接下来的一组主题讲应用如何安全、高效地与 SQLite 交互,首先是预处理语句,它是参数绑定和防注入查询的基础。

常见问题

SQLite 中的 ATTACH DATABASE 是做什么的?

ATTACH DATABASE 'file.db' AS alias 在当前连接中打开第二个 SQLite 数据库文件,并给它一个 schema 名称。之后,你可以用 alias.table_name 引用它的表,并在一条查询中把它们和主数据库中的表联接起来。

SQLite 一次可以附加多少个数据库?

默认情况下,每个连接最多可以附加 10 个数据库,另外还有 main 和 temp 这两个 schema。硬性上限是 125,可以在编译时通过 SQLITE_MAX_ATTACHED 配置。超出限制时,你会得到 too many attached databases 错误。

可以在一条语句中跨多个附加的 SQLite 数据库查询吗?

可以。附加之后,用 schema 名称限定每张表:SELECT * FROM main.users JOIN archive.orders ON ...。联接、子查询和 INSERT ... SELECT 都可以跨 schema 使用。事务也覆盖所有附加的数据库,所以一次 COMMIT 在多个文件之间是原子的。

如何分离一个 SQLite 数据库?

运行 DETACH DATABASE alias。磁盘上的文件不会受到任何影响,DETACH 只是关闭当前连接中的句柄。你不能分离 main 或 temp,也不能分离一个正处在事务中的数据库。

Coddy 编程语言插图

和 Coddy 一起学编程

开始