INSERT:插入数据
本教程共 50 篇 · 第 14 篇 · 更新于 2026-07-31
14. INSERT:插入数据
本节目标:学完本章你能用 INSERT 向表中写入数据,掌握单行插入、一次插入多行、只给部分列赋值,以及用 INSERT OR REPLACE / INSERT OR IGNORE 处理”重复”或”冲突”的写入。
先建立一个关键认知:写入就是写文件
SQLite 是 serverless(无服务进程)/ 零配置 的嵌入式数据库。它背后没有像 MySQL、PostgreSQL 那样独立运行的服务进程(daemon),所谓”数据库”其实就是磁盘上的单个文件,比如 mydb.db。你每敲一条 INSERT,SQLite 引擎都是直接打开这个文件、在对应表里追加或修改一行记录。
理解这点能帮你少踩很多坑:插入数据本质上是一次本地文件写入,不需要先”连服务器”、不需要启动任何后台服务、也不需要账号密码去认证连接。这也意味着——写进去的数据,换一个程序、甚至换一台电脑,只要把这个 .db 文件拷过去,数据就跟着过去了。写入和读取(SELECT 返回的结果集)是一体两面:先有 INSERT 写进去,后面才能 SELECT 查出来。
为什么需要 INSERT
建好表(CREATE TABLE)之后,表里是空的。关系型数据库里,数据以”行(row)“为单位组织,每一行代表一条完整记录。INSERT 语句就是往表里追加新行的唯一标准方式。
你当然可以用文本编辑器手动打开 .db 文件去改字节,但那等于直接破坏数据库内部结构,十有八九会让文件损坏。INSERT 才是 SQLite 官方认可、安全、可回滚的写入入口。
最基本的写法:单行插入
最完整的 INSERT 需要两部分信息:往哪张表插、每一列分别是什么值。
INSERT INTO users (id, name, age, email)
VALUES (1, '张三', 28, 'zhangsan@example.com');
在命令行里演示就是这样(注意 sqlite> 是 SQLite CLI 的提示符,你只需要敲后面的语句):
sqlite> INSERT INTO users (id, name, age, email)
...> VALUES (1, '张三', 28, 'zhangsan@example.com');
sqlite> SELECT * FROM users;
1|张三|28|zhangsan@example.com
这里 users 表沿用本教程全局统一的示例表结构:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER,
email TEXT
);
Note
id列声明为INTEGER PRIMARY KEY,它会成为表内部ROWID的别名,具备隐式自增能力:你插入时若不指定id,SQLite 会自动填入当前最大 id + 1。所以日常插入用户数据,完全可以省略id,让 SQLite 自己分配。除非你确实需要手动控制主键值,否则不必写AUTOINCREMENT关键字——那是另一回事,仅在”已删除的 id 永不复用”这种特殊需求下才用。
指定列插入:顺序不重要,缺省走默认值
刚才的写法是”列名 + 值”一一对应。更推荐的写法也是显式写出列名,好处有两个:
- 列顺序自由:值只要和括号里的列名顺序一致即可,不必和表定义里的物理顺序一致。
- 可以省略有默认值的列:表里如果某列有
DEFAULT,或者允许为NULL,插入时就可以不写这一列,SQLite 会自动填默认值或NULL。
-- 只写 name 和 age,id 自增、email 留空
INSERT INTO users (name, age)
VALUES ('李四', 33);
反过来,如果你省略列名、用”全列插入”的简写,就必须按表定义的列顺序把所有值都填满,少一个都会报错:
-- 不写列名,值顺序必须与表结构完全一致
INSERT INTO users VALUES (3, '王五', 41, 'wangwu@example.com');
Tip工程实践里强烈建议永远显式写出列名。原因很现实:哪天你给表
ALTER TABLE ... ADD COLUMN加了一列,旧的”全列插入”简写会立刻错位报错,而带列名的写法不受影响。多打几个列名,换的是长期的稳健。
一次插入多行:一条语句写多条记录
需要批量导入时,没必要写 N 条 INSERT。SQLite 支持在一条 INSERT 里用逗号分隔多个 VALUES 元组:
INSERT INTO users (name, age, email) VALUES
('赵六', 22, 'zhaoliu@example.com'),
('孙七', 19, 'sunqi@example.com'),
('周八', 37, 'zhouba@example.com');
适用场景:从 CSV、Excel 或其他系统导出的一批数据,想一次性落地;或者程序初始化时批量种入测试/基础数据。
为什么用一条多行而不是多条单行? 三个理由:第一,网络/IO 往返更少(虽然是本地文件,但减少解析次数仍有收益);第二,如果包在一条事务里,要么全成功要么全失败,不会出现”插了一半”的中间状态;第三,语句更短、可读性更好。
INSERT OR REPLACE:遇到冲突就”替换”
现实里经常碰到这种情况:你拿到一条数据,不确定库里有没有,有就更新、没有就新增。SQLite 提供了一个便捷写法 INSERT OR REPLACE:
-- 假设 id = 1 已经存在,这条会先删旧行、再插新行
INSERT OR REPLACE INTO users (id, name, age, email)
VALUES (1, '张三(已改名)', 29, 'zhangsan_new@example.com');
它的底层逻辑是:当插入触发 UNIQUE 或 PRIMARY KEY 约束冲突时,SQLite 会先删除造成冲突的那一行,再插入新行。
Warning
REPLACE是”替换整行”,不是”更新某些字段”!新行里你没写到的列会丢掉旧值,变成该列的默认值或NULL,而不是保留原数据。比如上面只写了(id, name, age, email),如果原行还有别的列,那些列会被清空。想要”只更新部分字段”,应当用第 21 章的UPDATE,而不是REPLACE。另外还有更现代的ON CONFLICT ... DO UPDATE(UPSERT)写法,等学到冲突子句时再展开。
INSERT OR IGNORE:遇到冲突就”跳过”
另一种是”有就别动、没有才插”的场景,用 INSERT OR IGNORE:
-- 如果 id = 1 已存在,整条语句静默跳过,不报错
INSERT OR IGNORE INTO users (id, name, age, email)
VALUES (1, '张三', 28, 'zhangsan@example.com');
适用场景:幂等导入。比如你反复跑同一份初始化脚本,第二次跑时数据已存在,用 IGNORE 就能避免”约束冲突”报错中断脚本,同时又不会覆盖已有数据。
对比一下两者的取舍:REPLACE 会用新数据覆盖旧的;IGNORE 会保留旧的、丢弃新的。选哪个,取决于”冲突时以谁为准”。
顺带理解 SQLite 的”动态类型”:插入的值决定存储类
这是 SQLite 和很多数据库不一样的地方,务必记住(纠偏红线)。SQLite 只有 5 种存储类(Storage Class):NULL / INTEGER / REAL / TEXT / BLOB。它没有 DATE、TIME、DATETIME、TIMESTAMP 这类日期时间”类型”。
你在建表时写的 INTEGER、TEXT 等,只是类型亲和性(Type Affinity)的”建议”,并非强制约束。实际这一行里某个值到底是哪种存储类,由你插入的值本身决定。看个例子:
-- age 声明为 INTEGER,但你塞了个带小数的值
INSERT INTO users (name, age) VALUES ('测试', 25.6);
-- INTEGER 亲和性只做"无损可逆"转换:25.6 含小数,转整数会丢精度,
-- 所以它仍以 REAL 存进去;只有 25.0 这种"恰好等于整数"的浮点才会被收成整数 25
-- email 声明为 TEXT,但你塞了个纯数字
INSERT INTO users (name, email) VALUES ('数字哥', 12345);
-- 在 TEXT 亲和性下,12345 这个 INTEGER 会被转成文本 '12345' 存储
这解释了为什么 SQLite 用起来”很宽容”:你插入的值类型不严格匹配列声明,它也会按亲和性规则尽力转换,而不是一律报错。但反过来提醒你:不要依赖这种宽松——明确该存什么类型就存什么类型,否则查询时类型混乱会让你头疼。
涉及外键关联时的红线警告
本教程另一张全局表 orders 通过 user_id 关联到 users:
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL,
created_at TEXT
);
当你插入一笔订单、却填了一个 users 表里不存在的 user_id 时,按理说应该被拒绝。但 SQLite 有个著名陷阱——
Warning外键约束默认是 OFF(关闭)的! 也就是说,即使你建表时写了外键,SQLite 默认也不会去校验
user_id是否真的存在于users表。要让外键真正生效,每次连接都必须先执行:PRAGMA foreign_keys = ON;而且这个开关只在当前连接有效,CLI 里你每重新打开一次数据库都要再开一次。插订单前务必先
PRAGMA foreign_keys = ON;,否则脏数据会悄悄混进来。
类比小结
把 users 表想象成一张 Excel 表格:INSERT 就是”在表格末尾新增一行并填好各列”;指定列插入像”只填姓名和年龄两格,其余留空”;多行插入像”一次复制粘贴三行”;REPLACE 像”这行身份证号重复了,整行撕掉重写”;IGNORE 像”这行已经有了就别动”。而 SQLite 没有服务器、数据库就是那个 .db 文件,所以你每一次 INSERT 都是在直接往这个文件里写内容——写对了,后面的 SELECT 才能查到;写错了(比如外键没开),脏数据就悄悄躺进了文件里。