这篇解决什么问题
从零建一个数据库、建第一张表、插入两条记录、查出来。学完你能说清楚:数据库文件、表、行、列、主键分别是什么,SQLite 在应用里适合承担什么角色,不适合承担什么角色。
一、SQLite 适合存什么,不适合存什么
假设应用要管理一批媒体文件:
bash
/record/clip_001.mp4
/record/clip_002.mp4
这些文件本身几百 MB,放在文件系统。应用需要知道每个文件属于哪个通道、什么时候开始、持续多久。如果每次查询都扫描目录、从文件名猜信息,后期无法维护。
合理的分工是:
| 存什么 | 存在哪 | 为什么 |
|---|---|---|
| 文件路径、通道、时间、时长、状态 | SQLite 数据库文件 | 查询快、能按条件筛选、事务保证一致性 |
| MP4、JPG 等文件本体 | 文件系统 | 大文件进数据库会导致文件膨胀、读写变慢、清理复杂 |
规则:SQLite 存"管理信息",文件系统存"数据本体"。数据库表里只存路径和属性,不存二进制大文件。
SQLite 官方文档专门讨论过"内部 BLOB vs 外部文件存储",结论是:大文件存数据库会导致数据库文件膨胀、读写效率下降、备份和清理都变复杂。
二、SQLite 和 MySQL 的本质区别
MySQL 是一个独立运行的服务器进程,应用通过网络连接它。SQLite 没有独立进程,应用链接 libsqlite3,调用 sqlite3_open 时,SQLite 库直接打开磁盘上的数据库文件,读写在自己的进程里完成。
两个直接后果:
- 数据库就是一个文件。可以复制、传输、拷到另一台装了 SQLite 的机器直接打开。
- 同一时刻只允许一个写事务。写入频率不高的场景完全够用,高并发写入才需要考虑其他方案。
三、建表语句与逐字段理解
sql
CREATE TABLE IF NOT EXISTS recordings (
id INTEGER PRIMARY KEY,
channel_id INTEGER NOT NULL,
file_path TEXT NOT NULL,
start_time INTEGER NOT NULL,
duration_sec INTEGER NOT NULL DEFAULT 0
);
IF NOT EXISTS 的含义与坑
表已存在就不重复创建。注意 :它不会更新已有表的结构。如果改了字段再执行,旧表不会被改,需要先 DROP TABLE 或删库文件。这是后面容易踩的坑。
id INTEGER PRIMARY KEY
SQLite 每张表有一个隐藏的 ROWID,唯一标识每一行。声明 INTEGER PRIMARY KEY 后,id 成为 ROWID 的别名。插入时不指定 id,SQLite 自动分配递增整数。
不加 AUTOINCREMENT 的理由:官方文档明确说它带来额外的 CPU、内存、磁盘和 I/O 开销,除非严格需要"已删除的 id 永不复用",否则避免使用。如果清理逻辑按时间排序,不依赖 id 单调,不加没问题。
channel_id INTEGER NOT NULL
通道号,不能缺失。NOT NULL 是约束:插入时必须给值,不能为 NULL。
file_path TEXT NOT NULL
存路径字符串,不是文件本体。
start_time INTEGER NOT NULL
Unix 时间戳,从 1970-01-01 UTC 起的秒数。存整数而不是字符串,比较和排序都快,没有时区歧义。
duration_sec INTEGER NOT NULL DEFAULT 0
时长,默认 0。DEFAULT 0 表示插入时省略这一列,自动填 0。但因为同时有 NOT NULL,不能显式 插入 NULL 给它。
SQLite 的类型系统与 C/C++ 不同
声明 INTEGER 并不意味着这一列"只能存整数"。SQLite 使用的是动态类型系统:值的数据类型和值本身绑定,而不是和列绑定。列的类型声明是"亲和性"(affinity),影响比较和排序行为,不是强制约束。
实用规则:时间戳统一存 INTEGER(Unix 秒),路径存 TEXT,不要混用。保持一致,避免踩坑。
四、插入与查询
sql
INSERT INTO recordings (channel_id, file_path, start_time, duration_sec)
VALUES (0, '/record/clip_001.mp4', 1800000000, 60);
SELECT * FROM recordings;
为什么 INSERT 要显式写列名 :不依赖表的列顺序。如果以后用 ALTER TABLE 加列,不带列名的 INSERT 会错位。显式写列名是防御性写法。
SELECT * :返回所有列。实际项目里不一定需要全部字段,比如界面列表只显示 id、channel_id、duration_sec,就写 SELECT id, channel_id, duration_sec FROM recordings,少查不需要的列能减少内存和传输开销。
插入时不写 id :因为它是 INTEGER PRIMARY KEY,SQLite 自动分配。
五、命令行操作:点命令不是 SQL
进入 SQLite 交互环境:
sqlite3 media_demo.db
常用点命令:
| 命令 | 作用 |
|---|---|
.tables |
列出所有表 |
.schema recordings |
显示建表语句 |
.headers on |
查询结果显示列名 |
.mode column |
列对齐格式 |
.quit |
退出 |
关键规则 :点命令必须以 . 开头,只在交互环境有效,不能写进 .sql 文件 让 sqlite3 批量执行。它们是命令行工具的功能,不是 SQL 标准。
批量执行 SQL 脚本用:
bash
sqlite3 media_demo.db < 01_basics.sql
< 把文件内容喂给 sqlite3 作为标准输入。CREATE TABLE 和 INSERT 成功时默认不打印,终端"没反应"是正常的。
六、这一篇要记住的规则
- SQLite 存管理信息,文件系统存文件本体。 大文件不进数据库。
- 数据库就是一个文件,SQLite 没有独立进程,同一时刻只允许一个写事务。
INTEGER PRIMARY KEY是ROWID的别名,插入不指定 id 会自动分配。- 不加
AUTOINCREMENT除非严格需要 id 不复用。 IF NOT EXISTS不会更新已有表的结构,改结构要删表重建。DEFAULT 0和NOT NULL同时存在时,不能显式插入NULL。- SQLite 是动态类型系统,列的类型声明是亲和性,不是强制约束。
- 点命令只在交互环境有效,不能写进 SQL 脚本。
七、验证清单
跑完后确认:
sqlite3 --version有版本号,说明命令行工具已安装。pkg-config --modversion sqlite3有输出,说明开发库已安装。media_demo.db文件已生成。.tables显示recordings表。SELECT * FROM recordings;返回你插入的记录,id列自动有值。
如果 pkg-config 没输出,说明 libsqlite3-dev 没装好。下一篇进入增删改查与条件过滤。