当前位置: 首页 > news >正文

SQLite的并发问题

   转载自:C# 下 SQLite 并发操作与锁库问题的 5 种解决方案_51CTO博客_sqlcipher c#

SQLite是轻量级的数据库,可用于嵌入式设备,仅需几百KB的内存即可工作,整个数据库存储在单一文件中,便于管理,迁移,备份。无需繁琐配置。

轻量高性能必然带来一定的局限,这次遇到的就是SQLite数据库的并发操作问题

关键点:在多线程或多进程并发访问的场景下,同一时刻仅允许单个线程进行写入操作。

现象:database is locked 错误

问题描述:在一个线程正在执行写入操作的过程中,另一个线程的写入请求只能被迫等待,等待时间过长超过系统预设的超时时间(通常5s可更改)就会触发此错误。

解决方案

1. 读写锁

2. 事务

3. WAL模式

4. 连接池

5. 多线程模式

详细说明

1. 读写锁

确保资源不被争抢导致崩溃

读写锁
using System.Threading;public class SqliteDataManager
{//ReaderWriterLockSlim类提供读写锁private static ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim;private static string _connString = "Data Source=mydb.db;Version=3;";// 读操作(允许多线程并发)public void ReadData{_rwLock.EnterReadLock;  // 获取读锁try{using (var conn = new SQLiteConnection(_connString)){conn.Open;var cmd = conn.CreateCommand;cmd.CommandText = "SELECT * FROM MyTable";using (var reader = cmd.ExecuteReader){while (reader.Read) { /* 读取数据 */ }}}}finally{_rwLock.ExitReadLock;  // 必须在finally中释放}}// 写操作(独占锁)public void WriteData(string value){_rwLock.EnterWriteLock;  // 获取写锁try{using (var conn = new SQLiteConnection(_connString)){conn.Open;var cmd = conn.CreateCommand;cmd.CommandText = "INSERT INTO MyTable VALUES (@Value)";cmd.Parameters.AddWithValue("@Value", value);cmd.ExecuteNonQuery;}}finally{_rwLock.ExitWriteLock;  // 必须释放}}
}

可升级为写锁的读锁,可减少锁竞争,避免重复加锁

可升级为写锁的读锁
public sealed class SqliteConnectionManager
{private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim;private static SQLiteConnection _conn;public static SQLiteConnection GetConnection{_lock.EnterUpgradeableReadLock;  // 可升级为写锁的读锁try{if (_conn == null){_lock.EnterWriteLock;  // 升级为写锁try{if (_conn == null){_conn = new SQLiteConnection("Data Source=mydb.db");_conn.Open;}}finally { _lock.ExitWriteLock; }}return _conn;}finally { _lock.ExitUpgradeableReadLock; }}
}

具体使用

查看代码
using System;
using System.Data.SQLite;
using System.Threading;class DatabaseManager
{private static SQLiteConnection _connection;private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();public static SQLiteConnection GetConnection(){if(_connection==null){_lock.EnterWriteLock;try{if(_connection == null){_connection=new SQLiteConnection("Data Source=database.db");}}finally{_lock.ExitWriteLock();}return _connection;}public static void InsertUser(string name) { var connection = GetConnection();_lock.EnterWriteLock(); try { //写锁中使用了事务using (var transaction = connection.BeginTransaction()) { try { using (var command = new SQLiteCommand(connection)) { command.CommandText = "INSERT INTO Users (Name) VALUES (@name)";                        command.Parameters.AddWithValue("@name", name); command.ExecuteNonQuery(); } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); } } } finally { _lock.ExitWriteLock(); }}public static void SelectUsers() { var connection = GetConnection(); _lock.EnterReadLock(); try { using (var command = new SQLiteCommand("SELECT * FROM Users", connection)) { using (var reader = command.ExecuteReader()) { while (reader.Read()) { Console.WriteLine(reader["Name"]); } } } } finally { _lock.ExitReadLock(); }}}}
}

2.事务

保障数据完整性,事务具有原子性,内部的所有操作要么成功要么一起失败并回滚到插入之前的状态。一般只在读的时候使用事务。

在写入时使用事务
public static void InsertUser(string name) 
{ var connection = GetConnection(); using (var transaction = connection.BeginTransaction()) { try { using (var command = new SQLiteCommand(connection)) { command.CommandText = "INSERT INTO Users (Name) VALUES (@name)"; command.Parameters.AddWithValue("@name", name); command.ExecuteNonQuery(); } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); }} 
}

3. WAL模式

Write-Ahead Logging

_connection = new SQLiteConnection("Data Source=database.db;Journal Mode=WAL");

在连接字符串中,加入Journal Mode =WAL即可开启WAL模式,此模式可读写并行

原理:将写入的数据暂存到WAL文件中,在这个过程中主数据库文件依然可以对外提供读服务,当文件满足一定条件(如WAL文件大小达到阈值、事务提交等)时,数据库引擎在后台将WAL文件中的数据合并到主数据库文件中。

4. 连接池

_connection = new SQLiteConnection("Data Source=database.db;Max Pool Size=100;Pooling=True");

预先创建一定数量的数据库连接,减少连接创建与销毁开销,但需要合理配置连接池参数,否则可能出现连接泄露或资源浪费

5 多线程模式(即综合使用)

http://www.hskmm.com/?act=detail&tid=15659

相关文章:

  • 域渗透靶场-vulntarget-a综合靶场
  • 数组和链表读取、插入、删除以及查找的区别
  • day 09 课程
  • 在K8S中,日志分析工具有哪些可以与K8S集群通讯?
  • 在K8S中,网络通信模式有哪些?
  • 一文教你搞定PASS 2025:样本量计算神器安装到使用全流程
  • React 18.2中采用React Router 6.4
  • 题解:AT_abc257_h [ABC257Ex] Dice Sum 2
  • ClickHouse UPDATE 机制详解 - 若
  • ClickHouse index_granularity 详解 - 若
  • P13885 [蓝桥杯 2023 省 Java/Python A] 反异或 01 串
  • clickhouse轻量级更新 - 若
  • 西电PCB设计指南第3章学习笔记
  • Vitrualbox、kali、metaspolitable2下载安装
  • LazyLLM端到端实战:用RAG+Agent实现自动出题与学习计划的个性化学习助手智能体
  • 补充图
  • 【阿里云事件总线】域名+邮件推送+事件总线=实现每天定时邮件!
  • llm入门环境
  • FLASH空间划分/存储数据至指定CODEFLASH位置
  • SOOMAL 降噪数据表
  • 案例分享|借助IronPDF IronOCR,打造医疗等行业的智能化解决方案
  • ClickHouse UPDATE 操作问题解决方案 - 若
  • 利用 Milvus + RustFS,快速打造一个 RAG!
  • Docker 私有镜像仓库 Harbor 安装部署带签名认证
  • ARC180 做题记
  • 借助Aspose.HTML控件,使用 Python 编辑 HTML
  • 微前端 micro-app 在vue 中的路由跳转问题
  • 1. 设计模式--工厂办法模式
  • 汽车视频总线采集过程中,如何兼顾响应速度和可靠性?
  • P8865 [NOIP2022] 种花