SQLite 学习博客(一):从零建库,理解 SQLite 的角色与第一张表

这篇解决什么问题

从零建一个数据库、建第一张表、插入两条记录、查出来。学完你能说清楚:数据库文件、表、行、列、主键分别是什么,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 成功时默认不打印,终端"没反应"是正常的。


六、这一篇要记住的规则

  1. SQLite 存管理信息,文件系统存文件本体。 大文件不进数据库。
  2. 数据库就是一个文件,SQLite 没有独立进程,同一时刻只允许一个写事务。
  3. INTEGER PRIMARY KEY 是 ROWID 的别名,插入不指定 id 会自动分配。
  4. 不加 AUTOINCREMENT 除非严格需要 id 不复用。
  5. IF NOT EXISTS 不会更新已有表的结构,改结构要删表重建。
  6. DEFAULT 0 和 NOT NULL 同时存在时,不能显式插入 NULL。
  7. SQLite 是动态类型系统,列的类型声明是亲和性,不是强制约束。
  8. 点命令只在交互环境有效,不能写进 SQL 脚本。

七、验证清单

跑完后确认:

  1. sqlite3 --version 有版本号,说明命令行工具已安装。
  2. pkg-config --modversion sqlite3 有输出,说明开发库已安装。
  3. media_demo.db 文件已生成。
  4. .tables 显示 recordings 表。
  5. SELECT * FROM recordings; 返回你插入的记录,id 列自动有值。

如果 pkg-config 没输出,说明 libsqlite3-dev 没装好。下一篇进入增删改查与条件过滤。

相关推荐
福娃筱欢1 小时前
金仓数据库配置SSL双向认证
数据库·网络协议·ssl
captain3761 小时前
MyBatis操作数据库(入门1)
数据库·oracle·mybatis
snpgroupcn1 小时前
SAP 系统退役的归档架构怎么设计:可检索、可理解、可追溯、可证明
java·数据库·架构
清水白石0081 小时前
React Native与Flutter长期维护成本对比
开发语言·数据库
弦儿1 小时前
MySQL 数据库从入门到实践:SQL 四大分类 + DDL/DML/DQL 全梳理(附全套代码)
数据库
一个天蝎座的程序猿1 小时前
金仓KingbaseES的数据集成软件,我用了一年多,把KDTS和KFS的用法还有踩过的坑一次说清
数据库
泡海椒2 小时前
JQuick-SQL CASE WHEN 与函数:在查询中构建业务字段
数据库·sql
一个天蝎座的程序猿2 小时前
2TB传感器数据搬迁记:IoTDB数据导入导出实战详解(CSV/SQL/TsFile全踩了一遍)
数据库
charlotte102410242 小时前
Redis 简介:从诞生到数据类型,再到缓存三大问题
数据库·redis·缓存