提到数据库,大多数人第一反应是MySQL、PostgreSQL这类需要安装服务端的大型系统,但全球部署量最大的数据库其实是SQLite。它的代码运行在数十亿台手机、浏览器、汽车和电视机里。SQLite最大的特点是没有独立的服务进程,整个数据库就是一个文件,应用程序通过函数库直接读写它。要真正用好SQLite,光会写SQL语句是不够的,理解它的存储结构和架构分层,才能解释那些奇怪的性能问题和并发限制。

一、SQLite的基本概念:零配置的嵌入式数据库
SQLite是一个自包含的、无服务器的、事务型的SQL数据库引擎。所谓自包含,是指它不依赖任何外部依赖库,编译后整个库只有几百KB;所谓无服务器,是指它不像MySQL那样需要先启动一个守护进程再通过网络连接,而是直接以函数库的形式链接进应用程序进程内部,应用程序调用sqlite3_open就能打开一个数据库文件。
一个完整的SQLite数据库就是磁盘上的一个普通文件,通常是.db或.sqlite后缀。这个文件内部由固定大小的页组成,默认每页4096字节。所有的表、索引、触发器和数据都存在这一个文件里,复制这个文件就等于备份了整个数据库,这种特性让SQLite的数据迁移变得极其简单。
SQLite的大部分SQL语法与标准SQL兼容,支持事务、视图、子查询、CTE等特性。但要注意它采用了动态类型系统,字段类型可以是INTEGER、TEXT、REAL、BLOB和NULL五种存储类型,写入时并不强制校验与列声明类型一致,这一点和MySQL的严格模式差别很大。
二、核心架构剖析:五大模块如何协作
从官方架构图来看,SQLite内部可以分为五层:接口层、SQL编译器、虚拟机、B-Tree存储引擎和操作系统抽象层。理解这个分层对排查问题非常有帮助。
最上层是接口层,也就是C语言API,比如sqlite3_prepare、sqlite3_step、sqlite3_finalize等。大部分语言的SQLite绑定库最终都是封装这一层。接下来SQL编译器把SQL文本解析成语法树,再由代码生成器生成虚拟机指令。SQLite的虚拟机也叫VDBE(Virtual Database Engine),每条SQL语句都会被翻译成一串 opcode,比如打开一个游标、读取一行、比较值、跳转等,这种设计类似于一个简单的字节码解释器。
存储引擎层使用B-Tree结构组织数据,表数据存在表B-Tree中,索引存在索引B-Tree中。B-Tree以页为单位读写,每个页有页头、单元格指针数组和数据单元格。最底层是操作系统抽象层(OS Interface,内部称为VFS),它把文件读写、锁操作抽象成统一接口,这使得SQLite可以同时跑在Windows、Linux、iOS以及各种嵌入式系统上,甚至能在内存或只读介质上运行。
#include <sqlite3.h>
#include <stdio.h>
int main() {
sqlite3 *db;
char *err = NULL;
// 打开数据库文件,不存在则自动创建
sqlite3_open("test.db", &db);
// 执行建表语句
sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS user(id INTEGER PRIMARY KEY, name TEXT)", NULL, NULL, &err);
// 插入一条数据
sqlite3_exec(db, "INSERT INTO user(name) VALUES('张三')", NULL, NULL, &err);
sqlite3_close(db);
return 0;
}三、存储结构与事务机制:页、WAL与锁
SQLite数据库文件按页组织,页是磁盘I/O的最小单位。文件开头100字节是数据库头,记录页大小、文件变更计数、schema格式版本等信息。每个表在文件里对应一棵B-Tree,根页编号记录在sqlite_master系统表中。当执行SELECT * FROM sqlite_master时,查到的就是整个数据库的schema信息。
事务层面,SQLite支持完整的ACID。默认使用回滚日志模式(journal mode),写入时先把原始页内容备份到journal文件,修改直接写入主文件,提交时删除journal,崩溃时则用journal恢复。而更推荐的WAL模式(Write-Ahead Logging)则相反:修改先追加写入-wal文件,checkpoint时才合并回主文件。WAL模式的一大优势是读写不再互相阻塞,读操作继续读主文件,写操作写wal文件,大幅提升了并发性能。开启方式是执行PRAGMA journal_mode=WAL;。
锁机制方面,SQLite采用文件锁实现五种锁状态:UNLOCKED、SHARED、RESERVED、PENDING和EXCLUSIVE。读事务持有SHARED锁,多个连接可以同时读;但写操作最终需要EXCLUSIVE锁,所以任意时刻只允许一个写入者。这就是SQLite写入并发受限的根本原因,也是高并发写场景下要考虑换用客户端服务器数据库的关键判断依据。
四、适用场景与选型建议
SQLite官方给出的定位是嵌入式设备、单机应用和中小规模网站。它非常适合移动App本地存储、桌面软件配置存储、浏览器本地数据、测试环境的临时数据库,以及数据分析时替代CSV文件。单个数据库文件支持到TB级别,实测百万级数据量下配合索引查询性能依然出色。
不适合的场景同样要清楚:高并发写入、多机共享同一份数据、需要细粒度用户权限控制的系统,这些场景MySQL或PostgreSQL更合适。简单说,如果数据和应用在同一个进程生态里、并发写压力不大,SQLite往往是成本最低、运维最省心的选择;反之就要慎重评估写入锁带来的瓶颈。