资讯动态

SqlSugar多线程环境下的线程安全解决方案

发布时间:2026/10/26 16:59:06 来源:尧图企业网站定制
1. SqlSugar多线程环境下的线程安全陷阱解析在.NET生态中SqlSugar作为一款轻量级ORM框架因其简洁的API设计和出色的性能表现被广泛应用于各类数据库操作场景。但在多线程环境下使用SqlSugarClient时开发者往往会遇到一些隐蔽的线程安全问题。这些问题轻则导致数据不一致重则引发系统崩溃。重要提示SqlSugarClient实例本身并非线程安全对象直接在多线程环境中共享使用会导致不可预知的后果。这与其内部连接池管理和状态机机制密切相关。1.1 典型问题场景复现假设我们有一个电商系统的订单处理模块采用多线程方式处理并发订单。以下代码展示了典型的错误用法// 错误示例多线程共享SqlSugarClient实例 public class OrderService { private static SqlSugarClient _db new SqlSugarClient(new ConnectionConfig() { ConnectionString Server.;DatabaseTestDB;Uidsa;Pwd123456, DbType DbType.SqlServer, IsAutoCloseConnection true }); public void ProcessOrder(Order order) { Task.Run(() { _db.Insertable(order).ExecuteCommand(); // 其他业务操作... }); } }这段代码在低并发时可能运行正常但当线程数增加时会出现以下典型问题数据库连接被意外关闭事务状态异常查询结果混乱偶发的NullReferenceException1.2 底层原理分析SqlSugarClient内部维护着这些关键状态连接池管理采用Stack结构存储空闲连接事务状态机跟踪当前操作的事务状态缓存机制包括查询缓存和映射缓存临时参数集合存储当前操作的临时变量当多个线程同时操作这些共享状态时如果没有适当的同步机制就会产生竞态条件Race Condition。特别是在高并发场景下这种问题会被放大。2. 线程安全解决方案对比2.1 方案一线程局部存储ThreadLocalprivate static readonly ThreadLocalSqlSugarClient _threadLocalDb new ThreadLocalSqlSugarClient(() { return new SqlSugarClient(new ConnectionConfig() { ConnectionString Server.;DatabaseTestDB;Uidsa;Pwd123456, DbType DbType.SqlServer, IsAutoCloseConnection true }); }); public void ProcessOrder(Order order) { var db _threadLocalDb.Value; db.Insertable(order).ExecuteCommand(); }优点每个线程拥有独立实例无需频繁创建销毁对象性能较好缺点线程池复用可能导致实例被不同逻辑使用需要手动管理生命周期2.2 方案二依赖注入推荐在ASP.NET Core中最佳实践是通过依赖注入容器管理生命周期// Startup.cs services.AddScopedISqlSugarClient(provider { return new SqlSugarClient(new ConnectionConfig() { ConnectionString Configuration.GetConnectionString(Default), DbType DbType.SqlServer, IsAutoCloseConnection true }); }); // Controller中使用 public class OrderController : Controller { private readonly ISqlSugarClient _db; public OrderController(ISqlSugarClient db) { _db db; } [HttpPost] public IActionResult CreateOrder([FromBody] Order order) { _db.Insertable(order).ExecuteCommand(); return Ok(); } }生命周期选择建议ScopedWeb请求级别默认推荐Transient每次获取新实例性能开销较大Singleton绝对不要使用会引发线程安全问题2.3 方案三对象池模式对于非Web环境的高并发场景可以考虑使用对象池public class DbPool { private readonly ConcurrentQueueSqlSugarClient _pool new ConcurrentQueueSqlSugarClient(); private readonly ConnectionConfig _config; public DbPool(ConnectionConfig config, int initialCount 10) { _config config; for(int i 0; i initialCount; i) { _pool.Enqueue(CreateInstance()); } } public SqlSugarClient Rent() { if(_pool.TryDequeue(out var instance)) { return instance; } return CreateInstance(); } public void Return(SqlSugarClient instance) { // 重置状态 instance.Ado.RollbackTran(); instance.Ado.Close(); _pool.Enqueue(instance); } private SqlSugarClient CreateInstance() { return new SqlSugarClient(_config); } }3. 高级场景下的特殊处理3.1 分布式锁集成在高并发更新场景即使解决了线程安全问题仍需考虑数据一致性public void UpdateProductStock(int productId, int quantity) { using(var redisLock new RedLock($product_lock_{productId}, TimeSpan.FromSeconds(10))) { if(redisLock.IsAcquired) { var db _dbProvider.Get(); var product db.QueryableProduct().First(p p.Id productId); product.Stock - quantity; db.Updateable(product).ExecuteCommand(); } else { throw new Exception(获取锁失败请重试); } } }3.2 批量操作优化对于批量插入场景推荐使用专有API// 正确的高性能批量插入 public void BatchInsertOrders(ListOrder orders) { var db new SqlSugarClient(_config); db.Insertable(orders).ExecuteReturnIdentity(); }而非// 错误做法在循环中使用单条插入 foreach(var order in orders) { db.Insertable(order).ExecuteCommand(); }4. 性能监控与诊断4.1 连接泄漏检测添加以下代码可帮助识别未关闭的连接var db new SqlSugarClient(new ConnectionConfig() { // ...其他配置 OnInitCommand (conn, cmd) { Debug.WriteLine($命令执行: {cmd.CommandText}); }, OnLogExecuting (sql, pars) { var stack new System.Diagnostics.StackTrace(); Debug.WriteLine($SQL调用堆栈:\n{stack}); } });4.2 性能计数器监控关键指标PerformanceCounter connCounter new PerformanceCounter( .NET Data Provider for SqlServer, NumberOfActiveConnectionPools, YourAppName); PerformanceCounter poolCounter new PerformanceCounter( .NET Data Provider for SqlServer, NumberOfPooledConnections, YourAppName);5. 单元测试策略确保线程安全的测试方案[Test] public void TestMultiThreadAccess() { var config new ConnectionConfig { /* 测试配置 */ }; int successCount 0; int threadCount 100; Parallel.For(0, threadCount, i { try { using(var db new SqlSugarClient(config)) { db.Insertable(new TestEntity { Name $Test_{i} }).ExecuteCommand(); Interlocked.Increment(ref successCount); } } catch(Exception ex) { Assert.Fail($线程{i}失败: {ex.Message}); } }); Assert.AreEqual(threadCount, successCount); }6. 框架设计建议对于大型项目建议采用仓储模式隔离数据访问public interface IRepositoryT where T : class, new() { ISqlSugarClient Db { get; } // 基础CRUD方法... } public class RepositoryT : IRepositoryT where T : class, new() { private readonly ISqlSugarClient _db; public Repository(ISqlSugarClient db) { _db db; } public ISqlSugarClient Db _db; // 实现具体方法... } // 业务层使用 public class OrderService { private readonly IRepositoryOrder _orderRepo; public OrderService(IRepositoryOrder orderRepo) { _orderRepo orderRepo; } public void ProcessOrder(Order order) { using(var tran _orderRepo.Db.Ado.UseTran()) { _orderRepo.Db.Insertable(order).ExecuteCommand(); // 其他业务操作 tran.CommitTran(); } } }7. 连接字符串最佳实践7.1 加密处理不要将明文连接字符串放在配置文件中// appsettings.json { ConnectionStrings: { Default: 加密后的字符串 } } // 解密使用 services.AddScopedISqlSugarClient(provider { var config provider.GetRequiredServiceIConfiguration(); var connStr Decrypt(config.GetConnectionString(Default)); return new SqlSugarClient(new ConnectionConfig { ConnectionString connStr, DbType DbType.SqlServer }); });7.2 多租户支持对于SaaS应用动态连接字符串管理public class TenantAwareDbProvider { private readonly IHttpContextAccessor _httpAccessor; private readonly IConfiguration _config; public TenantAwareDbProvider(IHttpContextAccessor httpAccessor, IConfiguration config) { _httpAccessor httpAccessor; _config config; } public ISqlSugarClient Get() { var tenantId _httpAccessor.HttpContext.User.FindFirst(tenant_id)?.Value; var baseConnStr _config.GetConnectionString(Master); var connStr ${baseConnStr};Application NameTenant_{tenantId}; return new SqlSugarClient(new ConnectionConfig { ConnectionString connStr, DbType DbType.SqlServer }); } }8. 事务管理的线程安全8.1 显式事务的正确用法public void TransferMoney(int fromId, int toId, decimal amount) { using(var db new SqlSugarClient(_config)) { try { db.Ado.BeginTran(); var fromAccount db.QueryableAccount().First(a a.Id fromId); fromAccount.Balance - amount; db.Updateable(fromAccount).ExecuteCommand(); var toAccount db.QueryableAccount().First(a a.Id toId); toAccount.Balance amount; db.Updateable(toAccount).ExecuteCommand(); db.Ado.CommitTran(); } catch { db.Ado.RollbackTran(); throw; } } }8.2 异步事务处理public async Task TransferMoneyAsync(int fromId, int toId, decimal amount) { using(var db new SqlSugarClient(_config)) { try { await db.Ado.BeginTranAsync(); var fromAccount await db.QueryableAccount().FirstAsync(a a.Id fromId); fromAccount.Balance - amount; await db.Updateable(fromAccount).ExecuteCommandAsync(); var toAccount await db.QueryableAccount().FirstAsync(a a.Id toId); toAccount.Balance amount; await db.Updateable(toAccount).ExecuteCommandAsync(); await db.Ado.CommitTranAsync(); } catch { await db.Ado.RollbackTranAsync(); throw; } } }9. 连接池调优9.1 关键参数配置var config new ConnectionConfig { ConnectionString Server.;DatabaseTestDB;Uidsa;Pwd123456; Poolingtrue; Min Pool Size5; Max Pool Size100; Connection Lifetime300;, DbType DbType.SqlServer };参数说明Min Pool Size连接池保持的最小连接数Max Pool Size连接池允许的最大连接数Connection Lifetime连接在池中的最大存活时间秒9.2 监控与调优使用SQL Server DMV监控连接池状态SELECT DB_NAME(dbid) as DatabaseName, COUNT(dbid) as NumberOfConnections, loginame as LoginName FROM sys.sysprocesses WHERE dbid 0 GROUP BY dbid, loginame10. 异常处理策略10.1 重试机制实现public T RetryDbOperationT(FuncISqlSugarClient, T operation, int maxRetries 3) { int retryCount 0; while(true) { using(var db new SqlSugarClient(_config)) { try { return operation(db); } catch(SqlException ex) when (IsTransientError(ex) retryCount maxRetries) { retryCount; Thread.Sleep(100 * retryCount); continue; } } } } private bool IsTransientError(SqlException ex) { // 1205: 死锁 // -2: 超时 // 4060: 数据库不可用 int[] transientErrors { 1205, -2, 4060 }; return transientErrors.Contains(ex.Number); }10.2 死锁处理try { db.Insertable(entity).ExecuteCommand(); } catch(SqlException ex) when (ex.Number 1205) { // 死锁发生等待随机时间后重试 var waitTime new Random().Next(100, 500); await Task.Delay(waitTime); await RetryOperation(); }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑