SQLite 应该具有(Rust 风格)版本
日期:2026-07-15 Git: https://gitlab.com/mort96/blog/blob/published/content/00000-home/00017-sqlite-editions.md SQLite 是一个令人惊叹的数据库引擎。我在许多嵌入式项目中使用它作为数据库,我认为称其为本地数据存储的行业标准并不夸张。一些服务器软件甚至使用它;例如,lobste.rs 现在运行在 SQLite 上。与传统的 RDBMS(关系数据库管理系统)不同,SQLite 不是一个独立的进程;它是一种作为库的 RDBMS,这意味着你的软件仍然是自包含的。与传统文件格式不同,你不需要编写自定义的序列化器和解析器。在某些方面,它是两全其美。然而,唯一一个巨大的问题是,它的默认设置都是错误的。 坏默认设置 #1:外键约束默认被忽略 你没看错。外键约束可以说是我们保持数据库一致性并避免悬挂引用的主要工具。作为快速介绍,SQL 外键约束的样子如下: CREATE TABLE users ( id INTEGER PRIMARY KEY, display_name TEXT ); CREATE TABLE posts ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, content TEXT NOT NULL, FOREIGN KEY(user_id) REFERENCES users(id) ); 其他所有 RDBMS 的典型行为是,帖子中的 user_id 列必须始终引用有效用户的 ID。你不能在不提供有效用户 ID 的情况下创建新帖子;在不删除其帖子时不能删除用户,否则会出现外键约束违规错误。我所知道的唯一一个不默认强制执行此行为的 RDBMS 是 SQLite。SQLite 的 ROWID 重用倾向让问题更严重。你看,在这个示例中,这些 INTEGER PRIMARY KEY 行成为表的 ROWID 的别名,ROWID 是分配给 SQLite 中每行的唯一整数 ID。分配 ROWID 的算法有点复杂(更多详细信息见 SQLite 文档),但它在某些情况下会导致 ID 重用。这意味着悬挂引用很容易导致错误列的引用,这比悬挂引用更糟,因为一切似乎都很好。你在查找时甚至不会收到错误。 看一下我们玩具数据库架构中的假设操作序列: -- Bob 创建一个用户账户 INSERT INTO users (display_name) VALUES ('Bob'); SELECT * FROM users; -- id | display_name -- 1 | Bob -- Bob 发布了一篇介绍帖子 INSERT INTO posts (user_id, content) VALUES (1, '你好,我是 Bob'); SELECT u.display_name, p.content FROM users as u, posts as p WHERE u.id = p.user_id; -- display_name | content -- Bob | 你好,我是 Bob -- Bob 删除了他的账户, -- 但我们忘记删除帖子。 -- SQLite 不会产生错误,因为它忽略了我们的外键。 DELETE FROM users WHERE id = 1; -- Alice 创建了一个账户。 -- 由于 ROWID 算法,Alice 获得了与 Bob 相同的 ID。 INSERT INTO users (display_name) VALUES ('Alice'); SELECT * FROM users; -- id | display_name -- 1 | Alice -- Alice 现在继承了 Bob 的旧帖子! SELECT u.display_name, p.content FROM users as u, posts as p WHERE u.id = p.user_id; -- display_name | content -- Alice | 你好,我是 Bob 解决方案是通过 pragma 启用 foreign_keys: PRAGMA foreign_keys = ON; 如果我们在一开始就这样做,错误的 DELETE 将产生错误: DELETE FROM users WHERE id = 1; -- 运行时错误:外键约束失败 (19) 坏默认设置 #2:列可以存储错误的数据类型 SQLite 具有简单的类型系统:值可以是 NULL、INTEGER、REAL(即双精度浮点数)、TEXT 或 BLOB(即二进制数据)。因此,可以将列定义为保存任何这些类型的值。然而,定义为 INTEGER 列的列并不局限于仅保存整数;相反,SQLite 认为它“使用 INTEGER 亲和性”。这基本上意味着:如果你尝试插入一个 TEXT 值,并且它是有效的整数字符串表示,则将其转换为整数并存储。如果你尝试插入一个 TEXT 值,并且它是有效的实数的字符串表示,则将其转换为实数(即双精度浮点数)并存储。否则,值按原样存储。其他亲和性具有不同但更简单的规则:具有 BLOB 亲和性的列按原样存储值。具有 TEXT 亲和性的列按原样存储 BLOB、TEXT 和 NULL 值,但将数值转换为 TEXT。具有 REAL 亲和性的列的工作原理与具有 INTEGER 亲和性的列相似,除了将整数值转换为 REAL。以下是实际情况的示例: CREATE TABLE music ( id INTEGER PRIMARY KEY, name TEXT, duration_sec INTEGER ); INSERT INTO music (name, duration_sec) VALUES ('Lost In Hollywood', 321); INSERT INTO music (name, duration_sec) VALUES ('Comfortably Numb', 382); INSERT INTO music (name, duration_sec) VALUES ('The Way of All Flesh', '太长了,我是说,来吧'); SELECT * FROM music;
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡