当前位置: 首页 > 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.rkmt.cn/news/10796.html

相关文章:

  • day 09 课程
  • Jetpack Room 从入门到精通 - 实践
  • LazyLLM端到端实战:用RAG+Agent实现自动出题与学习计划的个性化学习助手智能体
  • FLASH空间划分/存储数据至指定CODEFLASH位置
  • 深入解析:【C语言代码】数组排序
  • 利用 Milvus + RustFS,快速打造一个 RAG!
  • 微前端 micro-app 在vue 中的路由跳转问题
  • 1. 设计模式--工厂办法模式
  • traefik 反向代理 + IdentityServer4
  • Word-通过宏格式化文档中的表格和图片
  • 深入解析:find_code 插件 react_vite
  • SAP BAPI_PR_CREATE 创建采购申请(含自定义字段)
  • NCCL论文阅读
  • 皇牌空战7豪华版DLC补丁
  • BeanUtils中的copyProperties方法使用和分析
  • WoTerm、WindTerm及putty的性能测试对比
  • Python - csv.writer()
  • BM25 关键词检索算法
  • 55.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--实现手机邮箱登录 - 实践
  • 详细介绍:Xilinx系列FPGA实现12G-SDI音视频编解码,支持4K60帧分辨率,提供2套工程源码和技术支持
  • 使用 VMware Workstation 安装 CentOS-7 虚拟机
  • uv Python安装镜像加速
  • AT_arc167_c [ARC167C] MST on Line++
  • CentOS操作系统
  • window系统下使用二进制包安装MySQL数据库
  • 在Vona ORM中实现多数据库/多数据源
  • sql over()函数使用
  • 小柏实战学习Liunx(图文教程三十二)
  • VPX处理板设计原理图:9-基于DSP TMS320C6678+FPGA XC7V690T的6U VPX信号处理卡 C6678板卡, XC7VX690T板卡, VPX处理板
  • VitePress 添加友链界面