编辑
2025-09-28
C#
0

目录

常见的锁库场景
基本解决方案
使用using语句确保连接释放
设置超时时间
完整例子
高级解决方案
实现重试机制
最佳实践总结
注意事项

SQLite是一个轻量级的关系型数据库,在并发访问时可能会遇到数据库锁定的问题。本文将详细介绍如何在C#中正确处理SQLite的并发操作,避免锁库问题。

常见的锁库场景

SQLite在以下情况下容易发生锁库:

  • 多线程同时写入数据
  • 长时间事务占用连接
  • 读写操作混合执行

基本解决方案

使用using语句确保连接释放

C#
public void SafeInsert(string data) { using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (var command = connection.CreateCommand()) { command.CommandText = "INSERT INTO TestTable (Data) VALUES (@data)"; command.Parameters.AddWithValue("@data", data); command.ExecuteNonQuery(); } } }

设置超时时间

C#
public class DatabaseManager { private readonly string _connectionString; public DatabaseManager(string dbPath) { var builder = new SQLiteConnectionStringBuilder { DataSource = dbPath, DefaultTimeout = 30, // 设置默认超时时间为30秒 BusyTimeout = 5000 // 设置忙等待超时时间为5秒 }; _connectionString = builder.ConnectionString; } }

完整例子

C#
using System; using System.Collections.Generic; using System.Data.SQLite; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AppSqlite { public class DatabseManager : IDisposable { private readonly string _connectionString; private readonly string _dbPath; public DatabaseManager(string dbPath) { _dbPath = dbPath; // 构建连接字符串,添加一些常用配置 var builder = new SQLiteConnectionStringBuilder { DataSource = dbPath, Version = 3, DefaultTimeout = 30, BusyTimeout = 5000, JournalMode = SQLiteJournalModeEnum.Wal, // 使用WAL模式提高并发性能 Pooling = true, CacheSize = 2000, // 缓存大小 SyncMode = SynchronizationModes.Normal // 同步模式 }; _connectionString = builder.ConnectionString; InitializeDatabase(); } // 初始化数据库 private void InitializeDatabase() { // 如果数据库文件不存在,则创建 if (!File.Exists(_dbPath)) { SQLiteConnection.CreateFile(_dbPath); } using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (var command = connection.CreateCommand()) { // 创建测试表 command.CommandText = @" CREATE TABLE IF NOT EXISTS TestTable ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Data TEXT NOT NULL, CreateTime DATETIME DEFAULT CURRENT_TIMESTAMP )"; command.ExecuteNonQuery(); // 创建索引 command.CommandText = "CREATE INDEX IF NOT EXISTS IX_TestTable_Data ON TestTable(Data)"; command.ExecuteNonQuery(); } } } // 安全的插入操作 public void SafeInsert(string data) { if (string.IsNullOrEmpty(data)) { throw new ArgumentNullException(nameof(data)); } try { using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (var transaction = connection.BeginTransaction()) { try { using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = "INSERT INTO TestTable (Data) VALUES (@data)"; command.Parameters.AddWithValue("@data", data); command.ExecuteNonQuery(); } transaction.Commit(); } catch { transaction.Rollback(); throw; } } } } catch (SQLiteException ex) { // 处理特定的SQLite异常 switch (ex.ResultCode) { case SQLiteErrorCode.Busy: throw new Exception("数据库正忙,请稍后重试", ex); case SQLiteErrorCode.Locked: throw new Exception("数据库被锁定,请稍后重试", ex); case SQLiteErrorCode.IoErr: throw new Exception("数据库IO错误", ex); default: throw new Exception("数据库操作失败", ex); } } } // 批量插入操作 public void BatchInsert(IEnumerable<string> dataList) { try { using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (var transaction = connection.BeginTransaction()) { try { using (var command = connection.CreateCommand()) { command.Transaction = transaction; command.CommandText = "INSERT INTO TestTable (Data) VALUES (@data)"; var parameter = command.Parameters.Add("@data", System.Data.DbType.String); foreach (var data in dataList) { parameter.Value = data; command.ExecuteNonQuery(); } } transaction.Commit(); } catch { transaction.Rollback(); throw; } } } } catch (Exception ex) { throw new Exception("批量插入失败", ex); } } // 查询数据 public List<(long Id, string Data, DateTime CreateTime)> QueryAll() { var results = new List<(long Id, string Data, DateTime CreateTime)>(); using (var connection = new SQLiteConnection(_connectionString)) { connection.Open(); using (var command = connection.CreateCommand()) { command.CommandText = "SELECT Id, Data, CreateTime FROM TestTable"; using (var reader = command.ExecuteReader()) { while (reader.Read()) { results.Add(( reader.GetInt64(0), reader.GetString(1), reader.GetDateTime(2) )); } } } } return results; } public void Dispose() { // 在这里可以添加清理代码 GC.SuppressFinalize(this); } } }

调用

C#
public static async Task Main() { var dbPath = "test.db"; if (File.Exists(dbPath)) { File.Delete(dbPath); } using (var dbManager = new DatabaseManager(dbPath)) { // 测试单条插入 Console.WriteLine("测试单条插入..."); dbManager.SafeInsert("Test Data 1"); // 测试批量插入 Console.WriteLine("测试批量插入..."); var batchData = Enumerable.Range(1, 5) .Select(i => $"Batch Data {i}") .ToList(); dbManager.BatchInsert(batchData); // 测试并发插入 Console.WriteLine("测试并发插入..."); var tasks = Enumerable.Range(1, 10) .Select(i => Task.Run(() => dbManager.SafeInsert($"Concurrent Data {i}"))) .ToArray(); await Task.WhenAll(tasks); // 查询并显示所有数据 Console.WriteLine("\n查询所有数据:"); var results = dbManager.QueryAll(); foreach (var (id, data, createTime) in results) { Console.WriteLine($"Id: {id}, Data: {data}, CreateTime: {createTime}"); } } Console.WriteLine("\n所有测试完成!"); }

image.png

高级解决方案

实现重试机制

C#
using System; using System.Data.SQLite; using System.Collections.Generic; using System.Threading.Tasks; using System.IO; public class DatabaseRetryManager : IDisposable { private readonly string _connectionString; private const int MaxRetries = 3; private const int RetryDelayMs = 1000; public DatabaseRetryManager(string dbPath) { var builder = new SQLiteConnectionStringBuilder { DataSource = dbPath, Version = 3, DefaultTimeout = 30, BusyTimeout = 5000, JournalMode = SQLiteJournalModeEnum.Wal, Pooling = true }; _connectionString = builder.ConnectionString; InitializeDatabase(dbPath); } private void InitializeDatabase(string dbPath) { if (!File.Exists(dbPath)) { SQLiteConnection.CreateFile(dbPath); } using var connection = new SQLiteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); // 创建示例表 command.CommandText = @" CREATE TABLE IF NOT EXISTS Users ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT NOT NULL, Email TEXT UNIQUE, CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS Logs ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Message TEXT NOT NULL, LogLevel TEXT NOT NULL, Timestamp DATETIME DEFAULT CURRENT_TIMESTAMP );"; command.ExecuteNonQuery(); } // 基础重试方法 public async Task ExecuteWithRetryAsync(Func<SQLiteConnection, Task> action) { for (int i = 0; i < MaxRetries; i++) { try { using (var connection = new SQLiteConnection(_connectionString)) { await connection.OpenAsync(); await action(connection); return; } } catch (SQLiteException ex) when (IsTransientError(ex)) { if (i == MaxRetries - 1) throw; await LogRetryAttemptAsync(ex, i + 1); await Task.Delay(RetryDelayMs * (i + 1)); } } } // 带返回值的重试方法 public async Task<T> ExecuteWithRetryAsync<T>(Func<SQLiteConnection, Task<T>> action) { for (int i = 0; i < MaxRetries; i++) { try { using (var connection = new SQLiteConnection(_connectionString)) { await connection.OpenAsync(); return await action(connection); } } catch (SQLiteException ex) when (IsTransientError(ex)) { if (i == MaxRetries - 1) throw; await LogRetryAttemptAsync(ex, i + 1); await Task.Delay(RetryDelayMs * (i + 1)); } } throw new Exception("重试次数已达上限"); } private bool IsTransientError(SQLiteException ex) { return ex.ResultCode == SQLiteErrorCode.Busy || ex.ResultCode == SQLiteErrorCode.Locked; } private async Task LogRetryAttemptAsync(SQLiteException ex, int attempt) { var logMessage = $"重试尝试 {attempt}/{MaxRetries}. 错误: {ex.Message}"; await ExecuteWithoutRetryAsync(async conn => { using var cmd = conn.CreateCommand(); cmd.CommandText = "INSERT INTO Logs (Message, LogLevel) VALUES (@message, @level)"; cmd.Parameters.AddWithValue("@message", logMessage); cmd.Parameters.AddWithValue("@level", "Warning"); await cmd.ExecuteNonQueryAsync(); }); } // 不带重试的执行方法(用于内部日志等操作) private async Task ExecuteWithoutRetryAsync(Func<SQLiteConnection, Task> action) { using var connection = new SQLiteConnection(_connectionString); await connection.OpenAsync(); await action(connection); } // 实用方法:添加用户 public async Task AddUserAsync(string name, string email) { await ExecuteWithRetryAsync(async connection => { using var command = connection.CreateCommand(); command.CommandText = "INSERT INTO Users (Name, Email) VALUES (@name, @email)"; command.Parameters.AddWithValue("@name", name); command.Parameters.AddWithValue("@email", email); await command.ExecuteNonQueryAsync(); }); } // 实用方法:获取用户 public async Task<List<UserModel>> GetUsersAsync() { return await ExecuteWithRetryAsync(async connection => { var users = new List<UserModel>(); using var command = connection.CreateCommand(); command.CommandText = "SELECT Id, Name, Email, CreatedAt FROM Users"; using var reader = await command.ExecuteReaderAsync(); while (await reader.ReadAsync()) { users.Add(new UserModel { Id = reader.GetInt32(0), Name = reader.GetString(1), Email = reader.GetString(2), CreatedAt = reader.GetDateTime(3) }); } return users; }); } public void Dispose() { // 清理资源 GC.SuppressFinalize(this); } } public class UserModel { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } public DateTime CreatedAt { get; set; } } // 示例用法 public class Program { public static async Task Main() { var dbPath = "test.db"; if (File.Exists(dbPath)) File.Delete(dbPath); using var dbManager = new DatabaseRetryManager(dbPath); // 测试添加用户 try { Console.WriteLine("添加用户..."); await dbManager.AddUserAsync("John Doe", "john@example.com"); await dbManager.AddUserAsync("Jane Doe", "jane@example.com"); // 测试并发操作 var tasks = new List<Task>(); for (int i = 0; i < 10; i++) { int index = i; tasks.Add(dbManager.AddUserAsync( $"User{index}", $"user{index}@example.com" )); } await Task.WhenAll(tasks); // 获取所有用户 Console.WriteLine("\n获取所有用户:"); var users = await dbManager.GetUsersAsync(); foreach (var user in users) { Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, " + $"Email: {user.Email}, Created: {user.CreatedAt}"); } } catch (Exception ex) { Console.WriteLine($"错误: {ex.Message}"); } } }

image.png

最佳实践总结

  1. 始终使用参数化查询,避免SQL注入
  2. 正确管理数据库连接的生命周期
  3. 实现适当的重试机制
  4. 设置合理的超时时间
  5. 在出现锁库时提供用户友好的错误提示

注意事项

  1. SQLite不适合高并发的生产环境
  2. 避免长时间持有数据库连接
  3. 合理设计数据库结构,避免锁表
  4. 定期维护和优化数据库
  5. 监控数据库性能和锁定情况

通过以上方案,我们可以有效地处理SQLite在并发操作时的锁库问题,提高应用程序的稳定性和可靠性。

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!