03 - SQLite 技术全景:嵌入式关系数据库引擎完整解析
全球部署量最大的数据库引擎 | Android 数据存储基石 | 24万行单文件架构
目录
概述
什么是 SQLite?
SQLite 是一个零配置、无服务器、自包含的 SQL 数据库引擎,是世界上使用最广泛的数据库软件。每个 Android 设备都包含多个 SQLite 数据库实例。
位置 : external/sqlite/
语言 : C (单文件架构)
代码行数 : ~241,651 行(sqlite3.c 单文件)
许可证 : Public Domain(公有领域)
版本: 基于 SQLite 3.x 系列
核心特性
| 特性 | 说明 |
|---|---|
| 零配置 | 无需安装、配置或管理 |
| 自包含 | 单文件数据库,易于备份和传输 |
| 无服务器 | 直接读写磁盘文件 |
| 跨平台 | 支持 Android、iOS、Linux、Windows、macOS |
| ACID事务 | 完整的原子性、一致性、隔离性、持久性 |
| 完整SQL | 支持 SQL-92 标准及扩展 |
| 小巧高效 | 编译后仅 ~2-3 MB |
使用统计
- 全球部署: 超过 1 万亿个活跃数据库实例
- Android 集成: 每个应用可创建多个数据库
- 系统使用: 联系人、通话记录、浏览器历史、设置等
- 应用使用: 微信、WhatsApp、Facebook 等主流应用
架构哲学
"SQLite is not designed to compete with Oracle.
It's designed to compete with fopen()."
------ SQLite 设计目标:取代文件 I/O
核心理念:
- 简单性: 单一库文件,零依赖
- 可靠性: 经过数十亿次部署验证
- 效率: 针对嵌入式环境优化
- 稳定性: API 向后兼容 20+ 年
架构设计
整体架构层次
┌─────────────────────────────────────────┐
│ 应用层 (Application) │
│ android.database.sqlite.SQLiteDatabase │
└─────────────────────────────────────────┘
↓ JNI
┌─────────────────────────────────────────┐
│ 接口层 (Interface) │
│ sqlite3_open, sqlite3_prepare, etc. │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ SQL编译器 (SQL Compiler) │
│ Tokenizer → Parser → Code Generator │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 虚拟机 (Virtual Machine - VDBE) │
│ 150+ Opcodes, Memory Cells │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ B-树引擎 (B-tree Engine) │
│ Table B-tree, Index B-tree │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 分页器 (Pager & Cache) │
│ Page Cache, Journal, WAL, Locking │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 操作系统接口 (OS Interface - VFS) │
│ File I/O, Locking, Memory │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 磁盘文件 (Disk File) │
│ .db, .db-journal, .db-wal │
└─────────────────────────────────────────┘
模块职责
| 模块 | 文件 | 职责 |
|---|---|---|
| Tokenizer | tokenize.c | 词法分析,将SQL文本转换为Token流 |
| Parser | parse.c | 语法分析,构建抽象语法树(AST) |
| Code Generator | select.c, insert.c, update.c, delete.c | 将AST转换为VDBE字节码 |
| VDBE | vdbe.c, vdbeaux.c, vdbeapi.c | 执行字节码指令 |
| B-tree | btree.c | 管理表和索引的B+树存储 |
| Pager | pager.c | 页缓存、事务、崩溃恢复 |
| OS Interface | os_unix.c, os_win.c | 平台抽象层 |
数据流向
SQL字符串输入
↓
词法分析 (Tokenizer)
↓
语法分析 (Parser) → AST
↓
查询优化 (Query Planner)
↓
代码生成 → VDBE字节码
↓
VDBE执行 → 操作B-tree游标
↓
B-tree → 读写页面
↓
Pager → 缓存管理
↓
VFS → 文件I/O
↓
磁盘存储
核心组件
1. SQL 编译器
1.1 Tokenizer(词法分析器)
位置 : sqlite3.c 内嵌(tokenize.c 逻辑)
行数: ~3,000 行
功能:
- 将 SQL 文本分解为 Token
- 识别关键字、标识符、字面量、操作符
- 处理注释和空白字符
- 支持 UTF-8 和 UTF-16 编码
Token 类型:
c
#define TK_SEMI 1 // ;
#define TK_EXPLAIN 2 // EXPLAIN
#define TK_QUERY 3 // QUERY
#define TK_PLAN 4 // PLAN
#define TK_BEGIN 5 // BEGIN
#define TK_TRANSACTION 6 // TRANSACTION
#define TK_DEFERRED 7 // DEFERRED
#define TK_IMMEDIATE 8 // IMMEDIATE
#define TK_EXCLUSIVE 9 // EXCLUSIVE
#define TK_COMMIT 10 // COMMIT
#define TK_END 11 // END
#define TK_ROLLBACK 12 // ROLLBACK
#define TK_SAVEPOINT 13 // SAVEPOINT
#define TK_RELEASE 14 // RELEASE
#define TK_TO 15 // TO
#define TK_TABLE 16 // TABLE
#define TK_CREATE 17 // CREATE
#define TK_IF 18 // IF
// ... 150+ token types
关键字哈希表:
- 使用完美哈希函数
- O(1) 关键字识别
- 147 个 SQL 关键字
示例:
sql
SELECT name FROM users WHERE age > 18;
Token 流:
TK_SELECT → TK_ID(name) → TK_FROM → TK_ID(users) →
TK_WHERE → TK_ID(age) → TK_GT → TK_INTEGER(18) → TK_SEMI
1.2 Parser(语法分析器)
位置 : sqlite3.c 内嵌(parse.c)
行数 : ~14,000 行
工具: LEMON Parser Generator
核心数据结构:
c
typedef struct Parse Parse;
struct Parse {
sqlite3 *db; // 数据库连接
char *zErrMsg; // 错误消息
Vdbe *pVdbe; // 正在构建的虚拟机
int rc; // 返回码
u8 colNamesSet; // 列名是否已设置
u8 checkSchema; // 是否需要检查Schema
u8 nested; // 嵌套级别
u8 nTempReg; // 临时寄存器数量
u8 isMultiWrite; // 是否多写操作
u8 mayAbort; // 是否可能中止
int nRangeReg; // 范围寄存器数量
int iRangeReg; // 范围寄存器起始
int nErr; // 错误计数
int nTab; // 表数量
int nMem; // 内存单元数量
int nSet; // Set指令数量
int nOnce; // Once指令数量
int nOpAlloc; // 已分配的操作码数量
int iSelfTab; // Self-tab for UPDATE/DELETE
AggInfo *pAggInfo; // 聚合函数信息
Trigger *pTriggerPrg; // 触发器程序
Table *pTriggerTab; // 触发器表
Parse *pToplevel; // 顶层解析器
Table *pTriggerTab; // 触发器表
};
AST 节点类型:
c
typedef struct Expr Expr; // 表达式节点
typedef struct ExprList ExprList; // 表达式列表
typedef struct Select Select; // SELECT语句
typedef struct SrcList SrcList; // FROM子句表列表
typedef struct IdList IdList; // 标识符列表
typedef struct Where WhereInfo; // WHERE子句信息
语法规则示例:
c
// LEMON语法定义
cmd ::= SELECT selcollist(X) from(F) where_opt(W) groupby_opt(G)
having_opt(H) orderby_opt(O) limit_opt(L). {
Select *pSelect = sqlite3SelectNew(pParse, X, F, W, G, H, O, L);
sqlite3Select(pParse, pSelect, &dest);
}
expr(A) ::= expr(X) AND expr(Y). {
A.pExpr = sqlite3ExprAnd(pParse->db, X.pExpr, Y.pExpr);
}
expr(A) ::= expr(X) OR expr(Y). {
A.pExpr = sqlite3ExprOr(pParse->db, X.pExpr, Y.pExpr);
}
1.3 Code Generator(代码生成器)
位置 : select.c, insert.c, update.c, delete.c, expr.c
行数: ~90,000 行(合计)
功能:
- 将 AST 转换为 VDBE 字节码
- 生成查询执行计划
- 寄存器分配
- 游标管理
SELECT 语句代码生成流程:
c
// select.c 核心函数
int sqlite3Select(
Parse *pParse, // 解析器上下文
Select *p, // SELECT语句AST
SelectDest *pDest // 结果输出目标
) {
// 1. 解析FROM子句,打开表和索引
// 2. 生成WHERE子句过滤代码
// 3. 生成GROUP BY聚合代码
// 4. 生成HAVING过滤代码
// 5. 生成ORDER BY排序代码
// 6. 生成LIMIT限制代码
// 7. 生成结果输出代码
}
示例SQL及生成的VDBE代码:
sql
SELECT name FROM users WHERE age > 18;
生成的字节码(简化版):
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------
0 Init 0 15 0 0 Start at 15
1 OpenRead 0 2 0 3 0 root=2 iDb=0; users
2 Rewind 0 13 0 0
3 Column 0 2 1 0 r[1]=users.age
4 Ge 18 12 1 (BINARY) 81 if r[1]>=18 goto 12
5 Column 0 1 2 0 r[2]=users.name
6 ResultRow 2 1 0 0 output=r[2]
12 Next 0 3 0 1
13 Close 0 0 0 0
14 Halt 0 0 0 0
15 Transaction 0 0 1 0 1 usesStmtJournal=0
16 Goto 0 1 0 0
指令说明:
OpenRead: 打开表游标Rewind: 定位到第一行Column: 读取列值Ge: 大于等于比较ResultRow: 输出结果行Next: 移动到下一行
2. 虚拟数据库引擎 (VDBE)
2.1 VDBE 架构
位置 : vdbe.c, vdbeaux.c, vdbeapi.c, vdbemem.c
行数: ~40,000 行
核心概念:
- SQLite 的"心脏" ------ 执行所有数据库操作
- 基于寄存器的虚拟机(不是栈式)
- 类似于 JVM 或 .NET CLR,但专为数据库设计
VDBE 结构:
c
typedef struct Vdbe Vdbe;
struct Vdbe {
sqlite3 *db; // 数据库连接
Vdbe *pPrev, *pNext; // 链表
Parse *pParse; // 解析器上下文
int nMem; // 内存单元数量
int nCursor; // 游标数量
u32 cacheCtr; // 缓存计数器
int pc; // 程序计数器
int rc; // 返回码
u16 nResColumn; // 结果列数量
u16 nOp; // 指令数量
int nOpAlloc; // 已分配指令数量
VdbeOp *aOp; // 指令数组
Mem *aMem; // 内存单元数组
Mem **apArg; // 参数数组
Mem *pResultSet; // 结果集
char *zErrMsg; // 错误消息
VdbeCursor **apCsr; // 游标数组
// ... 更多字段
};
2.2 操作码系统
操作码数量 : 150+ opcodes
位置: opcodes.h(自动生成)
主要类别:
数据操作:
c
#define OP_Init 1 // 初始化
#define OP_Goto 2 // 无条件跳转
#define OP_Gosub 3 // 子程序调用
#define OP_Return 4 // 子程序返回
#define OP_Yield 5 // 协程让出
#define OP_HaltIfNull 6 // 空值则停止
#define OP_Halt 7 // 停止执行
#define OP_Integer 8 // 加载整数常量
#define OP_Int64 9 // 加载64位整数
#define OP_String 10 // 加载字符串
#define OP_Null 11 // 加载NULL
#define OP_Blob 12 // 加载BLOB
表/索引操作:
c
#define OP_OpenRead 31 // 打开表/索引(只读)
#define OP_OpenWrite 32 // 打开表/索引(读写)
#define OP_OpenAutoindex 33 // 打开自动索引
#define OP_OpenEphemeral 34 // 打开临时表
#define OP_Close 35 // 关闭游标
#define OP_SeekGE 36 // 查找 >= 的记录
#define OP_SeekGT 37 // 查找 > 的记录
#define OP_SeekLE 38 // 查找 <= 的记录
#define OP_SeekLT 39 // 查找 < 的记录
#define OP_Seek 40 // 定位到指定位置
#define OP_NotFound 41 // 未找到则跳转
#define OP_Found 42 // 找到则跳转
#define OP_IsNull 43 // 空值检查
#define OP_NotNull 44 // 非空检查
数据读写:
c
#define OP_Column 51 // 读取列值
#define OP_Affinity 52 // 应用类型亲和性
#define OP_MakeRecord 53 // 创建记录
#define OP_Count 54 // 计数
#define OP_Savepoint 55 // 保存点
#define OP_Insert 56 // 插入记录
#define OP_Delete 57 // 删除记录
#define OP_ResetCount 58 // 重置计数
#define OP_SorterCompare 59 // 排序比较
#define OP_SorterData 60 // 排序数据
#define OP_RowData 61 // 行数据
#define OP_Rowid 62 // 行ID
#define OP_NullRow 63 // 空行
聚合函数:
c
#define OP_AggStep 71 // 聚合步骤
#define OP_AggFinal 72 // 聚合完成
#define OP_AggValue 73 // 聚合值
流程控制:
c
#define OP_If 81 // 条件跳转
#define OP_IfNot 82 // 条件跳转(取反)
#define OP_IfNullRow 83 // 空行跳转
#define OP_Jump 84 // 多路跳转
#define OP_Once 85 // 仅执行一次
算术/逻辑:
c
#define OP_Add 91 // 加法
#define OP_Subtract 92 // 减法
#define OP_Multiply 93 // 乘法
#define OP_Divide 94 // 除法
#define OP_Remainder 95 // 取余
#define OP_Concat 96 // 字符串连接
#define OP_BitAnd 97 // 位与
#define OP_BitOr 98 // 位或
#define OP_ShiftLeft 99 // 左移
#define OP_ShiftRight 100 // 右移
#define OP_AddImm 101 // 立即数加法
#define OP_MustBeInt 102 // 必须是整数
#define OP_RealAffinity 103 // 实数亲和性
2.3 内存单元系统
Mem 结构:
c
typedef struct Mem Mem;
struct Mem {
union MemValue {
double r; // 实数值
i64 i; // 整数值
int nZero; // 零字节数量
void *p; // 指针值
FuncDef *pDef; // 函数定义
} u;
char *z; // 字符串/BLOB数据
int n; // 字符串/BLOB长度
u16 flags; // 类型标志
u8 enc; // 编码 (UTF-8/UTF-16)
u8 eSubtype; // 子类型
Mem *pScopyFrom; // 浅拷贝源
void (*xDel)(void *); // 析构函数
#ifdef SQLITE_DEBUG
Mem *pFiller; // Debug填充
#endif
};
类型标志:
c
#define MEM_Null 0x0001 // NULL值
#define MEM_Str 0x0002 // 字符串
#define MEM_Int 0x0004 // 整数
#define MEM_Real 0x0008 // 浮点数
#define MEM_Blob 0x0010 // BLOB
#define MEM_AffMask 0x001f // 类型掩码
#define MEM_RowSet 0x0020 // 行集合
#define MEM_Frame 0x0040 // 栈帧
#define MEM_Undefined 0x0080 // 未定义
#define MEM_Cleared 0x0100 // 已清除
#define MEM_TypeMask 0x81ff // 类型掩码
#define MEM_Static 0x1000 // 静态字符串
#define MEM_Ephem 0x2000 // 临时字符串
#define MEM_Agg 0x4000 // 聚合上下文
#define MEM_Zero 0x8000 // 零终止
2.4 游标系统
VdbeCursor 结构:
c
typedef struct VdbeCursor VdbeCursor;
struct VdbeCursor {
u8 eCurType; // 游标类型
i8 iDb; // 数据库索引
u8 nullRow; // 是否空行
u8 deferredMoveto; // 延迟移动
u8 isTable; // 是否表游标
u8 seekOp; // 最近的seek操作
Bool isOrdered; // 是否有序
Bool isSorter; // 是否排序器
Bool multiPseudo; // 是否多伪表
Bool seekHit; // Seek命中
Bool isEphemeral; // 是否临时表
Bool useRandomRowid; // 使用随机rowid
Bool isOrdered; // 是否有序
Pgno pgnoRoot; // B-tree根页号
i16 nField; // 列数量
u16 nHdrParsed; // 已解析的头部列数
BtCursor *pBtx; // B-tree游标
// ... 更多字段
};
游标类型:
c
#define CURTYPE_BTREE 0 // B-tree游标
#define CURTYPE_SORTER 1 // 排序器游标
#define CURTYPE_VTAB 2 // 虚拟表游标
#define CURTYPE_PSEUDO 3 // 伪表游标
3. B-树存储引擎
3.1 B-树架构
位置 : btree.c, btreeInt.h
行数: ~28,000 行
设计目标:
- 高效的磁盘访问(最小化I/O)
- 支持范围查询
- 支持快速插入/删除
- 保持数据有序
B+树特性:
• 所有数据存储在叶子节点
• 内部节点仅存储键和指针
• 叶子节点通过链表连接
• 树高度通常 2-4 层(百万行数据)
• 页面大小默认 4KB(可配置)
节点结构:
Internal Node (内部节点):
┌──────────────────────────────────┐
│ Header (页头) │
├──────────────────────────────────┤
│ Cell Pointer Array (单元指针数组) │
├──────────────────────────────────┤
│ Unallocated Space (未分配空间) │
├──────────────────────────────────┤
│ Cell Content Area (单元内容区) │
│ Cell 1: Key + Left Child Ptr │
│ Cell 2: Key + Left Child Ptr │
│ ... │
└──────────────────────────────────┘
Leaf Node (叶子节点):
┌──────────────────────────────────┐
│ Header (页头) │
├──────────────────────────────────┤
│ Cell Pointer Array (单元指针数组) │
├──────────────────────────────────┤
│ Unallocated Space (未分配空间) │
├──────────────────────────────────┤
│ Cell Content Area (单元内容区) │
│ Cell 1: Key + Data │
│ Cell 2: Key + Data │
│ ... │
└──────────────────────────────────┘
3.2 核心数据结构
Btree 结构:
c
typedef struct Btree Btree;
struct Btree {
sqlite3 *db; // 数据库连接
BtShared *pBt; // 共享B-tree状态
u8 inTrans; // 事务状态
u8 sharable; // 是否可共享
u8 locked; // 是否锁定
u8 hasIncrblobCur; // 是否有增量BLOB游标
int wantToLock; // 期望锁定的表数量
int nBackup; // 备份计数
Btree *pNext; // 链表
Btree *pPrev; // 链表
BtLock *pLock; // 锁列表
};
BtCursor 结构:
c
typedef struct BtCursor BtCursor;
struct BtCursor {
Btree *pBtree; // B-tree实例
BtShared *pBt; // 共享状态
BtCursor *pNext; // 游标链表
Pgno *aOverflow; // 溢出页数组
CellInfo info; // 当前单元信息
i64 nKey; // 键值(INTKEY表)
Pgno pgnoRoot; // 根页号
u8 curFlags; // 游标标志
u8 eState; // 游标状态
u8 hints; // 查询优化提示
u8 curPagerFlags; // Pager标志
i16 iPage; // 当前页索引
MemPage *pPage; // 当前页
// ... 更多字段
};
3.3 表类型
INTKEY 表(普通表):
c
// 使用64位整数作为键(rowid)
// 键隐式存储,不占用数据空间
// 数据直接存储在叶子节点
CREATE TABLE users (
id INTEGER PRIMARY KEY, -- INTKEY
name TEXT,
age INTEGER
);
BLOBKEY 表(索引):
c
// 使用任意字节序列作为键
// 键显式存储
// 用于实现索引
CREATE INDEX idx_name ON users(name);
// 内部使用BLOBKEY B-tree
3.4 核心操作
打开B-tree:
c
int sqlite3BtreeOpen(
sqlite3_vfs *pVfs, // VFS接口
const char *zFilename, // 数据库文件名
sqlite3 *db, // 数据库连接
Btree **ppBtree, // 输出B-tree实例
int flags, // 标志
int vfsFlags // VFS标志
);
创建表:
c
int sqlite3BtreeCreateTable(
Btree *p, // B-tree实例
int *piTable, // 输出根页号
int flags // BTREE_INTKEY 或 BTREE_BLOBKEY
);
游标操作:
c
// 创建游标
int sqlite3BtreeCursor(
Btree *p, // B-tree实例
int iTable, // 表根页号
int wrFlag, // 读/写标志
struct KeyInfo *pKeyInfo, // 键信息(索引)
BtCursor *pCur // 输出游标
);
// 定位到第一条记录
int sqlite3BtreeFirst(BtCursor *pCur, int *pRes);
// 移动到下一条记录
int sqlite3BtreeNext(BtCursor *pCur, int flags);
// 读取数据
const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt);
// 插入记录
int sqlite3BtreeInsert(
BtCursor *pCur, // 游标
const BtreePayload *pX, // 数据
int flags, // 标志
int seekResult // Seek结果
);
// 删除记录
int sqlite3BtreeDelete(BtCursor *pCur, u8 flags);
事务操作:
c
// 开始事务
int sqlite3BtreeBeginTrans(Btree *p, int wrflag);
// 提交事务
int sqlite3BtreeCommit(Btree *p);
// 回滚事务
int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly);
4. 分页器与缓存系统
4.1 Pager 架构
位置 : pager.c, pager.h
行数: ~13,000 行
核心职责:
- 页面缓存管理: 内存中缓存数据库页面
- 事务控制: 支持ACID事务
- 崩溃恢复: 通过日志实现原子提交
- 锁管理: 多进程并发访问控制
- 写入优化: 批量写入、延迟刷新
Pager 结构:
c
typedef struct Pager Pager;
struct Pager {
sqlite3_vfs *pVfs; // VFS接口
u8 exclusiveMode; // 独占模式
u8 journalMode; // 日志模式
u8 useJournal; // 是否使用日志
u8 noSync; // 禁用同步
u8 fullSync; // 完全同步
u8 extraSync; // 额外同步
u8 ckptSyncFlags; // 检查点同步标志
u8 walSyncFlags; // WAL同步标志
u8 syncFlags; // 同步标志
u8 tempFile; // 临时文件
u8 noLock; // 无锁模式
u8 readOnly; // 只读模式
u8 memDb; // 内存数据库
u8 eState; // Pager状态
u8 eLock; // 锁状态
u8 changeCountDone; // 变更计数完成
u8 setMaster; // 设置主日志
u8 doNotSpill; // 不溢出
u8 subjInMemory; // 子日志在内存
u8 bUseFetch; // 使用fetch
u8 hasHeldSharedLock; // 持有共享锁
Pgno dbSize; // 数据库大小(页数)
Pgno dbOrigSize; // 原始大小
Pgno dbFileSize; // 文件大小
Pgno dbHintSize; // 提示大小
int errCode; // 错误码
int nRec; // 日志记录数
u32 cksumInit; // 校验和初始值
u32 nSubRec; // 子事务记录数
Bitvec *pInJournal; // 日志页位图
sqlite3_file *fd; // 数据库文件
sqlite3_file *jfd; // 日志文件
sqlite3_file *sjfd; // 子日志文件
i64 journalOff; // 日志偏移
i64 journalHdr; // 日志头偏移
sqlite3_backup *pBackup; // 备份实例
PagerSavepoint *aSavepoint; // 保存点数组
int nSavepoint; // 保存点数量
u32 iDataVersion; // 数据版本
char dbFileVers[16]; // 文件版本字符串
// ... 更多字段
};
4.2 日志模式
DELETE 模式(默认):
1. 开始事务 → 创建 .db-journal 文件
2. 写入原始页面到日志
3. 修改数据库页面
4. 提交事务 → 删除日志文件
5. 如果崩溃 → 从日志恢复原始页面
TRUNCATE 模式:
• 提交时截断日志文件(不删除)
• 减少文件系统元数据操作
• 适合频繁事务的场景
PERSIST 模式:
• 保留日志文件但清空内容
• 避免文件创建/删除开销
• 首次事务后性能更好
MEMORY 模式:
• 日志存储在内存中
• 崩溃后无法恢复
• 临时数据库或性能优先场景
WAL 模式(Write-Ahead Logging):
• 写入操作记录到 .db-wal 文件
• 读写并发(读不阻塞写)
• 更好的并发性能
• 需要定期检查点(checkpoint)
4.3 WAL 模式详解
WAL 文件结构:
┌─────────────────────┐
│ WAL Header (32字节) │
│ - Magic number │
│ - File format │
│ - Page size │
│ - Checkpoint seq │
│ - Salt values │
│ - Checksum │
├─────────────────────┤
│ Frame 1 │
│ - Page number (4B) │
│ - DB size (4B) │
│ - Salt (8B) │
│ - Checksum (8B) │
│ - Page data (4KB) │
├─────────────────────┤
│ Frame 2 │
├─────────────────────┤
│ Frame 3 │
│ ... │
└─────────────────────┘
WAL 优势:
- 读写并发: 读操作不阻塞写操作
- 更快的事务: 顺序写入日志,减少随机I/O
- 原子提交: 通过检查点实现
- 更少的fsync: 批量刷新
WAL 缺点:
- 额外文件: .db-wal 和 .db-shm(共享内存)
- 网络文件系统: 不支持某些NFS
- 单向传播: 不适合主从复制
检查点操作:
c
// 自动检查点(WAL达到1000页时)
PRAGMA wal_autocheckpoint=1000;
// 手动检查点
PRAGMA wal_checkpoint;
PRAGMA wal_checkpoint(PASSIVE); // 被动模式
PRAGMA wal_checkpoint(FULL); // 完全模式
PRAGMA wal_checkpoint(RESTART); // 重启模式
PRAGMA wal_checkpoint(TRUNCATE); // 截断模式
4.4 页面缓存
PCache 接口:
c
typedef struct PCache PCache;
typedef struct PgHdr PgHdr;
// 页头结构
struct PgHdr {
sqlite3_pcache_page *pPage; // 缓存页
void *pData; // 页数据
void *pExtra; // 额外数据
PgHdr *pDirty; // 脏页链表
Pager *pPager; // 所属Pager
Pgno pgno; // 页号
u16 flags; // 标志
i16 nRef; // 引用计数
PCache *pCache; // 所属缓存
PgHdr *pDirtyNext; // 下一个脏页
PgHdr *pDirtyPrev; // 上一个脏页
};
缓存策略(pcache1.c):
c
// LRU (Least Recently Used) 替换算法
// 配置缓存大小
PRAGMA cache_size = 2000; // 2000页 = 8MB(4KB页)
// 查询缓存大小
PRAGMA cache_size;
缓存统计:
c
// 查看缓存命中率
PRAGMA cache_stats;
// 输出: 命中数, 未命中数, 写入数, 脏页数
5. 查询优化器
5.1 查询规划器架构
位置 : where.c, whereInt.h, whereCode.c, whereexpr.c
行数: ~35,000 行
核心任务:
- 分析 WHERE 子句
- 选择最优索引
- 确定表连接顺序
- 生成循环嵌套代码
- 估算查询成本
WhereInfo 结构:
c
typedef struct WhereInfo WhereInfo;
struct WhereInfo {
Parse *pParse; // 解析器上下文
SrcList *pTabList; // FROM子句表列表
ExprList *pOrderBy; // ORDER BY子句
ExprList *pResultSet; // 结果集
Expr *pWhere; // WHERE子句
int aiCurOnePass[2]; // 单遍优化游标
int iContinue; // 继续地址
int iBreak; // 中断地址
int savedNQueryLoop; // 保存的查询循环计数
u16 wctrlFlags; // 控制标志
u8 nLevel; // 嵌套层数
i8 nOBSat; // ORDER BY满足数量
u8 eOnePass; // 单遍删除/更新
u8 eDistinct; // DISTINCT处理
u8 bOrderedInnerLoop; // 内层循环有序
WhereLevel a[1]; // 每层信息(柔性数组)
};
5.2 索引选择
成本估算模型:
c
// 成本 = 磁盘I/O次数 + CPU周期
Cost = (DiskIO * 1.0) + (CPUCycles * 0.001)
// 全表扫描成本
FullScanCost = TablePages * 1.0
// 索引扫描成本
IndexScanCost = IndexPages + (MatchedRows * 1.0)
索引类型:
单列索引:
sql
CREATE INDEX idx_age ON users(age);
-- 可用于:
SELECT * FROM users WHERE age = 18;
SELECT * FROM users WHERE age > 18;
SELECT * FROM users WHERE age BETWEEN 18 AND 30;
多列索引:
sql
CREATE INDEX idx_age_name ON users(age, name);
-- 可用于:
SELECT * FROM users WHERE age = 18 AND name = 'Alice';
SELECT * FROM users WHERE age = 18; -- 使用索引前缀
-- 不可用于:
SELECT * FROM users WHERE name = 'Alice'; -- 跳过前导列
覆盖索引:
sql
CREATE INDEX idx_age_name ON users(age, name);
-- 覆盖索引查询(不需要回表)
SELECT name FROM users WHERE age = 18;
-- 所有需要的列都在索引中
部分索引(Partial Index):
sql
CREATE INDEX idx_active_users ON users(name) WHERE active = 1;
-- 仅索引活跃用户,减少索引大小
5.3 连接优化
嵌套循环连接(Nested Loop Join):
sql
SELECT * FROM users u, orders o WHERE u.id = o.user_id;
生成代码逻辑:
python
for row_u in users: # 外层循环
for row_o in orders: # 内层循环
if row_u.id == row_o.user_id:
output(row_u, row_o)
连接顺序选择:
• 成本模型: 选择最小化总行数的顺序
• 小表驱动大表原则
• 考虑索引可用性
示例:
users: 1000行
orders: 100000行
users.id 上有索引
最优顺序: orders → users
• 外层: orders (100000行)
• 内层: users (通过索引查找, 每次O(log N))
• 总成本: 100000 * log(1000) ≈ 1,000,000
差的顺序: users → orders
• 外层: users (1000行)
• 内层: orders (全表扫描, 每次100000行)
• 总成本: 1000 * 100000 = 100,000,000
Bloom Filter 优化:
c
// 对于大表连接,使用Bloom Filter预过滤
// 在内存中构建小表的Bloom Filter
// 扫描大表时先检查Bloom Filter,减少无效连接
5.4 查询重写
子查询扁平化:
sql
-- 原始查询
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
-- 重写为连接
SELECT DISTINCT users.* FROM users JOIN orders ON users.id = orders.user_id;
常量折叠:
sql
-- 原始
SELECT * FROM users WHERE age > 10 + 8;
-- 优化为
SELECT * FROM users WHERE age > 18;
布尔代数简化:
sql
-- 原始
SELECT * FROM users WHERE (age > 18 OR age > 21);
-- 优化为
SELECT * FROM users WHERE age > 18;
BETWEEN 展开:
sql
-- 原始
SELECT * FROM users WHERE age BETWEEN 18 AND 30;
-- 展开为
SELECT * FROM users WHERE age >= 18 AND age <= 30;
-- 便于索引范围扫描
5.5 EXPLAIN QUERY PLAN
查看查询计划:
sql
EXPLAIN QUERY PLAN SELECT * FROM users WHERE age > 18;
输出示例:
QUERY PLAN
|--SEARCH TABLE users USING INDEX idx_age (age>?)
计划类型:
- SCAN TABLE: 全表扫描
- SEARCH TABLE USING INDEX: 使用索引
- SEARCH TABLE USING INTEGER PRIMARY KEY: 使用rowid
- USE TEMP B-TREE FOR ORDER BY: 临时B-tree排序
- USE TEMP B-TREE FOR DISTINCT: 临时B-tree去重
SQL执行流程
完整执行流程
1. 应用调用 sqlite3_prepare_v2()
↓
2. Tokenizer: SQL文本 → Token流
↓
3. Parser: Token流 → AST
↓
4. Resolver: 解析表名、列名、函数
↓
5. Query Planner: 生成执行计划
↓
6. Code Generator: AST → VDBE字节码
↓
7. sqlite3_step(): 执行字节码
↓
8. VDBE执行:
- 打开B-tree游标
- 执行查询/修改操作
- 管理事务
↓
9. B-tree层: 读写数据页
↓
10. Pager层: 缓存管理、日志记录
↓
11. VFS层: 文件I/O
↓
12. sqlite3_column_*(): 获取结果
↓
13. sqlite3_finalize(): 释放资源
示例:SELECT 查询
SQL:
sql
SELECT name, age FROM users WHERE age > 18 ORDER BY name;
执行步骤:
-
Prepare 阶段:
csqlite3_prepare_v2(db, sql, -1, &stmt, NULL);- Tokenizer: 识别关键字、标识符
- Parser: 构建 Select AST
- Planner: 选择索引(如果有 idx_age)
- Code Generator: 生成 VDBE 字节码
-
生成的 VDBE 字节码(简化):
Init 0 20 0 0 OpenRead 0 2 0 3 root=2; users表 OpenEphemeral 1 2 0 keyinfo(1,BINARY) 临时表用于排序 Rewind 0 12 0 遍历users表 Column 0 2 1 r[1]=age Ge 18 11 1 (BINARY) if r[1]>=18 goto 11 Column 0 1 2 r[2]=name Column 0 2 3 r[3]=age MakeRecord 2 2 4 r[4]=record(r[2],r[3]) IdxInsert 1 4 2 2 插入临时表 Next 0 5 0 Close 0 0 0 Sort 1 17 0 排序临时表 Column 1 0 5 r[5]=name Column 1 1 6 r[6]=age ResultRow 5 2 0 输出r[5],r[6] Next 1 13 0 Close 1 0 0 Halt 0 0 0 Transaction 0 0 1 0 Goto 0 1 0 -
Step 阶段:
cwhile (sqlite3_step(stmt) == SQLITE_ROW) { const char *name = sqlite3_column_text(stmt, 0); int age = sqlite3_column_int(stmt, 1); printf("%s, %d\n", name, age); } -
Finalize 阶段:
csqlite3_finalize(stmt);
示例:INSERT 操作
SQL:
sql
INSERT INTO users (name, age) VALUES ('Alice', 25);
执行步骤:
- 开始事务(隐式)
- 分配 rowid :
- 查找最大 rowid
- 递增生成新 rowid
- 构造记录 :
- MakeRecord 指令创建行数据
- 插入 B-tree :
- 定位插入位置
- 插入叶子节点
- 如果需要,分裂节点
- 更新索引 :
- 为每个索引插入条目
- 写入日志 :
- 记录修改的页面到 .db-journal
- 提交事务(隐式)
VDBE 字节码(简化):
Init 0 15 0
Transaction 0 1 1 0 开始写事务
Integer 25 1 0 r[1]=25
String8 0 2 0 Alice r[2]='Alice'
NewRowid 0 3 0 r[3]=new_rowid
MakeRecord 2 2 4 r[4]=record(r[2],r[1])
Insert 0 4 3 插入rowid=r[3], data=r[4]
Halt 0 0 0
构建系统
Android.bp 配置
主库配置 (dist/Android.bp):
go
cc_library {
name: "libsqlite",
host_supported: true,
vendor_available: true,
product_available: true,
vndk: {
enabled: true,
},
srcs: ["sqlite3.c"],
cflags: [
"-DSQLITE_THREADSAFE=2", // 序列化访问模式
"-DSQLITE_TEMP_STORE=3", // 临时表存储在内存
"-DSQLITE_ENABLE_FTS3", // 全文搜索v3
"-DSQLITE_ENABLE_FTS3_BACKWARDS", // FTS1/2兼容
"-DSQLITE_ENABLE_FTS4", // 全文搜索v4
"-DSQLITE_ENABLE_ICU", // Unicode支持
"-DSQLITE_ENABLE_DBSTAT_VTAB", // 数据库统计虚拟表
"-DSQLITE_ENABLE_BATCH_ATOMIC_WRITE", // 批量原子写
"-DSQLITE_DEFAULT_AUTOVACUUM=1", // 自动清理
"-DSQLITE_POWERSAFE_OVERWRITE=1", // 电源安全覆写
"-DSQLITE_SECURE_DELETE", // 安全删除
"-DSQLITE_DEFAULT_FILE_PERMISSIONS=0600", // 文件权限
"-DUSE_PREAD64", // 64位pread
"-DHAVE_MALLOC_H",
"-DHAVE_MALLOC_USABLE_SIZE",
"-DBIONIC_IOCTL_NO_SIGNEDNESS_OVERLOAD",
],
shared_libs: [
"libandroidicu", // ICU库
"liblog", // Android日志
"libdl", // 动态链接
],
whole_static_libs: [
"libsqlite3_android", // Android扩展
],
export_include_dirs: ["."],
target: {
android: {
cflags: [
"-DSQLITE_OMIT_BUILTIN_TEST",
"-DSQLITE_OMIT_COMPILEOPTION_DIAGS",
"-DSQLITE_OMIT_LOAD_EXTENSION",
],
},
},
}
编译选项详解
线程安全模式:
c
-DSQLITE_THREADSAFE=2 // Serialized模式
// 0: 无线程安全(单线程)
// 1: Multi-threaded(多个数据库连接可跨线程)
// 2: Serialized(单个连接可跨线程,自动加锁)
临时存储:
c
-DSQLITE_TEMP_STORE=3 // 全部在内存
// 0: 总是使用文件
// 1: 默认文件,可配置内存
// 2: 默认内存,可配置文件
// 3: 总是使用内存
功能启用:
c
-DSQLITE_ENABLE_FTS3 // 全文搜索v3
-DSQLITE_ENABLE_FTS4 // 全文搜索v4(推荐)
-DSQLITE_ENABLE_FTS5 // 全文搜索v5(最新)
-DSQLITE_ENABLE_ICU // ICU Unicode支持
-DSQLITE_ENABLE_RTREE // R-tree空间索引
-DSQLITE_ENABLE_JSON1 // JSON函数
-DSQLITE_ENABLE_DBSTAT_VTAB // 数据库统计
性能优化:
c
-DSQLITE_ENABLE_BATCH_ATOMIC_WRITE // 批量原子写入
-DSQLITE_DEFAULT_AUTOVACUUM=1 // 自动vacuum
-DSQLITE_POWERSAFE_OVERWRITE=1 // 假设电源安全
安全选项:
c
-DSQLITE_SECURE_DELETE // 覆写已删除数据
-DSQLITE_DEFAULT_FILE_PERMISSIONS=0600 // 仅所有者可读写
-DSQLITE_OMIT_LOAD_EXTENSION // 禁用动态扩展加载
功能裁剪:
c
-DSQLITE_OMIT_BUILTIN_TEST // 移除内置测试
-DSQLITE_OMIT_COMPILEOPTION_DIAGS // 移除编译选项诊断
-DSQLITE_OMIT_DEPRECATED // 移除废弃API
编译产物
共享库:
- libsqlite.so: 主SQLite库(~2-3 MB)
- 位置:
/system/lib64/libsqlite.so(64位) - 位置:
/system/lib/libsqlite.so(32位)
静态库:
- libsqlite_static_noicu.a: 无ICU版本(~1.5 MB)
- 用于最小化环境(microdroid)
命令行工具:
- sqlite3: 交互式SQL shell
- 位置:
/system/bin/sqlite3
Android集成
位置与层次
Java Framework Layer:
android.database.sqlite.*
↓ JNI
Native Layer:
frameworks/base/core/jni/android_database_SQLite*.cpp
↓
SQLite Library:
external/sqlite/dist/sqlite3.c
external/sqlite/android/sqlite3_android.cpp
↓
Disk:
/data/data/<package>/databases/*.db
Java API
SQLiteDatabase 类:
java
// 打开/创建数据库
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
"/data/data/com.example/databases/app.db",
null
);
// 执行查询
Cursor cursor = db.rawQuery(
"SELECT name, age FROM users WHERE age > ?",
new String[]{"18"}
);
while (cursor.moveToNext()) {
String name = cursor.getString(0);
int age = cursor.getInt(1);
}
cursor.close();
// 插入数据
ContentValues values = new ContentValues();
values.put("name", "Alice");
values.put("age", 25);
long rowId = db.insert("users", null, values);
// 事务
db.beginTransaction();
try {
db.execSQL("INSERT INTO users VALUES (...)");
db.execSQL("UPDATE users SET ...");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
db.close();
SQLiteOpenHelper 类:
java
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "app.db";
private static final int DATABASE_VERSION = 1;
public MyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT NOT NULL, " +
"age INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
}
// 使用
MyDatabaseHelper helper = new MyDatabaseHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();
Android 扩展功能
位置 : external/sqlite/android/
1. 电话号码比较函数
sqlite3_android.cpp 提供:
c
// 函数: PHONE_NUMBERS_EQUAL(num1, num2)
// 宽松匹配(默认)
SELECT * FROM contacts WHERE PHONE_NUMBERS_EQUAL(phone, '1234567890');
// 函数: PHONE_NUMBERS_EQUAL(num1, num2, use_strict)
// 严格匹配
SELECT * FROM contacts
WHERE PHONE_NUMBERS_EQUAL(phone, '1234567890', 1);
// 函数: PHONE_NUMBERS_EQUAL(num1, num2, use_strict, min_match)
// 指定最小匹配位数
SELECT * FROM contacts
WHERE PHONE_NUMBERS_EQUAL(phone, '1234567890', 0, 7);
// 函数: _PHONE_NUMBER_STRIPPED_REVERSED(number)
// 电话号码标准化
SELECT _PHONE_NUMBER_STRIPPED_REVERSED('(123) 456-7890');
-- 返回: '0987654321'(去除格式,反转)
PhoneNumberUtils.cpp 实现:
cpp
// 宽松匹配
bool phone_number_compare_loose(
const char* num1,
const char* num2
) {
// 1. 去除非数字字符 (-, (), 空格等)
// 2. 比较后N位数字(通常7位)
// 3. 忽略国家/地区代码差异
}
// 严格匹配
bool phone_number_compare_strict(
const char* num1,
const char* num2
) {
// 1. 完整号码比较
// 2. 考虑国家代码
// 3. 所有位都必须匹配
}
使用场景:
- 联系人应用搜索
- 来电号码匹配
- 短信发送人识别
2. Unicode 排序规则
注册 Collator:
c
int register_localized_collators(
sqlite3* handle,
const char* systemLocale,
int utf16Storage
);
可用 Collator:
UNICODE:
sql
-- 标准Unicode排序
CREATE INDEX idx_name ON users(name COLLATE UNICODE);
SELECT * FROM users ORDER BY name COLLATE UNICODE;
LOCALIZED:
sql
-- 本地化排序(根据系统语言)
CREATE INDEX idx_name ON users(name COLLATE LOCALIZED);
SELECT * FROM users ORDER BY name COLLATE LOCALIZED;
-- 示例:中文拼音排序
-- 系统语言=zh_CN时,按拼音排序
-- 张三 < 李四 < 王五
实现(基于 ICU):
cpp
// 使用 libicuuc 和 libicui18n
UCollator* collator = ucol_open(locale, &status);
ucol_setStrength(collator, UCOL_PRIMARY); // 忽略大小写
int compareUTF8(
const char* str1, int len1,
const char* str2, int len2
) {
return ucol_strcollUTF8(collator,
str1, len1, str2, len2, &status);
}
3. 文件删除函数
_DELETE_FILE(path):
sql
-- 安全删除文件
SELECT _DELETE_FILE('/data/data/com.example/cache/temp.txt');
安全限制:
cpp
// 仅允许删除以下路径的文件:
// - /data/data/<package>/
// - /sdcard/Android/data/<package>/
//
// 拒绝删除系统文件或其他应用文件
JNI 集成
位置 : frameworks/base/core/jni/android_database_SQLite*.cpp
主要JNI函数:
cpp
// 打开数据库
static jlong nativeOpen(JNIEnv* env, jclass clazz,
jstring pathStr, jint openFlags, jstring labelStr,
jboolean enableTrace, jboolean enableProfile,
jint lookasideSlotSize, jint lookasideSlotCount)
// 准备语句
static jlong nativePrepareStatement(JNIEnv* env, jclass clazz,
jlong connectionPtr, jstring sqlString)
// 执行语句
static void nativeExecute(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr)
// 执行查询
static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr)
// 绑定参数
static void nativeBindNull(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr, jint index)
static void nativeBindLong(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr, jint index, jlong value)
static void nativeBindString(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr, jint index, jstring valueString)
系统数据库
联系人数据库:
/data/data/com.android.providers.contacts/databases/contacts2.db
表结构:
contacts- 联系人raw_contacts- 原始联系人数据data- 联系人详细信息(电话、邮箱等)groups- 联系人分组
浏览器数据库:
/data/data/com.android.browser/databases/browser.db
表结构:
bookmarks- 书签history- 浏览历史searches- 搜索历史
通话记录:
/data/data/com.android.providers.contacts/databases/calllog.db
表结构:
calls- 通话记录
设置数据库:
/data/system/users/0/settings.db
表结构:
system- 系统设置secure- 安全设置global- 全局设置
性能优化
1. 索引优化
创建索引:
sql
-- 单列索引
CREATE INDEX idx_age ON users(age);
-- 多列索引
CREATE INDEX idx_age_name ON users(age, name);
-- 部分索引(节省空间)
CREATE INDEX idx_active_users ON users(name) WHERE active = 1;
-- 唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);
-- 表达式索引
CREATE INDEX idx_lower_name ON users(LOWER(name));
索引最佳实践:
sql
-- ✅ 好的索引使用
SELECT * FROM users WHERE age > 18; -- 使用 idx_age
SELECT * FROM users WHERE age = 18 AND name = 'Alice'; -- 使用 idx_age_name
-- ❌ 无法使用索引
SELECT * FROM users WHERE LOWER(name) = 'alice'; -- 函数包裹列
SELECT * FROM users WHERE age + 1 > 18; -- 表达式包裹列
-- ✅ 修正:使用表达式索引或重写查询
CREATE INDEX idx_lower_name ON users(LOWER(name));
SELECT * FROM users WHERE LOWER(name) = 'alice';
-- 或
SELECT * FROM users WHERE age > 17; -- 重写表达式
查看索引使用情况:
sql
-- 查看索引列表
PRAGMA index_list('users');
-- 查看索引信息
PRAGMA index_info('idx_age');
-- 查看索引统计
ANALYZE;
SELECT * FROM sqlite_stat1 WHERE tbl = 'users';
2. 查询优化
使用 EXPLAIN QUERY PLAN:
sql
EXPLAIN QUERY PLAN SELECT * FROM users WHERE age > 18;
**避免 SELECT ***:
sql
-- ❌ 低效
SELECT * FROM users WHERE age > 18;
-- ✅ 高效(仅选择需要的列)
SELECT id, name FROM users WHERE age > 18;
使用 LIMIT:
sql
-- ❌ 低效(返回所有结果)
SELECT * FROM users ORDER BY name;
-- ✅ 高效(仅返回前10条)
SELECT * FROM users ORDER BY name LIMIT 10;
批量操作:
sql
-- ❌ 低效(多次事务)
FOR EACH row:
INSERT INTO users VALUES (...);
-- ✅ 高效(单个事务)
BEGIN TRANSACTION;
FOR EACH row:
INSERT INTO users VALUES (...);
COMMIT;
3. 事务优化
显式事务:
sql
-- ❌ 默认每条语句一个事务
INSERT INTO users VALUES ('Alice', 25);
INSERT INTO users VALUES ('Bob', 30);
-- 2次磁盘同步
-- ✅ 批量事务
BEGIN TRANSACTION;
INSERT INTO users VALUES ('Alice', 25);
INSERT INTO users VALUES ('Bob', 30);
COMMIT;
-- 1次磁盘同步(快100倍)
性能对比:
单条插入(自动提交): ~100 插入/秒
批量事务(10000条): ~50,000 插入/秒
使用WAL模式: ~100,000 插入/秒
4. WAL 模式优化
启用 WAL:
sql
PRAGMA journal_mode = WAL;
WAL 配置:
sql
-- 自动检查点阈值(默认1000页)
PRAGMA wal_autocheckpoint = 1000;
-- 设置更大的阈值以提高写入性能
PRAGMA wal_autocheckpoint = 10000;
-- 禁用自动检查点(手动控制)
PRAGMA wal_autocheckpoint = 0;
手动检查点:
sql
-- 被动检查点(不阻塞)
PRAGMA wal_checkpoint(PASSIVE);
-- 完全检查点(等待完成)
PRAGMA wal_checkpoint(FULL);
-- 重启检查点(重置WAL)
PRAGMA wal_checkpoint(RESTART);
5. 缓存优化
页面缓存大小:
sql
-- 查看当前缓存大小
PRAGMA cache_size;
-- 设置缓存大小(单位:页,负数表示KB)
PRAGMA cache_size = 10000; -- 10000页 = 40MB(4KB页)
PRAGMA cache_size = -10000; -- 10MB
推荐配置:
sql
-- 移动设备(内存受限)
PRAGMA cache_size = -2000; -- 2MB
-- 桌面应用
PRAGMA cache_size = -10000; -- 10MB
-- 服务器
PRAGMA cache_size = -100000; -- 100MB
6. 预编译语句
重用预编译语句:
java
// ❌ 低效(每次都编译)
for (int i = 0; i < 1000; i++) {
db.execSQL("INSERT INTO users VALUES (" + i + ", 'User" + i + "')");
}
// ✅ 高效(编译一次,重用多次)
SQLiteStatement stmt = db.compileStatement(
"INSERT INTO users VALUES (?, ?)"
);
db.beginTransaction();
try {
for (int i = 0; i < 1000; i++) {
stmt.bindLong(1, i);
stmt.bindString(2, "User" + i);
stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
stmt.close();
}
7. 数据类型优化
选择合适的数据类型:
sql
-- ✅ 使用INTEGER存储日期(Unix时间戳)
CREATE TABLE events (
id INTEGER PRIMARY KEY,
timestamp INTEGER -- 4-8字节
);
-- ❌ 使用TEXT存储日期
CREATE TABLE events (
id INTEGER PRIMARY KEY,
timestamp TEXT -- 19字节 '2024-01-01 12:00:00'
);
类型亲和性:
sql
-- SQLite类型亲和性(动态类型)
-- INTEGER: 整数
-- REAL: 浮点数
-- TEXT: 文本
-- BLOB: 二进制
-- NUMERIC: 数值(尝试转换为INTEGER或REAL)
8. 自动清理优化
自动 VACUUM:
sql
-- 启用自动vacuum(建议在创建数据库时设置)
PRAGMA auto_vacuum = FULL; -- 完全自动vacuum
PRAGMA auto_vacuum = INCREMENTAL; -- 增量vacuum
-- 手动vacuum(回收空闲空间)
VACUUM;
-- 增量vacuum(释放N页)
PRAGMA incremental_vacuum(100);
9. 分析与统计
ANALYZE 命令:
sql
-- 分析所有表
ANALYZE;
-- 分析特定表
ANALYZE users;
-- 查看统计信息
SELECT * FROM sqlite_stat1;
SELECT * FROM sqlite_stat4; -- 如果启用
统计信息作用:
- 帮助查询优化器选择最佳索引
- 估算查询成本
- 决定连接顺序
更新频率:
sql
-- 数据变化超过10%时重新分析
-- 或定期(如每天)执行ANALYZE
10. 内存管理
Lookaside 内存:
sql
-- 配置lookaside缓冲区(快速小对象分配)
PRAGMA lookaside = 128,250; -- 128字节 × 250个槽位
软堆限制:
c
// 设置软堆限制(限制内存使用)
sqlite3_soft_heap_limit64(100 * 1024 * 1024); // 100MB
高级特性
1. 全文搜索 (FTS)
FTS4 使用
创建FTS表:
sql
-- FTS4虚拟表
CREATE VIRTUAL TABLE documents_fts USING fts4(
title,
content,
tokenize=porter -- Porter词干算法
);
-- 插入数据
INSERT INTO documents_fts VALUES ('Hello World', 'This is a test document');
-- 全文搜索
SELECT * FROM documents_fts WHERE documents_fts MATCH 'test';
-- 短语搜索
SELECT * FROM documents_fts WHERE documents_fts MATCH '"hello world"';
-- 布尔查询
SELECT * FROM documents_fts WHERE documents_fts MATCH 'test AND document';
SELECT * FROM documents_fts WHERE documents_fts MATCH 'test OR hello';
SELECT * FROM documents_fts WHERE documents_fts MATCH 'test NOT hello';
-- 前缀搜索
SELECT * FROM documents_fts WHERE documents_fts MATCH 'hel*';
高级功能:
sql
-- Snippet(摘要)
SELECT snippet(documents_fts, '[', ']', '...', -1, 20)
FROM documents_fts
WHERE documents_fts MATCH 'test';
-- 输出: "This is a [test] document"
-- Offsets(匹配位置)
SELECT offsets(documents_fts)
FROM documents_fts
WHERE documents_fts MATCH 'test';
-- 输出: "1 0 10 4"(列号 词号 字节偏移 长度)
FTS5 使用
创建FTS5表:
sql
CREATE VIRTUAL TABLE documents_fts5 USING fts5(
title,
content,
tokenize='porter ascii'
);
-- 排名
SELECT *, rank FROM documents_fts5
WHERE documents_fts5 MATCH 'test'
ORDER BY rank;
-- BM25排名算法
SELECT *, bm25(documents_fts5) AS score
FROM documents_fts5
WHERE documents_fts5 MATCH 'test'
ORDER BY score;
注意: Android 默认不启用 FTS5,需要自定义编译。
2. R-Tree 空间索引
创建R-Tree表:
sql
CREATE VIRTUAL TABLE locations USING rtree(
id, -- 整数主键
min_lat, max_lat, -- 纬度范围
min_lon, max_lon -- 经度范围
);
-- 插入位置数据
INSERT INTO locations VALUES (
1, -- ID
40.7127, 40.7129, -- 纬度范围(纽约)
-74.0060, -74.0058 -- 经度范围
);
-- 查询范围内的位置
SELECT id FROM locations
WHERE min_lat >= 40.0 AND max_lat <= 41.0
AND min_lon >= -75.0 AND max_lon <= -73.0;
使用场景:
- 地理位置查询(附近的POI)
- 游戏中的碰撞检测
- 图像区域搜索
3. JSON 支持
JSON1 扩展:
sql
-- 创建带JSON列的表
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT,
metadata JSON -- 实际存储为TEXT
);
-- 插入JSON数据
INSERT INTO users VALUES (
1,
'Alice',
'{"age": 25, "city": "NYC", "tags": ["developer", "reader"]}'
);
-- 提取JSON值
SELECT name, json_extract(metadata, '$.age') AS age
FROM users;
-- JSON路径查询
SELECT name FROM users
WHERE json_extract(metadata, '$.city') = 'NYC';
-- 数组操作
SELECT name, json_extract(metadata, '$.tags[0]') AS first_tag
FROM users;
-- 修改JSON
UPDATE users SET metadata = json_set(metadata, '$.age', 26)
WHERE id = 1;
-- 添加字段
UPDATE users SET metadata = json_insert(metadata, '$.country', 'USA')
WHERE id = 1;
-- 删除字段
UPDATE users SET metadata = json_remove(metadata, '$.tags')
WHERE id = 1;
JSON函数:
json_extract(json, path)- 提取值json_set(json, path, value)- 设置值json_insert(json, path, value)- 插入值json_remove(json, path)- 删除值json_type(json, path)- 获取类型json_valid(json)- 验证JSONjson_array(...)- 创建数组json_object(...)- 创建对象
注意: Android 需要 API 30+ 或自定义编译启用 JSON1。
4. 窗口函数
Window Functions(SQLite 3.25+):
sql
-- ROW_NUMBER
SELECT name, age,
ROW_NUMBER() OVER (ORDER BY age) AS row_num
FROM users;
-- RANK
SELECT name, score,
RANK() OVER (ORDER BY score DESC) AS rank
FROM students;
-- PARTITION BY
SELECT name, department, salary,
AVG(salary) OVER (PARTITION BY department) AS dept_avg
FROM employees;
-- LEAD/LAG(获取上下行数据)
SELECT date, sales,
LAG(sales, 1) OVER (ORDER BY date) AS prev_sales,
LEAD(sales, 1) OVER (ORDER BY date) AS next_sales
FROM daily_sales;
5. Common Table Expressions (CTE)
WITH 子句:
sql
-- 简单CTE
WITH active_users AS (
SELECT * FROM users WHERE active = 1
)
SELECT * FROM active_users WHERE age > 18;
-- 递归CTE(组织结构树)
WITH RECURSIVE org_chart(id, name, manager_id, level) AS (
SELECT id, name, manager_id, 0
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.name, e.manager_id, oc.level + 1
FROM employees e
JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT * FROM org_chart ORDER BY level, name;
6. 触发器
创建触发器:
sql
-- AFTER INSERT触发器
CREATE TRIGGER users_audit_insert
AFTER INSERT ON users
BEGIN
INSERT INTO audit_log VALUES (
NEW.id,
'INSERT',
datetime('now')
);
END;
-- BEFORE UPDATE触发器
CREATE TRIGGER users_updated_at
BEFORE UPDATE ON users
BEGIN
UPDATE users SET updated_at = datetime('now')
WHERE id = NEW.id;
END;
-- INSTEAD OF触发器(视图)
CREATE TRIGGER users_view_insert
INSTEAD OF INSERT ON users_view
BEGIN
INSERT INTO users (name, age) VALUES (NEW.name, NEW.age);
INSERT INTO user_profiles (user_id) VALUES (last_insert_rowid());
END;
7. 视图
创建视图:
sql
-- 简单视图
CREATE VIEW active_users AS
SELECT id, name, age FROM users WHERE active = 1;
-- 使用视图
SELECT * FROM active_users WHERE age > 18;
-- 物化视图(手动维护)
CREATE TABLE active_users_cache AS
SELECT id, name, age FROM users WHERE active = 1;
-- 定期刷新
DELETE FROM active_users_cache;
INSERT INTO active_users_cache
SELECT id, name, age FROM users WHERE active = 1;
8. 备份与恢复
在线备份 API:
c
// C API
sqlite3 *pSource = ...; // 源数据库
sqlite3 *pDest = ...; // 目标数据库
sqlite3_backup *pBackup = sqlite3_backup_init(
pDest, "main",
pSource, "main"
);
if (pBackup) {
sqlite3_backup_step(pBackup, -1); // -1表示全部复制
sqlite3_backup_finish(pBackup);
}
SQL 备份:
sql
-- 导出到SQL文件
.output backup.sql
.dump
-- 恢复
.read backup.sql
-- VACUUM INTO(复制并压缩)
VACUUM INTO 'backup.db';
9. 附加数据库
ATTACH DATABASE:
sql
-- 附加另一个数据库
ATTACH DATABASE 'other.db' AS other_db;
-- 跨数据库查询
SELECT u.name, o.total
FROM main.users u
JOIN other_db.orders o ON u.id = o.user_id;
-- 跨数据库插入
INSERT INTO other_db.backup_users SELECT * FROM main.users;
-- 分离数据库
DETACH DATABASE other_db;
使用场景:
- 数据迁移
- 跨库查询
- 数据备份
10. 虚拟表
创建自定义虚拟表:
c
// C API
typedef struct my_vtab my_vtab;
struct my_vtab {
sqlite3_vtab base;
// 自定义字段
};
static int myVtabCreate(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVTab,
char **pzErr
) {
// 创建虚拟表
}
static sqlite3_module myModule = {
0, // iVersion
myVtabCreate, // xCreate
myVtabConnect, // xConnect
myVtabBestIndex, // xBestIndex
myVtabDisconnect, // xDisconnect
myVtabDestroy, // xDestroy
myVtabOpen, // xOpen
myVtabClose, // xClose
myVtabFilter, // xFilter
myVtabNext, // xNext
myVtabEof, // xEof
myVtabColumn, // xColumn
myVtabRowid, // xRowid
// ...
};
// 注册虚拟表模块
sqlite3_create_module(db, "my_module", &myModule, NULL);
使用场景:
- CSV文件访问
- 系统信息查询(/proc、/sys)
- REST API包装
- 日志文件解析
测试基础设施
Android CTS 测试
TEST_MAPPING 配置:
json
{
"presubmit": [
{
"name": "CtsDatabaseTestCases"
}
],
"hwasan-postsubmit": [
{
"name": "CtsDatabaseTestCases"
}
]
}
CTS 测试覆盖:
android.database.sqlite.*API测试- CRUD操作测试
- 事务测试
- 游标测试
- 并发测试
- 性能测试
单元测试
PhoneNumberUtilsTest.cpp:
cpp
// 电话号码比较测试
TEST(PhoneNumberUtils, CompareLoose) {
EXPECT_TRUE(phone_number_compare_loose(
"1234567890",
"(123) 456-7890"
));
}
TEST(PhoneNumberUtils, CompareStrict) {
EXPECT_TRUE(phone_number_compare_strict(
"+1-234-567-8900",
"12345678900"
));
}
TEST(PhoneNumberUtils, MinMatch) {
EXPECT_TRUE(phone_number_compare(
"1234567890",
"7890",
false, // use_strict
4 // min_match
));
}
性能测试
基准测试:
sql
-- 插入性能
.timer on
BEGIN TRANSACTION;
-- 插入10000行
COMMIT;
-- 查询性能
EXPLAIN QUERY PLAN SELECT * FROM users WHERE age > 18;
SELECT * FROM users WHERE age > 18;
-- 索引影响
DROP INDEX IF EXISTS idx_age;
SELECT * FROM users WHERE age > 18; -- 全表扫描
CREATE INDEX idx_age ON users(age);
SELECT * FROM users WHERE age > 18; -- 索引扫描
调试工具
sqlite3 CLI:
bash
# 进入交互模式
sqlite3 app.db
# 执行命令
sqlite3 app.db "SELECT * FROM users"
# 导出数据
sqlite3 app.db .dump > backup.sql
# 导入数据
sqlite3 new.db < backup.sql
常用命令:
sql
.tables -- 列出所有表
.schema users -- 查看表结构
.indices users -- 查看表的索引
.mode column -- 列模式显示
.headers on -- 显示列名
.timer on -- 显示执行时间
.stats on -- 显示统计信息
.explain on -- 显示VDBE字节码
adb 调试:
bash
# 查看应用数据库
adb shell
cd /data/data/com.example.app/databases
sqlite3 app.db
# 复制数据库到本地
adb pull /data/data/com.example.app/databases/app.db .
# 查看数据库文件信息
adb shell ls -l /data/data/com.example.app/databases/
最佳实践
1. 数据库设计
表设计原则:
sql
-- ✅ 使用INTEGER PRIMARY KEY(rowid别名)
CREATE TABLE users (
id INTEGER PRIMARY KEY, -- 自动递增,无需AUTOINCREMENT
name TEXT NOT NULL,
email TEXT UNIQUE
);
-- ❌ 避免AUTOINCREMENT(除非必要)
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 额外开销
name TEXT
);
-- ✅ 使用NOT NULL约束
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL, -- 防止NULL值
age INTEGER NOT NULL DEFAULT 0
);
-- ✅ 使用外键约束
PRAGMA foreign_keys = ON;
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
total REAL NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
规范化:
sql
-- ✅ 第三范式(3NF)
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE addresses (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
street TEXT NOT NULL,
city TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- ❌ 反规范化(存储冗余)
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
street TEXT,
city TEXT,
-- 地址信息冗余存储
);
2. 索引策略
何时创建索引:
sql
-- ✅ WHERE子句常用列
CREATE INDEX idx_age ON users(age);
SELECT * FROM users WHERE age > 18;
-- ✅ JOIN条件列
CREATE INDEX idx_user_id ON orders(user_id);
SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
-- ✅ ORDER BY列
CREATE INDEX idx_name ON users(name);
SELECT * FROM users ORDER BY name;
-- ✅ GROUP BY列
CREATE INDEX idx_category ON products(category);
SELECT category, COUNT(*) FROM products GROUP BY category;
何时避免索引:
sql
-- ❌ 小表(<1000行)
-- 全表扫描可能更快
-- ❌ 频繁写入的列
-- 索引维护开销 > 查询收益
-- ❌ 低选择性列(如性别)
-- 索引效果不明显
-- ❌ 从不查询的列
-- 浪费空间
3. 查询优化
避免全表扫描:
sql
-- ❌ 函数包裹列
SELECT * FROM users WHERE LOWER(name) = 'alice';
-- ✅ 使用表达式索引或重写查询
CREATE INDEX idx_lower_name ON users(LOWER(name));
-- 或存储小写版本
ALTER TABLE users ADD COLUMN name_lower TEXT;
UPDATE users SET name_lower = LOWER(name);
CREATE INDEX idx_name_lower ON users(name_lower);
使用参数化查询:
java
// ❌ SQL注入风险 + 性能差
String sql = "SELECT * FROM users WHERE name = '" + userName + "'";
db.rawQuery(sql, null);
// ✅ 安全 + 性能好(预编译)
String sql = "SELECT * FROM users WHERE name = ?";
db.rawQuery(sql, new String[]{userName});
4. 事务管理
显式事务:
java
// ✅ 批量操作使用事务
db.beginTransaction();
try {
for (User user : users) {
db.insert("users", null, user.toContentValues());
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
// ❌ 每次插入自动提交
for (User user : users) {
db.insert("users", null, user.toContentValues());
}
嵌套事务(Savepoint):
sql
BEGIN TRANSACTION;
INSERT INTO users VALUES (...);
SAVEPOINT sp1;
UPDATE users SET age = 30 WHERE id = 1;
-- 如果失败
ROLLBACK TO sp1;
RELEASE sp1;
COMMIT;
5. 并发控制
WAL模式:
java
// ✅ 启用WAL模式(读写并发)
db.enableWriteAheadLogging();
// 或通过PRAGMA
db.execSQL("PRAGMA journal_mode = WAL");
连接池:
java
// Android SQLiteDatabase内部使用连接池
// 默认配置:
// - 最大连接数 = CPU核心数 + 1
// - 主线程使用主连接
// - 其他线程共享连接池
6. 内存管理
关闭资源:
java
// ✅ 总是关闭Cursor
Cursor cursor = db.rawQuery("SELECT * FROM users", null);
try {
while (cursor.moveToNext()) {
// 处理数据
}
} finally {
cursor.close(); // 释放内存
}
// ✅ 使用try-with-resources(API 16+)
try (Cursor cursor = db.rawQuery("SELECT * FROM users", null)) {
while (cursor.moveToNext()) {
// 处理数据
}
}
7. 升级策略
版本管理:
java
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final int VERSION_1 = 1;
private static final int VERSION_2 = 2;
private static final int VERSION_3 = 3;
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < VERSION_2) {
// 升级到版本2
db.execSQL("ALTER TABLE users ADD COLUMN email TEXT");
}
if (oldVersion < VERSION_3) {
// 升级到版本3
db.execSQL("CREATE INDEX idx_email ON users(email)");
}
}
}
迁移最佳实践:
java
// ✅ 使用ALTER TABLE ADD COLUMN
db.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
// ✅ 新表创建
db.execSQL("CREATE TABLE user_profiles (...)");
// ❌ 避免DROP TABLE(数据丢失)
// 如必须重建:
db.execSQL("ALTER TABLE users RENAME TO users_old");
db.execSQL("CREATE TABLE users (...)");
db.execSQL("INSERT INTO users SELECT id, name, ... FROM users_old");
db.execSQL("DROP TABLE users_old");
8. 安全建议
防止SQL注入:
java
// ❌ 危险
String sql = "SELECT * FROM users WHERE name = '" + input + "'";
// 如果input = "'; DROP TABLE users; --"
// 实际执行: SELECT * FROM users WHERE name = ''; DROP TABLE users; --'
// ✅ 使用参数化查询
db.rawQuery("SELECT * FROM users WHERE name = ?", new String[]{input});
权限控制:
java
// ✅ 设置数据库文件权限
SQLiteDatabase.openOrCreateDatabase(
dbPath,
null,
Context.MODE_PRIVATE // 仅应用可访问
);
加密(需要第三方库,如SQLCipher):
java
// 使用SQLCipher加密数据库
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
dbPath,
password, // 加密密码
null
);
9. 性能监控
查询分析:
sql
-- 启用查询日志
PRAGMA vdbe_trace = ON;
-- 查看查询计划
EXPLAIN QUERY PLAN SELECT * FROM users WHERE age > 18;
-- 查看执行统计
.stats on
SELECT * FROM users WHERE age > 18;
Android Profiler:
java
// 使用Android Studio Profiler监控:
// - 数据库查询时间
// - 内存使用
// - I/O操作
10. 错误处理
异常捕获:
java
try {
db.beginTransaction();
// 数据库操作
db.setTransactionSuccessful();
} catch (SQLiteException e) {
Log.e(TAG, "Database error", e);
// 错误处理
} finally {
db.endTransaction();
}
约束冲突处理:
sql
-- 插入或忽略
INSERT OR IGNORE INTO users (id, name) VALUES (1, 'Alice');
-- 插入或替换
INSERT OR REPLACE INTO users (id, name) VALUES (1, 'Alice');
-- 插入或回滚
INSERT OR ROLLBACK INTO users (id, name) VALUES (1, 'Alice');
总结
SQLite 核心价值
- 零配置: 无需安装、配置、管理服务器
- 自包含: 单文件数据库,易于部署和备份
- 跨平台: 在所有主流平台上运行
- 可靠性: 经过数十亿次部署验证
- 高性能: 针对嵌入式环境优化
- 完整SQL: 支持SQL-92及扩展
- ACID: 完整的事务支持
- 小巧: 编译后仅几MB
技术亮点
架构设计:
- ✅ 分层清晰:SQL编译器 → VDBE → B-tree → Pager → VFS
- ✅ 模块化:每层职责明确,易于维护
- ✅ 可扩展:虚拟表、自定义函数、Collator
性能优化:
- ✅ 基于成本的查询优化器
- ✅ B+树索引(O(log N)查找)
- ✅ 页面缓存(LRU替换)
- ✅ WAL模式(读写并发)
- ✅ 批量原子写入
Android集成:
- ✅ 电话号码比较函数
- ✅ Unicode排序规则(ICU)
- ✅ FD Sanitizer集成
- ✅ Java Framework包装
- ✅ 连接池管理
高级特性:
- ✅ 全文搜索(FTS3/4/5)
- ✅ 空间索引(R-Tree)
- ✅ JSON支持(JSON1)
- ✅ 窗口函数
- ✅ CTE(递归查询)
- ✅ 触发器和视图
使用建议
适用场景:
- ✅ 移动应用数据存储
- ✅ 嵌入式设备
- ✅ 桌面应用
- ✅ 原型开发
- ✅ 测试和CI/CD
- ✅ 缓存存储
- ✅ 配置管理
不适用场景:
- ❌ 高并发写入(每秒数千次写入)
- ❌ 客户端-服务器架构(使用PostgreSQL/MySQL)
- ❌ 大数据分析(使用专用OLAP数据库)
- ❌ 需要复杂权限管理
性能对比
| 操作 | DELETE模式 | WAL模式 | 相对速度 |
|---|---|---|---|
| 单条插入 | ~150/秒 | ~150/秒 | 1x |
| 批量插入(事务) | ~50,000/秒 | ~100,000/秒 | 2x |
| 简单查询 | ~100,000/秒 | ~100,000/秒 | 1x |
| 索引查询 | ~500,000/秒 | ~500,000/秒 | 1x |
| 读写并发 | 不支持 | 支持 | ∞ |
最终建议
数据库设计:
- 使用规范化设计(3NF)
- 合理使用NOT NULL约束
- 启用外键约束(PRAGMA foreign_keys = ON)
- 为常用查询列创建索引
性能优化:
- 启用WAL模式(读写并发)
- 批量操作使用显式事务
- 增大页面缓存(PRAGMA cache_size)
- 定期执行ANALYZE更新统计
- 使用预编译语句
安全性:
- 使用参数化查询(防SQL注入)
- 设置文件权限(MODE_PRIVATE)
- 敏感数据考虑加密(SQLCipher)
- 定期备份数据库
可维护性:
- 使用SQLiteOpenHelper管理版本
- 妥善处理数据库升级
- 总是关闭Cursor和Statement
- 记录数据库错误日志
参考资源
官方文档
Android文档
源码
工具
- SQLite Browser - 图形化管理工具
- SQLCipher - 加密扩展
- sqlite-web - Web管理界面
学习资源
"SQLite不是为了与Oracle竞争,而是为了取代fopen()"