主键 (Primary Key)
简介
主键(Primary Key)是关系型数据库中的核心概念,用于唯一标识表中的每一行记录。在 SQLite 中,主键不仅约束数据的唯一性,还在存储引擎层面扮演着重要角色。
与其他数据库(如 MySQL 或 PostgreSQL)相比,SQLite 的主键处理机制有一些独特的特性,特别是关于 INTEGER PRIMARY KEY 和 ROWID 的关系。
1. 定义主键
在 SQLite 中,可以在创建表(CREATE TABLE)时定义主键。主要有两种定义方式:列级定义和表级定义。
列级定义 (Column-level)
适用于单列主键,直接在列定义后添加 PRIMARY KEY 关键字。
表级定义 (Table-level)
适用于单列或多列(复合)主键。在所有列定义之后,使用 PRIMARY KEY (column_list) 语法。
2. INTEGER PRIMARY KEY 的特殊性
这是 SQLite 中最重要的概念之一。
ROWID 别名机制
默认情况下,SQLite 的每张表(非 WITHOUT ROWID 表)都有一个隐藏的列叫 ROWID,它是一个 64 位的有符号整数。
如果你将一列定义为 INTEGER PRIMARY KEY(必须完全匹配这三个单词,大小写不敏感),那么这一列就变成了 ROWID 的别名(Alias)。
- 存储效率:该列实际上就是
ROWID,不会额外占用存储空间。 - 查询速度:根据该列进行查找是 SQLite 中最快的查询方式(O(log N))。
- 类型陷阱:如果你定义的是
INT PRIMARY KEY或BIGINT PRIMARY KEY,由于类型亲和性(Type Affinity)规则,它不会成为ROWID的别名,且不具备自动增长特性。必须严格使用INTEGER。
自动赋值
对于定义为 INTEGER PRIMARY KEY 的列:
- 如果插入时未指定该列的值,或者指定为
NULL,SQLite 会自动分配一个未使用的整数值(通常是最大值 + 1)。 - 这实际上实现了“自增”功能,而不需要使用额外的
AUTOINCREMENT关键字。
3. AUTOINCREMENT 关键字
许多开发者习惯性地使用 INTEGER PRIMARY KEY AUTOINCREMENT,但在 SQLite 中,通常不需要加 AUTOINCREMENT。
默认行为 vs. AUTOINCREMENT
- 默认 (仅 INTEGER PRIMARY KEY):如果删除了最大 ID 的行,后续插入可能会重用之前的 ID 值(只要该值当前未被使用)。它旨在高效地填补空缺或直接递增。
- AUTOINCREMENT:强制 ID 单调递增。即使最大 ID 的行被删除,新插入的 ID 也会比历史上存在过的最大 ID 大。它保证了 ID 永远不会被重用。
性能代价
使用 AUTOINCREMENT 会引入额外的 CPU、内存、磁盘 I/O 开销,并且会向 sqlite_sequence 系统表中写入数据。
建议:除非业务逻辑严格要求 ID 绝对不可重复使用(例如某些财务记录或对外暴露的订单号),否则不要使用 AUTOINCREMENT。
4. 复合主键 (Composite Primary Key)
如果一行记录的唯一性由多列共同决定,需要使用复合主键。这必须使用表级约束语法。
注意:复合主键中的列即使类型是 INTEGER,也不会成为 ROWID 的别名。
5. NULL 值处理
这 是 SQLite 与标准 SQL 的一个主要区别。
- 标准 SQL:主键列默认隐含
NOT NULL约束。 - SQLite:
- INTEGER PRIMARY KEY:总是
NOT NULL。尝试插入NULL会触发自动赋值。 - 其他类型主键:由于历史遗留原因,早期的 SQLite 允许主键为
NULL。但在现代应用中,这是被视为 bug 的行为。 - WITHOUT ROWID 表:主键列不允许为
NULL。
- INTEGER PRIMARY KEY:总是
最佳实践:除了 INTEGER PRIMARY KEY 外,定义其他类型的主键时,显式加上 NOT NULL 是个好习惯。
6. 修改主键
SQLite 对 ALTER TABLE 的支持有限。你不能直接通过 ALTER TABLE 添加或删除主键约束。
如果需要修改主键(例如从单列改为复合主键),必须遵循“迁移”模式:
- 创建一个带有新主键结构的新表。
- 将旧表的数据复制到新表。
- 删除旧表。
- 将新表重命名为旧表名。
总结
- 推荐使用:绝大多数表都应该有一个
INTEGER PRIMARY KEY列作为主键,以利用ROWID的性能优势。 - 慎用 AUTOINCREMENT:除非绝对必要,否则只写
INTEGER PRIMARY KEY即可,性能更好。 - 类型拼写:必须拼写为
INTEGER,不能是INT。 - 复合主键:使用表级约束语法定义。
- 不可修改:设计表结构时需谨慎,因为后期修改主键比较麻烦。

