默认值与自增主键
本教程共 50 篇 · 第 12 篇 · 更新于 2026-07-31
12. 默认值与自增主键
本节目标:学完本章你能正确写出”自增主键”——知道默认就该用 INTEGER PRIMARY KEY 隐式自增,理解 AUTOINCREMENT 只是可选的补充,并掌握 DEFAULT 给列设默认值。
这一章要解开 SQLite 入门者最容易被误导的两个点:一是”自增主键到底怎么写”,二是”列的默认值怎么给”。很多教程一上来就教 INTEGER PRIMARY KEY AUTOINCREMENT,让人以为少了 AUTOINCREMENT 就不自增。真相恰恰相反:在 99% 的场景里,你根本不需要 AUTOINCREMENT 关键字。
DEFAULT:当没给值时填什么
先看简单的 DEFAULT。建表时给某列加 DEFAULT 值,意思是:插入一行时如果没指定这一列,SQLite 就自动用默认值填空。
CREATE TABLE users(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER,
email TEXT,
status TEXT DEFAULT 'active',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
这里 status 默认 'active',created_at 默认用 CURRENT_TIMESTAMP(SQLite 内置表达式,返回当前时间的 ISO8601 文本)。插入时不写这两列试试:
sqlite> INSERT INTO users(name, email) VALUES('张三','a@b.com');
sqlite> SELECT id, name, status, created_at FROM users;
1|张三|active|2026-07-31 10:22:05
DEFAULT 的几条要点:
- 默认值可以是字面量(
0、'active'),也可以是简单的常量表达式(如CURRENT_TIMESTAMP、(datetime('now')))。 - 如果显式写了
NULL(而不是”省略这一列”),DEFAULT 不会生效——NULL 就是 NULL。 - DEFAULT 和 NOT NULL 可以共存:先填默认,再保证不为空,常见于”创建时间""状态标记”这类字段。
关于 DEFAULT 的表达式写法,有两个容易踩的点要提前讲清。第一,凡是函数或运算形式的默认值,必须套在括号里,例如 DEFAULT (datetime('now'))、DEFAULT (abs(-3));如果漏掉括号写成 DEFAULT datetime('now'),SQLite 会把 datetime 当成列名去解析而直接报语法错。第二,这里的”表达式”指的是插入时刻可求值的常量表达式,它不能引用表中的其他列、也不要依赖随机状态(除 CURRENT_TIMESTAMP 这类时间函数外)。善用表达式默认值,可以把”默认值是另一个计算结果”的逻辑直接固化在建表语句里,省去一段应用代码。换句话说,DEFAULT 不只填”一个固定的值”,也能填”一个在插入时才算出来的值”。
Tip实用技巧:给”创建时间""是否删除”这类字段设 DEFAULT,比在应用代码里每次手工填更省心,也避免忘了填导致 NULL。注意
CURRENT_TIMESTAMP给的是 UTC 时间文本,展示时再按需要转换时区。
自增主键的真相:INTEGER PRIMARY KEY 已经自带自增
接下来是重头戏。回忆第 10 章提过:默认每张表都有一个隐藏的整数列 rowid,它唯一标识每行,并且自动递增——你插入新行不指定 rowid,SQLite 就给它分配”当前最大 rowid + 1”,从 1 开始。
关键来了:如果你把某一列声明为 INTEGER PRIMARY KEY,这一列就不再是普通列,而是变成了 rowid 的别名(alias)。换句话说,id 和 rowid 是同一个人、两个名字。于是 id 自动获得了 rowid 的自增能力:
CREATE TABLE users(
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
连续插入不指定 id:
sqlite> INSERT INTO users(name) VALUES('张三');
sqlite> INSERT INTO users(name) VALUES('李四');
sqlite> SELECT id, name FROM users;
1|张三
2|李四
看到了吗?没写 AUTOINCREMENT,id 也自动变成 1、2、3…… 这就是 SQLite 推荐的”自增主键”写法,零额外开销。
Note重点提示:只有写成
INTEGER PRIMARY KEY才会成为 rowid 别名。写INT PRIMARY KEY、BIGINT PRIMARY KEY都不会触发——必须含完整的INTEGER这个词。另外INTEGER PRIMARY KEY DESC(降序)也不会成为别名,这是一种少见但坑人的写法,入门阶段别碰。
AUTOINCREMENT:可选的关键字,不是必需品
那 AUTOINCREMENT 到底干嘛用?它解决的是 rowid 默认行为的一个边缘情况:rowid 在删除一些行后,会复用被删掉的 id。例如表里有 1、2、3,你删了 3,再插一行,新行可能又拿到 3。
对绝大多数应用,这完全无所谓——你本来就不该拿 id 当业务含义(比如”3 号订单”被复用没关系,因为它是系统内部标识)。但少数场景(如审计、合规)要求”id 永不重复、删了也不许再出现”,这时才用 AUTOINCREMENT:
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
加了这个关键字后,SQLite 会用一个叫 sqlite_sequence 的内部表记住”已经发到的最大 id”,下次永远发比它更大的数,绝不回头复用。
代价是什么?官方文档明确说:AUTOINCREMENT 会带来额外的 CPU、内存、磁盘空间和磁盘 I/O 开销,能不用就不用。具体坏处有两点:
- 多了
sqlite_sequence依赖:数据库里多一张系统表来记账,建表、插入都要额外读写它。 - 行为更”脆”:一旦 rowid 触达上限 9223372036854775807(极大,几乎碰不到),普通 rowid 还能找别的空号用,而 AUTOINCREMENT 表会直接报
database or disk is full错误,因为”永远递增”已经无号可增。
所以请把顺序记牢:默认写 INTEGER PRIMARY KEY;只有”绝不复用 id”是硬需求时才加 AUTOINCREMENT。
Warning常见坑:很多教程把
AUTOINCREMENT写成自增主键的”标配”,这是误导。正确认知是——自增能力来自INTEGER PRIMARY KEY(rowid 别名),AUTOINCREMENT 只是防止 id 复用的可选补充。盲目加上它,只是白白吃掉性能和多一张内部表,却换不来任何你真正需要的收益。另外注意AUTOINCREMENT必须紧跟在INTEGER PRIMARY KEY之后,单独写INTEGER AUTOINCREMENT是语法错误。
指定 id 也能覆盖自增
自增是”你不填时”的行为。如果你显式给 id 赋值,SQLite 就听话用你的值:
sqlite> INSERT INTO users(id, name) VALUES(100, '王五');
sqlite> SELECT id, name FROM users;
1|张三
2|李四
100|王五
之后不指定 id 再插,会从”当前最大值 + 1”继续,也就是 101。这也是 rowid alias 的标准表现。
对比一览:INTEGER PRIMARY KEY vs AUTOINCREMENT
用一张表把两者差异摆清楚,避免再混:
| 写法 | 是否自增 | 是否复用已删 id | 额外开销 | 何时用 |
|---|---|---|---|---|
INTEGER PRIMARY KEY | 是(rowid 别名) | 会复用 | 无 | 默认、绝大多数场景 |
INTEGER PRIMARY KEY AUTOINCREMENT | 是 | 永不复用 | 有(sqlite_sequence) | 仅当”id 永不可重复”是硬需求 |
记住结论:自增能力两种写法都有,差别只在”删过的号还让不让人用”。普通业务用第一行就够。
一个例外:WITHOUT ROWID 表
前面说 INTEGER PRIMARY KEY 是 rowid 别名,前提是这张表有 rowid。如果你建表时加了 WITHOUT ROWID(为某些海量小表省空间、改 B 树结构),表里根本没有 rowid,此时 INTEGER PRIMARY KEY 就退化成普通主键,不再自动自增,必须自己管理 id。这是进阶主题,入门阶段默认不写 WITHOUT ROWID,所以”INTEGER PRIMARY KEY 隐式自增”的套路始终成立。
DEFAULT 与自增能否共存
可以。id 走自增(本质 DEFAULT 是 rowid 的自动分配),其他列走 DEFAULT 字面量,互不冲突:
CREATE TABLE orders(
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
类比小结
把 INTEGER PRIMARY KEY 想成”带着自动编号机的抽屉”:你往里放东西,编号机自动贴 1、2、3 号,根本不用你操心——这就是隐式自增。而 AUTOINCREMENT 是”编号机旁边加了一本永不撕页的账本”,保证号码只增不减、用过就作废;账本要人看管(开销),而且号码用尽就卡死。普通人家用自动编号机就够了,没必要天天抱着账本。DEFAULT 则像”忘了填就按惯例补上”的便签——没写状态就默认 active,没写时间就盖当前时间戳。
Note重点提示:记住一句口诀——自增靠
INTEGER PRIMARY KEY(rowid 别名),AUTOINCREMENT仅用于”永不复用 id”的少见需求,且需承担 sqlite_sequence 开销。这是第 09 章”动态类型”之后第二个高频误解点,面试和实战都常考。