Skip to content

SQLite 架构与工作原理

了解 SQLite 的内部架构和工作原理对于 DBA 优化数据库性能、排查问题和制定最佳实践至关重要。本文将详细介绍 SQLite 的架构设计、核心组件和工作原理。

SQLite 架构设计

整体架构

SQLite 采用分层架构设计,从上到下分为以下几个主要层次:

层次主要功能核心组件
接口层提供编程接口C API、SQL 编译器
编译层SQL 语句处理词法分析器、语法分析器、代码生成器
核心层数据库核心功能B 树管理器、页管理器、事务管理器
存储层磁盘 I/O 操作OS 接口、VFS(虚拟文件系统)

架构特点

  • 模块化设计:各层之间通过清晰的接口交互,便于维护和扩展
  • 可移植性:通过 VFS 抽象层,实现跨平台支持
  • 高效性:核心操作直接访问磁盘,减少中间层开销
  • 可靠性:采用事务机制和 WAL 模式,确保数据完整性

核心组件详解

接口层

C API

  • 功能:提供 SQLite 的核心编程接口
  • 主要函数
    • sqlite3_open():打开数据库连接
    • sqlite3_exec():执行 SQL 语句
    • sqlite3_prepare():准备 SQL 语句
    • sqlite3_step():执行准备好的 SQL 语句
    • sqlite3_finalize():释放资源
  • 特点:简单易用,支持多种编程语言绑定

SQL 编译器

  • 功能:将 SQL 语句转换为可执行的字节码
  • 组成部分
    • 词法分析器:将 SQL 语句分解为标记(tokens)
    • 语法分析器:检查 SQL 语句的语法正确性,生成语法树
    • 语义分析器:检查 SQL 语句的语义正确性
    • 代码生成器:将语法树转换为字节码

编译层

词法分析器

  • 功能:将 SQL 语句分解为标记(tokens)
  • 示例:将 SELECT * FROM users WHERE id = 1; 分解为 SELECT*FROMusersWHEREid=1; 等标记
  • 实现:使用有限状态机实现

语法分析器

  • 功能:根据 SQL 语法规则,检查标记序列的语法正确性,生成语法树
  • 实现:使用 LALR(1) 语法分析器生成器(如 yacc)生成
  • 输出:抽象语法树(AST)

代码生成器

  • 功能:将语法树转换为 SQLite 虚拟机(VM)可执行的字节码
  • 优化:对字节码进行优化,提高执行效率
  • 输出:SQLite 虚拟机字节码

核心层

SQLite 虚拟机(VM)

  • 功能:执行字节码指令
  • 特点
    • 基于栈的虚拟机设计
    • 支持事务处理
    • 高效的内存管理
  • 主要操作
    • 数据读取和写入
    • 索引操作
    • 事务管理

B 树管理器

  • 功能:管理数据库的索引和表数据
  • 数据结构
    • 表 B 树:存储表数据
    • 索引 B 树:存储索引数据
  • 特点
    • 平衡树结构,确保查询效率
    • 支持范围查询和排序
    • 高效的插入、删除和更新操作

页管理器

  • 功能:管理数据库文件的页分配和缓存
  • 页大小:默认 4KB,可在创建数据库时指定(512B-64KB)
  • 缓存管理
    • 页缓存:减少磁盘 I/O 操作
    • 缓存替换策略:LRU(最近最少使用)

事务管理器

  • 功能:管理数据库事务,确保 ACID 特性
  • 核心组件
    • 锁管理器:管理数据库锁,确保并发访问安全
    • 日志管理器:管理事务日志,支持崩溃恢复
  • 事务状态
    • 开始(BEGIN)
    • 进行中(ACTIVE)
    • 提交(COMMIT)
    • 回滚(ROLLBACK)

锁管理器

  • 功能:管理数据库的锁,确保并发访问安全
  • 锁类型
    • 共享锁(Shared Lock):允许读取操作,阻止写入操作
    • 预留锁(Reserved Lock):表示准备写入,允许其他读取操作
    • 排他锁(Exclusive Lock):独占数据库,阻止所有其他操作
    • 未决锁(Pending Lock):表示即将获得排他锁,阻止新的读取操作
  • 锁升级流程:共享锁 → 预留锁 → 未决锁 → 排他锁

存储层

虚拟文件系统(VFS)

  • 功能:提供统一的文件系统接口,实现跨平台支持
  • 主要操作
    • 文件打开、关闭、读取和写入
    • 目录管理
    • 锁定操作
    • 随机访问
  • 特点
    • 抽象操作系统差异
    • 支持多种存储设备
    • 可扩展,支持自定义 VFS 实现

OS 接口

  • 功能:与操作系统交互,执行实际的 I/O 操作
  • 支持平台:Windows、Linux、macOS、iOS、Android 等
  • 主要操作
    • 文件 I/O
    • 内存管理
    • 线程同步
    • 时钟获取

SQLite 工作原理

SQL 语句执行流程

当应用程序执行一条 SQL 语句时,SQLite 内部的执行流程如下:

  1. 连接数据库:应用程序通过 C API 打开数据库连接
  2. SQL 编译
    • 词法分析:将 SQL 语句分解为标记
    • 语法分析:生成语法树
    • 语义分析:检查语义正确性
    • 代码生成:生成字节码
  3. 执行计划生成:优化器生成最优执行计划
  4. 字节码执行:SQLite 虚拟机执行字节码
  5. 数据访问
    • 从 B 树中读取或写入数据
    • 管理事务和锁
  6. 返回结果:将执行结果返回给应用程序
  7. 关闭连接:释放资源

数据存储原理

表数据存储

  • 数据结构:使用 B 树存储表数据
  • 行存储:每行数据作为一个记录存储在 B 树节点中
  • 主键索引:表数据本身就是一个以主键为键的 B 树
  • 无主键表:自动生成隐藏的 ROWID 作为主键

索引存储

  • 数据结构:使用 B 树存储索引数据
  • 索引项:包含索引键和指向表数据的指针(ROWID)
  • 多列索引:将多个列的值组合作为索引键
  • 唯一性索引:确保索引键唯一

数据类型存储

  • 动态类型:SQLite 使用动态类型系统,值的类型由值本身决定,而非列的声明类型
  • 类型亲和性:列具有类型亲和性,会尝试将存储的值转换为适合的类型
  • 存储格式
    • NULL:1 字节
    • INTEGER:1-9 字节,变长整数
    • REAL:8 字节,浮点数
    • TEXT:变长字符串,UTF-8 或 UTF-16 编码
    • BLOB:变长二进制数据,原样存储

事务处理原理

事务日志

  • 回滚日志(Rollback Journal):默认模式,在修改数据前,将原始数据写入回滚日志文件
  • 预写日志(WAL - Write-Ahead Logging):在修改数据前,将修改内容写入 WAL 文件

事务提交

  • 回滚日志模式

    1. 开始事务,获取锁
    2. 将修改的页复制到回滚日志
    3. 修改数据库文件
    4. 提交事务,释放锁
    5. 删除或截断回滚日志
  • WAL 模式

    1. 开始事务,获取锁
    2. 将修改内容写入 WAL 文件
    3. 提交事务,释放锁
    4. 定期执行 checkpoint,将 WAL 中的修改合并到数据库文件

崩溃恢复

  • 回滚日志模式:启动时检查回滚日志,如果存在未完成的事务,使用回滚日志恢复数据
  • WAL 模式:启动时检查 WAL 文件,将未合并的修改应用到数据库文件

并发控制原理

锁机制

  • 共享锁(S):多个读取操作可以同时持有共享锁
  • 预留锁(R):一个写入操作可以持有预留锁,允许其他读取操作
  • 未决锁(P):一个写入操作准备获取排他锁,阻止新的读取操作
  • 排他锁(X):一个写入操作持有排他锁,阻止所有其他操作

多版本并发控制(MVCC)

  • 实现方式:在 WAL 模式下,读取操作从数据库文件读取数据,写入操作写入 WAL 文件
  • 读取一致性:读取操作不会被写入操作阻塞,始终读取一致的数据版本
  • 写入原子性:写入操作要么完全成功,要么完全失败

索引工作原理

B 树索引结构

  • 节点结构:每个节点包含多个索引项和指向下级节点的指针
  • 平衡机制:插入或删除操作时,自动调整树结构,保持平衡
  • 查询效率
    • 点查询:O(log n) 时间复杂度
    • 范围查询:O(log n + k) 时间复杂度,k 为结果数量

索引查找过程

  1. 从根节点开始,比较查询键与索引项
  2. 根据比较结果,选择对应的子节点
  3. 重复上述过程,直到到达叶节点
  4. 在叶节点中查找匹配的索引项
  5. 根据索引项中的指针(ROWID)获取表数据

SQLite 版本差异

架构演进

版本主要架构改进
3.7.0 (2010)引入 WAL 模式,支持更好的并发性能
3.8.0 (2013)改进查询优化器,支持索引覆盖查询
3.9.0 (2015)引入 JSON1 扩展,支持 JSON 数据类型
3.11.0 (2016)改进 VACUUM 命令,支持增量 VACUUM
3.22.0 (2018)引入窗口函数支持
3.31.0 (2020)引入原生 JSON 数据类型
3.44.0 (2023)引入向量数据库支持

配置差异

不同版本的 SQLite 可能支持不同的配置选项和参数:

  • 页大小:早期版本默认 1KB,现在默认 4KB
  • WAL 模式:3.7.0 及以上版本支持
  • JSON 支持:3.9.0 及以上版本支持 JSON1 扩展

SQLite 性能优化原理

查询优化

  • 索引使用:优化器自动选择最优索引
  • 执行计划:生成高效的执行计划
  • 避免全表扫描:通过索引减少数据扫描量
  • 覆盖索引:如果查询只需要索引中的列,直接从索引获取数据,避免访问表数据

写入优化

  • WAL 模式:提高并发写入性能
  • 事务批量处理:减少事务次数,降低磁盘 I/O
  • 延迟写入:允许在事务提交前缓存写入操作
  • 异步 I/O:支持异步写入操作

内存优化

  • 页缓存:减少磁盘 I/O 操作
  • 内存管理:高效的内存分配和释放
  • 缓存大小调整:根据可用内存调整缓存大小

常见问题(FAQ)

Q: SQLite 为什么使用 B 树存储数据?

A: B 树是一种平衡树结构,具有以下优点:

  • 平衡结构,确保查询效率稳定
  • 支持范围查询和排序
  • 高效的插入、删除和更新操作
  • 适合磁盘存储,减少磁盘寻道次数

Q: WAL 模式与回滚日志模式有什么区别?

A: WAL 模式与回滚日志模式的主要区别:

  • 写入位置:WAL 模式将修改写入 WAL 文件,回滚日志模式将原始数据写入回滚日志
  • 并发性能:WAL 模式支持读写并发,回滚日志模式写入时阻塞读取
  • 恢复速度:WAL 模式恢复速度更快
  • 文件大小:WAL 模式需要管理 WAL 文件大小

Q: SQLite 如何确保数据完整性?

A: SQLite 通过以下机制确保数据完整性:

  • 事务处理:确保 ACID 特性
  • 锁机制:确保并发访问安全
  • 完整性约束:支持主键、外键、唯一约束等
  • 崩溃恢复:通过事务日志支持崩溃恢复

Q: 什么是 SQLite 的类型亲和性?

A: SQLite 使用动态类型系统,但列具有类型亲和性,会尝试将存储的值转换为适合的类型。主要的类型亲和性有:

  • TEXT:文本类型
  • NUMERIC:数值类型
  • INTEGER:整数类型
  • REAL:浮点类型
  • BLOB:二进制类型

Q: SQLite 如何处理并发访问?

A: SQLite 通过锁机制和 WAL 模式处理并发访问:

  • 共享锁:允许多个读取操作
  • 排他锁:只允许一个写入操作
  • WAL 模式:支持读写并发,读取操作从数据库文件读取,写入操作写入 WAL 文件

Q: 如何优化 SQLite 的查询性能?

A: 可以通过以下方法优化 SQLite 的查询性能:

  • 创建适当的索引
  • 优化 SQL 语句,避免全表扫描
  • 使用覆盖索引
  • 调整页大小和缓存大小
  • 启用 WAL 模式

总结

SQLite 的架构设计简洁高效,采用分层架构,便于维护和扩展。核心组件包括接口层、编译层、核心层和存储层,各层之间通过清晰的接口交互。

了解 SQLite 的工作原理对于 DBA 至关重要,包括 SQL 语句执行流程、数据存储原理、事务处理机制、并发控制原理和索引工作原理。通过理解这些原理,可以更好地优化数据库性能、排查问题和制定最佳实践。

不同版本的 SQLite 在架构和功能上有所差异,DBA 需要关注版本演进,选择适合应用需求的版本,并根据版本特性进行配置和优化。

SQLite 的性能优化原理包括查询优化、写入优化和内存优化,DBA 可以通过创建适当的索引、优化 SQL 语句、启用 WAL 模式、调整缓存大小等方式提高 SQLite 数据库的性能。