一、Sqlite介绍
数据库在现有的计算机系统中已经成为了举足轻重的重要功能,对于数据的高效持久化存储起着重要的管理作用。在嵌入式操作系统中,系统的资源十分受限、需求的数据库操作往往也更加简单。传统的数据库方案在往往为高级的事务管理机制、大量数据高并发访问等问题使用了太多的资源,不能很好的匹配嵌入式操作系统的需求。为了解决这一问题,嵌入式数据库针对资源受限的嵌入式平台进行了专门的优化和改造,使得数据库的实现更加轻量级和高效。也就是说,嵌入式数据库可能会牺牲一些功能以换取更好的性能表现。
SQLite是一种非常受欢迎的无服务器的关系数据库。SQLite并没有服务器进程,而是通过提供动态库实现本地数据库。在使用时,把应用和SQLite的动态库链接起来,通过动态库中指定的API接口去操作数据库。SQLite以单文件的方式存储数据库,每个数据库实际上是一个.db格式的数据库文件,这意味着数据库的读写遵从的是文件系统的锁机制——不支持多用户同时写入,但是可以多个用户同时读(一写多读)。SQLite支持SQL92标准的大部分特性,包括事务、触发器、视图等。用户可以使用通用的SQL语句完成数据库的管理和操作。
SQLite中对于数据库的实际操作依赖于虚拟数据库引擎VDBE(Virtual DataBase Engine)。VDBE本质上是一个寄存器式虚拟机,是SQL语句和实际底层的数据操作之间的一个中间层。SQL语句执行的过程中会先转化为对应的VDBE的字节码,然后由VDBE这个虚拟机执行。VDEB字节码相当于VDBE虚拟机的汇编语言,VDBE按照字节码进行一系列的数据操作。
VDBE的工作模式使得SQL语句需要不断的被解析,这在一定程度上带来了额外的开销。一种提升SQL语句处理速度的方式是数据库预处理。数据库预处理是一种预先将SQL语句编译成带有占位符的字节码,在执行时再将字节码中的占位符和实际使用的数据进行替换的机制。通过这种预处理机制,SQLite提升了大规模SQL语句处理时的效率。
SQL的页缓存机制通过使用内存缓冲器来减少磁盘的I/O以提升性能。将数据库文件分成固定大小的页(通常1KB-64KB),在内存中维护最近访问的数据库页,采用LRU(最近最少使用)算法管理缓存页。数据库中的所有修改先在内存中进行,之后再批量写入磁盘。
SQLite 3.7.0版本开始引入了WAL(Write-Ahead Logging,预写式日志)机制。在WAL机制中,事务对页面的修改首先写入WAL文件中,而非直接写入到数据库文件。这个过程就像是先记录下所有要做的修改“日志”,后续再根据这个“日志”统一去更新数据库文件。
二、Sqlite使用
三、Sqlite工作流程
SQLite 处理 SQL 语句的流程可以分为 五个主要阶段:
1. SQL 解析(Parsing) → 将 SQL 语句解析为语法树
2. 语义分析(Semantic Analysis) → 变量绑定、权限检查等
3. 代码生成(Bytecode Generation) → 生成 VDBE 字节码
4. 执行(Execution) → 解释执行 VDBE 字节码,读写数据
5. 持久化存储(Storage) → 通过 B-tree 写入磁盘
1. SQL 解析(Parsing)
主要函数:
sqlite3_prepare_v2()/sqlite3_prepare()
sqlite3LockAndPrepare()
sqlite3Prepare()
当 SQL 语句被传入 sqlite3_prepare_v2(),SQLite 需要 解析 它,确定 SQL 语法是否正确,并转换为内部表示(AST 语法树)。解析过程分为词法分析和语法分析两种。
词法分析(Tokenizer):
由 sqlite3RunParser() 调用 sqlite3GetToken() 进行词法分析,将 SQL 拆分为 关键字(SELECT, INSERT)、标识符(表名)、操作符(=, >)等。
语法分析(Parser):
SQLite 使用 Lemon Parser(类似 YACC/Bison)基于 parse.y 生成 parse.c,构建 SQL 的 AST 语法树。解析成功后,SQL 语句会转换为 SQLite 的 抽象语法树(AST)。
2. 语义分析(Semantic Analysis)
主要函数:
sqlite3ResolveExprNames() → 解析变量、检查列名是否存在
sqlite3SrcListLookup() → 检查 FROM 语句中的表是否存在
语义分析的主要工作是:
检查表是否存在(sqlite3LocateTable())
检查列是否存在(sqlite3ExprCheckHeight())
类型检查(确保 WHERE 条件、JOIN 逻辑正确)
权限验证(是否有 SELECT、INSERT 权限)
3. 代码生成(Bytecode Generation)
主要函数:
sqlite3CodeSelect() → 将 SELECT 语句转换为字节码
sqlite3VdbeAddOp() → 向 VDBE 生成字节码指令
SQLite 不会直接执行 AST,而是把 SQL 转换成 VDBE(Virtual Database Engine)字节码,然后由解释器执行。这些 VDBE 指令 本质上是 SQLite 的 解释执行代码。
VDBE 字节码示例
————————————————————————————————
SQL语句:
SELECT name FROM users WHERE id = 1;
转换之后的VDBE 指令:
1. OpenRead 0, users // 打开 users 表
2. Rewind 0 // 遍历表
3. Column 0, id // 读取 id 列
4. Integer 1 // 载入常数 1
5. Eq 3 // 比较 id == 1
6. Column 0, name // 读取 name 列
7. ResultRow 1 // 返回结果
8. Halt // 结束
4. 执行(Execution)
主要函数:
sqlite3_step()
VDBE 由 sqlite3VdbeExec() 解释执行,执行过程中可能涉及 B-tree 访问、索引查询、排序、聚合等。
常见 SQL 语句执行流程
————————————————————————————————
① SELECT 查询
Btree 打开数据库文件,读取 users 表
遍历 users,找到 id = 1 的行
读取 name 列,返回结果
② INSERT 插入
计算新数据的 rowid
通过 Btree 定位数据位置
写入 B-tree 结构
更新索引(如果有)
③ UPDATE 更新
通过 Btree 查找 WHERE 条件匹配的行
修改对应的列数据
更新索引(如果涉及 INDEX)
④ DELETE 删除
通过 Btree 定位 WHERE 条件匹配的行
删除行数据
更新索引
5. 持久化存储(Storage)
SQLite 采用 B-tree 作为存储结构,每张表或索引对应一个 B-tree。
B-tree 结构
Root Page
├── Page 1
│ ├── Row ID 1 → 数据1
│ ├── Row ID 2 → 数据2
│
├── Page 2
│ ├── Row ID 3 → 数据3
│ ├── Row ID 4 → 数据4
SQLite 持久化的关键步骤:
写入 WAL(Write-Ahead Logging)日志
事务开始时,先把数据写入 WAL 文件,确保崩溃后仍能恢复
写入主数据库文件
SQLite 通过 pager.c 把数据从 WAL 应用到 .db 文件
更新索引
如果表有索引,更新 B-tree 索引结构
事务提交
COMMIT 时,SQLite 调用 sqlite3PagerCommitPhaseTwo()
ROLLBACK 时,直接丢弃 WAL 变更