Skip to content

SQLite 核心概念

概述

理解 SQLite 的核心概念是使用和优化 SQLite 数据库的基础。本文档将介绍 SQLite 的基本概念,包括数据库结构、数据组织、事务处理、锁机制等,并结合版本差异和生产运维场景提供实用指导。

数据库与文件结构

数据库文件

SQLite 数据库存储在单个文件中,具有以下特点:

  • 单文件存储:整个数据库(表、索引、触发器等)都存储在一个文件中
  • 跨平台兼容:同一数据库文件可以在不同操作系统上使用
  • 自包含:不依赖于外部文件或目录
  • 大小灵活:可以从几KB到140TB

数据库文件格式

SQLite 数据库文件采用 B 树(B-tree)结构组织数据:

  1. 主文件头:包含数据库的元信息,如版本、页面大小等
  2. B 树页面:存储表数据和索引
  3. 空闲列表:管理未使用的页面
  4. 事务日志:记录事务操作,用于恢复和回滚

数据库页面

数据库文件被分为固定大小的页面(默认 4KB):

  • 页面大小:可以通过 PRAGMA page_size; 查看或设置(2KB-64KB)
  • 页面类型
    • 表 B 树页面
    • 索引 B 树页面
    • 溢出页面(存储大型数据)
    • 空闲页面

版本差异

  • SQLite 3.0+:引入了现代文件格式,支持更大的数据库大小
  • SQLite 3.10.0+:优化了页面分配算法,提高了空间利用率
  • SQLite 3.22.0+:改进了 WAL checkpoint 机制,减少了 I/O 开销

表与数据组织

表结构

表是 SQLite 中存储数据的基本单位,由行和列组成:

  • :定义表的结构,包括列名、数据类型、约束等
  • :实际存储的数据记录
  • 主键:唯一标识表中的每一行
  • 外键:建立表之间的关系(SQLite 3.6.19+ 支持,默认禁用)

表创建示例

sql
-- 启用外键约束
PRAGMA foreign_keys = ON;

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    age INTEGER CHECK (age >= 0),
    email TEXT UNIQUE,
    department_id INTEGER,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

数据存储

  • 行存储:数据按行存储在 B 树中
  • 页面组织:每行数据存储在一个或多个页面中
  • 溢出页面:对于大型数据(如 BLOB),使用溢出页面存储

生产实践

  • 合理设计表结构:遵循范式设计原则,避免冗余数据
  • 使用适当的数据类型:根据实际数据选择合适的数据类型
  • 设计合适的主键:优先使用整数主键,提高性能
  • 添加必要的约束:如 NOT NULL、UNIQUE 等,确保数据完整性

索引

索引概述

索引是提高查询性能的重要机制,通过创建索引可以加速数据的检索。

索引类型

SQLite 支持多种索引类型:

索引类型描述版本支持
B 树索引默认索引类型,适合范围查询和排序所有版本
R 树索引用于地理空间数据查询SQLite 3.1.0+
FTS 索引全文搜索索引SQLite 3.3.0+
表达式索引基于表达式创建的索引SQLite 3.9.0+
部分索引基于表中部分行创建的索引SQLite 3.8.0+

索引创建示例

sql
-- 创建普通索引
CREATE INDEX idx_users_name ON users(name);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- 创建复合索引
CREATE INDEX idx_users_department_age ON users(department_id, age);

-- 创建表达式索引
CREATE INDEX idx_users_name_lower ON users(lower(name));

-- 创建部分索引
CREATE INDEX idx_users_active ON users(name) WHERE active = 1;

索引工作原理

  • 索引使用 B 树结构存储索引键和对应的行指针
  • 查询时,数据库引擎首先在索引中查找匹配的键,然后通过行指针找到对应的行数据
  • 索引可以减少磁盘 I/O,提高查询速度

生产实践

  • 只创建必要的索引:索引会增加写操作的开销
  • 创建复合索引:对于多列查询,创建复合索引
  • 避免过度索引:过多的索引会降低写操作性能
  • 定期优化索引:使用 ANALYZE 命令优化索引
  • 考虑覆盖索引:包含查询所需的所有列,避免回表查询

事务

事务概述

事务是一组 SQL 操作,要么全部成功,要么全部失败,确保数据的完整性和一致性。

ACID 特性

SQLite 完全支持 ACID 特性:

  • 原子性(Atomicity):事务要么完全执行,要么完全不执行
  • 一致性(Consistency):事务执行前后数据库保持一致状态
  • 隔离性(Isolation):多个事务并发执行时相互隔离
  • 持久性(Durability):事务提交后数据永久保存

事务隔离级别

SQLite 支持多种事务隔离级别:

隔离级别描述
SERIALIZABLE默认级别,完全隔离,最高安全性
READ COMMITTED只能读取已提交的数据
READ UNCOMMITTED允许读取未提交的数据(脏读)
REPEATABLE READ同一事务中多次读取同一数据结果一致

事务控制语句

sql
-- 开始事务
BEGIN TRANSACTION;
BEGIN;

-- 提交事务
COMMIT TRANSACTION;
COMMIT;

-- 回滚事务
ROLLBACK TRANSACTION;
ROLLBACK;

-- 保存点
SAVEPOINT sp1;
ROLLBACK TO sp1;
RELEASE sp1;

生产实践

  • 使用事务:对于多个相关操作,使用事务确保数据完整性
  • 优化事务大小:避免长时间运行的事务,减少锁持有时间
  • 合理设置隔离级别:根据需求选择合适的隔离级别
  • 使用 BEGIN IMMEDIATE:对于写入操作,使用 BEGIN IMMEDIATE 减少锁竞争

锁机制

锁类型

SQLite 使用多种锁类型来控制并发访问:

锁类型描述
SHARED(共享锁)用于读取操作,多个连接可以同时持有
RESERVED(保留锁)表示连接想要写入数据,只能有一个连接持有
PENDING(挂起锁)等待所有共享锁释放,准备升级为排他锁
EXCLUSIVE(排他锁)用于写入操作,只有一个连接可以持有

锁升级过程

  1. 读取操作:获取 SHARED 锁
  2. 写入操作
    • 首先获取 RESERVED 锁
    • 等待所有 SHARED 锁释放,获取 PENDING 锁
    • 获取 EXCLUSIVE 锁,执行写入操作
  3. 操作完成:释放锁

WAL 模式下的锁机制

在 WAL(Write-Ahead Logging)模式下,锁机制有所不同:

  • 读取操作:不需要获取 SHARED 锁,可以直接读取
  • 写入操作:获取 WAL 锁,追加日志记录
  • checkpoint 操作:将 WAL 日志中的数据写入数据库文件

生产实践

  • 启用 WAL 模式:提高并发性能,减少锁竞争
  • 优化事务大小:减少长时间运行的事务
  • 避免频繁的提交和回滚:减少锁操作开销
  • 使用连接池:管理数据库连接,减少连接建立和关闭的开销

WAL 模式

WAL 模式概述

Write-Ahead Logging(WAL)是 SQLite 3.7.0 引入的一种日志模式,提供更好的并发性能。

WAL 模式工作原理

  1. 写入操作:将修改写入 WAL 日志文件,而不是直接修改数据库文件
  2. 读取操作:从数据库文件和 WAL 日志中读取数据
  3. Checkpoint:定期将 WAL 日志中的数据写入数据库文件

WAL 模式优势

  • 提高并发性能:读操作和写操作可以同时进行
  • 减少磁盘 I/O:写操作只需要追加日志,不需要修改数据文件
  • 提高可靠性:日志文件提供了额外的恢复机制
  • 减少锁竞争:降低了写入操作对读取操作的影响

启用 WAL 模式

sql
PRAGMA journal_mode = WAL;

WAL 文件

  • WAL 日志文件:与数据库文件同名,扩展名为 .wal
  • 共享内存文件:用于协调多个连接,扩展名为 .shm
  • 自动管理:SQLite 会自动管理 WAL 文件的大小和生命周期

版本差异

  • SQLite 3.7.0+:引入 WAL 模式
  • SQLite 3.8.0+:优化了 WAL 性能
  • SQLite 3.11.0+:改进了 WAL checkpoint 算法
  • SQLite 3.22.0+:进一步优化了 WAL 性能,减少了 I/O 开销

生产实践

  • 定期手动 checkpoint:对于写入密集型应用,可以定期执行 PRAGMA wal_checkpoint;
  • 调整 checkpoint 模式:使用 PRAGMA wal_autocheckpoint = 1000; 设置自动 checkpoint 阈值
  • 监控 WAL 文件大小:确保 WAL 文件不会过大影响性能
  • 备份 WAL 文件:备份数据库时,同时备份 WAL 文件以保证数据完整性

触发器

触发器概述

触发器是在特定事件(如 INSERT、UPDATE、DELETE)发生时自动执行的 SQL 语句。

触发器类型

触发器类型描述
BEFORE 触发器在事件发生前执行
AFTER 触发器在事件发生后执行
INSTEAD OF 触发器替代原始操作执行

触发器创建示例

sql
-- 创建 AFTER 触发器
CREATE TRIGGER update_users_updated_at
AFTER UPDATE ON users
FOR EACH ROW
BEGIN
    UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
END;

-- 创建 BEFORE 触发器
CREATE TRIGGER validate_user_age
BEFORE INSERT ON users
FOR EACH ROW
WHEN NEW.age < 0
BEGIN
    SELECT RAISE(ABORT, 'Age cannot be negative');
END;

-- 创建 INSTEAD OF 触发器
CREATE TRIGGER insert_into_view
INSTEAD OF INSERT ON user_view
FOR EACH ROW
BEGIN
    INSERT INTO users (name, age) VALUES (NEW.name, NEW.age);
    INSERT INTO user_profiles (user_id, bio) VALUES (last_insert_rowid(), NEW.bio);
END;

生产实践

  • 谨慎使用触发器:触发器会增加数据库的复杂性和维护成本
  • 避免在触发器中执行复杂操作:复杂操作会影响性能
  • 测试触发器逻辑:确保触发器不会导致意外的副作用
  • 文档化触发器:清晰记录触发器的目的和行为

视图

视图概述

视图是基于查询结果的虚拟表,不存储实际数据,只存储查询定义。

视图优势

  • 简化查询:将复杂查询封装为视图,简化应用程序代码
  • 数据安全性:限制用户只能访问视图中的特定列
  • 逻辑数据独立:应用程序可以使用视图,而不受底层表结构变化的影响

视图创建示例

sql
-- 创建简单视图
CREATE VIEW active_users AS
SELECT id, name, email FROM users WHERE active = 1;

-- 创建复杂视图
CREATE VIEW user_details AS
SELECT
    u.id,
    u.name,
    u.email,
    d.name AS department_name,
    COUNT(o.id) AS order_count
FROM users u
LEFT JOIN departments d ON u.department_id = d.id
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

-- 创建只读视图
CREATE VIEW read_only_view AS
SELECT id, name FROM users
WITH CHECK OPTION;

生产实践

  • 使用视图简化复杂查询:将频繁使用的复杂查询封装为视图
  • 使用视图加强数据安全性:限制用户只能访问视图中的特定列
  • 避免在视图中使用复杂计算:复杂计算会影响视图性能
  • 定期刷新物化视图:对于需要频繁查询的视图,可以考虑使用物化视图

存储过程与函数

用户定义函数(UDF)

SQLite 允许创建自定义 SQL 函数,扩展 SQLite 的功能。

函数创建示例

python
-- 使用 Python 创建自定义函数
import sqlite3
import math

conn = sqlite3.connect('test.db')
cursor = conn.cursor()

-- 创建自定义函数
def square(x):
    return x * x

-- 注册函数
conn.create_function('square', 1, square)

-- 使用自定义函数
cursor.execute("SELECT square(5)")
print(cursor.fetchone())  -- 输出: (25,)

-- 创建聚合函数
import sqlite3
from statistics import mean

conn = sqlite3.connect('test.db')
cursor = conn.cursor()

-- 创建聚合函数
class Average:
    def __init__(self):
        self.values = []
    
    def step(self, value):
        self.values.append(value)
    
    def finalize(self):
        return mean(self.values) if self.values else None

-- 注册聚合函数
conn.create_aggregate('avg_custom', 1, Average)

-- 使用聚合函数
cursor.execute("SELECT avg_custom(age) FROM users")
print(cursor.fetchone())

生产实践

  • 合理使用自定义函数:自定义函数可以扩展 SQLite 的功能,但会影响性能
  • 优化自定义函数:确保自定义函数高效执行
  • 测试自定义函数:确保自定义函数返回正确的结果
  • 文档化自定义函数:清晰记录自定义函数的用途和参数

事务日志

日志模式

SQLite 支持多种事务日志模式:

日志模式描述版本支持
DELETE传统模式,事务提交后删除日志文件所有版本
TRUNCATE事务提交后截断日志文件SQLite 3.6.0+
PERSIST事务提交后保留日志文件,但标记为已完成SQLite 3.6.0+
MEMORY将日志存储在内存中SQLite 3.6.0+
WALWrite-Ahead Logging 模式SQLite 3.7.0+

日志模式设置

sql
PRAGMA journal_mode = WAL;

生产实践

  • 选择合适的日志模式:根据应用需求选择合适的日志模式
  • 使用 WAL 模式:对于并发要求高的应用,使用 WAL 模式
  • 监控日志文件:确保日志文件不会占用过多磁盘空间
  • 定期备份日志文件:备份数据库时,同时备份日志文件

常见问题

什么是 SQLite 的 B 树结构?

B 树是一种平衡树数据结构,用于高效地存储和检索数据。SQLite 使用 B 树存储表数据和索引,每个节点包含多个键值对,便于范围查询和排序。

WAL 模式有什么优势?

WAL(Write-Ahead Logging)模式具有以下优势:

  • 提高并发性能:读操作和写操作可以同时进行
  • 减少磁盘 I/O:写操作只需要追加日志,不需要修改数据文件
  • 提高可靠性:日志文件提供了额外的恢复机制
  • 减少锁竞争:降低了写入操作对读取操作的影响

如何选择合适的索引类型?

选择索引类型时需要考虑:

  • 数据类型:B 树索引适合大多数数据类型
  • 查询类型:范围查询适合 B 树索引,全文搜索适合 FTS 索引
  • 数据分布:对于分布均匀的数据,索引效果更好
  • 版本支持:确保目标 SQLite 版本支持所选索引类型

如何优化 SQLite 的锁机制?

可以通过以下方式优化锁机制:

  • 启用 WAL 模式,提高并发性能
  • 优化事务大小,减少长时间运行的事务
  • 避免频繁的提交和回滚
  • 使用连接池管理数据库连接
  • 合理设置事务隔离级别

如何备份使用 WAL 模式的 SQLite 数据库?

备份 WAL 模式的 SQLite 数据库时,需要:

  1. 执行 PRAGMA wal_checkpoint; 确保所有数据都写入数据库文件
  2. 备份数据库文件
  3. 同时备份 WAL 文件和共享内存文件(可选,用于恢复未 checkpoint 的数据)

如何监控 SQLite 数据库的性能?

可以通过以下方式监控 SQLite 数据库的性能:

  • 使用 EXPLAIN QUERY PLAN 分析查询执行计划
  • 使用 PRAGMA optimize; 优化数据库
  • 监控数据库文件大小和增长趋势
  • 监控查询响应时间
  • 使用 SQLite 的内置统计信息(如 PRAGMA stats;

生产实践总结

数据库设计

  • 合理设计表结构,遵循范式设计原则
  • 使用适当的数据类型,避免过度使用大型数据类型
  • 设计合适的主键,优先使用整数主键
  • 添加必要的约束,确保数据完整性

索引优化

  • 只创建必要的索引,避免过度索引
  • 为频繁查询的列创建索引
  • 使用复合索引优化多列查询
  • 考虑使用覆盖索引,避免回表查询
  • 定期使用 ANALYZE 命令优化索引

事务管理

  • 使用事务确保数据完整性
  • 优化事务大小,减少长时间运行的事务
  • 合理设置事务隔离级别
  • 使用 BEGIN IMMEDIATE 减少锁竞争

并发控制

  • 启用 WAL 模式,提高并发性能
  • 优化锁机制,减少锁竞争
  • 使用连接池管理数据库连接
  • 避免频繁的提交和回滚

性能优化

  • 启用 WAL 模式
  • 调整页面大小,根据数据特点优化
  • 使用预编译语句,减少 SQL 解析开销
  • 优化查询语句,避免全表扫描
  • 定期清理和优化数据库,使用 VACUUM 命令

备份与恢复

  • 定期备份数据库文件
  • 备份 WAL 模式下的 WAL 文件和共享内存文件
  • 测试恢复流程,确保备份有效
  • 使用事务确保备份的一致性

总结

理解 SQLite 的核心概念是使用和优化 SQLite 数据库的基础。本文档介绍了 SQLite 的数据库文件结构、表与数据组织、索引、事务、锁机制、WAL 模式、触发器、视图等核心概念,并结合版本差异和生产运维场景提供了实用指导。

在实际应用中,需要根据具体需求和场景,灵活运用这些概念,设计高效、可靠的数据库解决方案。通过遵循最佳实践,可以提高 SQLite 数据库的性能、可靠性和安全性,满足生产环境的需求。