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、*、FROM、users、WHERE、id、=、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 数据库的性能。