外观
SQLite 并发控制
并发控制概述
并发控制是数据库管理系统的核心功能之一,用于协调多个并发事务对共享数据的访问。SQLite 采用多种机制实现并发控制,包括锁机制、预写日志(WAL)模式和事务隔离级别。
SQLite 锁机制
锁类型
SQLite 使用五种不同级别的锁,从低到高依次为:
- SHARED(共享锁):用于读取操作,允许多个事务同时读取数据库
- RESERVED(保留锁):表示事务准备写入数据库,此时仍允许其他事务读取
- PENDING(挂起锁):表示事务即将写入数据库,不允许新的读取事务开始,但已有的读取事务可以继续
- EXCLUSIVE(排他锁):用于写入操作,完全锁定数据库,不允许其他任何事务访问
- UNLOCKED(未锁定):表示事务未持有任何锁
锁升级过程
事务执行过程中,锁会按照以下顺序自动升级:
- 读取操作开始时,获取 SHARED 锁
- 写入操作开始前,升级到 RESERVED 锁
- 实际写入数据前,升级到 PENDING 锁
- 等待所有现有读取事务完成后,升级到 EXCLUSIVE 锁
- 事务提交或回滚后,释放所有锁
锁状态查看
可以使用 PRAGMA locking_mode 查看和设置数据库的锁定模式:
sql
-- 查看当前锁定模式
PRAGMA locking_mode;
-- 设置为 NORMAL 锁定模式(默认)
PRAGMA locking_mode = NORMAL;
-- 设置为 EXCLUSIVE 锁定模式
PRAGMA locking_mode = EXCLUSIVE;WAL 模式
WAL 模式概述
预写日志(Write-Ahead Logging,WAL)是 SQLite 3.7.0 引入的一种新的日志模式,显著提高了并发性能。在 WAL 模式下,写入操作不会直接修改数据库文件,而是先写入一个单独的 WAL 文件。
WAL 模式的优势
- 读-写并发:读取操作可以在写入操作进行时继续,不需要等待写入完成
- 写-写序列化:多个写入操作仍然是串行的,但它们对读取操作的影响最小化
- 更快的写入速度:写入操作只需要追加到 WAL 文件,不需要修改数据库文件的多个页面
- 更好的崩溃恢复:WAL 文件包含完整的事务日志,恢复过程更可靠
启用 WAL 模式
sql
-- 启用 WAL 模式
PRAGMA journal_mode = WAL;
-- 查看当前日志模式
PRAGMA journal_mode;WAL 模式相关参数
| 参数 | 描述 | 默认值 |
|---|---|---|
wal_autocheckpoint | 自动检查点的 WAL 文件大小阈值(页) | 1000 |
wal_checkpoint | 手动执行检查点 | - |
wal_synchronous | WAL 模式下的同步级别 | FULL |
检查点操作
检查点是将 WAL 文件中的修改合并到数据库文件的过程:
sql
-- 执行检查点
PRAGMA wal_checkpoint;
-- 执行完全检查点(等待所有读取者完成)
PRAGMA wal_checkpoint(FULL);
-- 执行截断检查点(合并并截断 WAL 文件)
PRAGMA wal_checkpoint(TRUNCATE);
-- 执行 PASSIVE 检查点(不等待任何操作)
PRAGMA wal_checkpoint(PASSIVE);事务隔离级别
SQLite 支持四种事务隔离级别,用于控制并发事务之间的可见性:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
设置隔离级别
sql
-- 设置为 READ COMMITTED 隔离级别
PRAGMA read_uncommitted = 0;
PRAGMA journal_mode = WAL;
-- 设置为 READ UNCOMMITTED 隔离级别(不推荐)
PRAGMA read_uncommitted = 1;并发控制策略
1. 选择合适的日志模式
- DELETE 模式:传统日志模式,写入时锁定数据库,适合单用户或低并发场景
- WAL 模式:现代日志模式,支持高并发读-写操作,适合多用户场景
2. 优化事务设计
- 保持事务简短:减少锁持有时间
- 避免长时间读事务:特别是在 DELETE 日志模式下
- 使用批量操作:减少事务次数和锁升级频率
3. 合理设置隔离级别
- 对于需要最高数据一致性的场景,使用默认的 SERIALIZABLE 隔离级别
- 对于高并发场景,如果业务允许,可以考虑使用 READ COMMITTED 隔离级别
4. 优化查询性能
- 创建合适的索引,减少查询执行时间
- 避免全表扫描,减少锁持有时间
- 使用
ANALYZE命令更新统计信息,帮助查询优化器生成更好的执行计划
版本差异
SQLite 3.7.0 及以上
- 引入 WAL 模式,支持读-写并发
- 改进了锁机制,减少锁争用
SQLite 3.8.0 及以上
- 优化了 WAL 模式的性能
- 支持
wal_autocheckpoint参数,自动管理检查点
SQLite 3.11.0 及以上
- 引入
wal_checkpoint命令,支持更灵活的检查点操作 - 改进了锁升级算法,减少死锁可能性
SQLite 3.22.0 及以上
- 支持
PRAGMA wal_synchronous参数,允许调整 WAL 模式下的同步级别 - 优化了 WAL 文件的管理,减少磁盘空间使用
并发问题与解决方案
1. 锁争用
症状:事务执行缓慢,出现大量锁等待
解决方案:
- 切换到 WAL 模式
- 保持事务简短
- 优化查询,减少锁持有时间
- 使用批量操作
- 考虑分片或分区(如果数据量很大)
2. 死锁
症状:事务陷入无限等待,无法完成
解决方案:
- 保持事务简短
- 按照固定顺序访问资源
- 避免嵌套事务
- 设置合理的超时机制
3. 写入饥饿
症状:写入事务长时间等待,无法获得排他锁
解决方案:
- 使用 WAL 模式,减少读-写冲突
- 合理设置
wal_autocheckpoint参数 - 定期手动执行检查点
- 考虑限制同时进行的读取事务数量
4. WAL 文件过大
症状:WAL 文件大小持续增长,占用过多磁盘空间
解决方案:
- 减小
wal_autocheckpoint参数值 - 定期手动执行检查点
- 使用
PRAGMA wal_checkpoint(TRUNCATE)截断 WAL 文件 - 考虑使用
VACUUM命令重组数据库
常见问题(FAQ)
Q: WAL 模式下,读取操作会锁定数据库吗?
A: 不会。在 WAL 模式下,读取操作直接从数据库文件读取,不需要锁定 WAL 文件,因此可以与写入操作并发执行。
Q: 如何确定是否应该使用 WAL 模式?
A: 以下情况适合使用 WAL 模式:
- 有大量并发读-写操作
- 读取操作远多于写入操作
- 对写入性能要求较高
- 需要支持多个连接同时访问数据库
Q: WAL 模式会影响数据安全性吗?
A: 不会。WAL 模式同样保证 ACID 特性,只是日志的实现方式不同。可以通过调整 wal_synchronous 参数来平衡性能和安全性。
Q: 多个连接可以同时写入 SQLite 数据库吗?
A: 在任何模式下,SQLite 都只允许一个连接同时写入数据库。但在 WAL 模式下,写入操作对读取操作的影响最小化,多个读取连接可以在写入操作进行时继续工作。
Q: 如何监控锁争用情况?
A:
- 使用
PRAGMA busy_timeout设置超时时间,超时后会返回错误 - 使用 SQLite 的内置性能监控功能
- 检查应用程序日志中的锁等待相关错误
- 使用第三方监控工具,如 SQLite Expert 或 DB Browser for SQLite
生产运维建议
- 默认使用 WAL 模式:对于大多数应用场景,WAL 模式提供更好的并发性能
- 合理设置 WAL 参数:根据业务需求调整
wal_autocheckpoint和wal_synchronous参数 - 定期执行检查点:特别是在写入频繁的场景下
- 监控 WAL 文件大小:设置告警,防止 WAL 文件过大
- 测试并发性能:在生产环境部署前,进行充分的并发性能测试
- 考虑使用连接池:对于高并发场景,使用连接池管理数据库连接
- 备份 WAL 文件:确保 WAL 文件与数据库文件一起备份,以便在恢复时使用
通过合理配置和优化,可以充分发挥 SQLite 在并发场景下的性能优势,满足大多数应用的需求。
