C#与SQL Server存储过程线程安全:板材电商网站价格计算异常排查
这种用户拿到其他用户价格的问题,十有八九是线程安全漏洞或者共享资源未正确隔离导致的——我之前在电商后端排查过几乎一模一样的问题,给你梳理下C#和SQL Server两端的可能原因,以及对应的排查步骤:
一、C#后端的常见问题点
1. 静态变量/静态类的误用
这是Web应用中最容易引发串户问题的原因。如果你的价格计算逻辑用了静态字段来临时存储用户的板材尺寸、计算结果,那多线程并发请求时,这些静态变量会被不同用户的请求互相覆盖。比如:
// 错误示例:静态变量被所有请求共享 public static class PriceCalcService { public static decimal CurrentWidth; public static decimal CurrentHeight; public static decimal FinalPrice; public static decimal GetPrice() { // 调用存储过程时用CurrentWidth/CurrentHeight当参数 // 把结果存到FinalPrice return FinalPrice; } }
当两个用户同时发起请求,UserA的尺寸刚存进去,UserB的请求就把它覆盖了,最后UserA拿到的可能是UserB的价格。
2. 服务生命周期配置错误
如果在ASP.NET(Core)中,把处理价格请求的服务注册成了Singleton(单例),但服务内部又有实例级的字段来存储请求参数/结果,那所有请求都会复用同一个实例,自然会串数据。比如:
// 错误示例:单例服务带实例字段 public class PriceCalcService { private decimal _width; private decimal _height; public async Task<decimal> CalculatePrice(decimal width, decimal height) { _width = width; _height = height; // 调用存储过程 return await CallStoredProc(_width, _height); } } // Startup中错误注册成单例 services.AddSingleton<PriceCalcService>();
3. HttpContext共享问题
如果在异步操作中错误地捕获了HttpContext.Current(比如在非请求线程中访问),或者把上下文里的用户数据存到了共享容器中,也可能导致数据串用。
C#端排查步骤
- 先扫一遍所有和价格计算相关的代码,找有没有静态字段/静态属性存储请求相关的数据;
- 检查依赖注入的配置:处理请求的服务是不是用了
Scoped(每个请求一个实例)或者Transient(每次获取一个新实例),而不是Singleton; - 加日志:把每个请求的
SessionId(或用户唯一标识)、传入的板材尺寸、返回的价格关联起来,出现错误时直接回溯对应的请求链,看参数是不是被覆盖了; - 本地复现:用JMeter、Postman批量发起并发请求,模拟多用户场景,看能不能稳定复现问题。
二、SQL Server存储过程的可能问题
1. 全局临时表的误用
如果存储过程中用了全局临时表(以##开头),那这个表是跨会话共享的——多个用户同时调用存储过程时,会互相读写同一个临时表,导致计算用了别人的参数。比如:
-- 错误示例:全局临时表被多会话共享 CREATE PROCEDURE dbo.CalculatePlatePrice @Width DECIMAL(18,2), @Height DECIMAL(18,2) AS BEGIN CREATE TABLE ##TempPlateParams (Width DECIMAL(18,2), Height DECIMAL(18,2)) INSERT INTO ##TempPlateParams VALUES (@Width, @Height) -- 基于临时表的数据计算价格 SELECT TOP 1 Price FROM dbo.PlatePrice WHERE Width >= @Width AND Height >= @Height ORDER BY Price ASC DROP TABLE ##TempPlateParams END
如果两个会话同时执行,第二个会话的INSERT会覆盖第一个会话的数据,导致第一个会话拿到错误的价格。
2. 全局变量/会话变量的误用
如果存储过程中依赖了自定义的全局变量(不是系统自带的@@开头的变量),或者误用了会话级变量(比如SET @GlobalVar = ...但没在每次调用时重置),也可能引发串户。
3. 参数传递错误
比如存储过程的嵌套调用中,没有正确传递参数,导致子存储过程用了父存储过程的参数,而父存储过程的参数可能被其他会话干扰。
SQL端排查步骤
- 检查存储过程代码,找有没有
##开头的全局临时表,或者自定义的全局变量; - 确认所有计算逻辑都是基于传入的
@参数,没有依赖任何会话级的共享数据; - 开启SQL Server的Extended Events(比Profiler轻量),捕获出现错误时的存储过程调用参数和返回结果,对比错误价格对应的参数是不是属于其他用户;
- 并发测试:打开多个查询窗口,同时调用存储过程传入不同参数,看返回结果是否和自己的参数匹配。
总结
这种跨用户的数据串扰,最常见的根源就是C#端的共享静态变量/单例服务的实例字段,或者SQL端的全局共享对象。建议先从C#端入手排查——毕竟Web应用的多线程场景下,静态变量的误用概率更高,排查起来也更简单。
内容的提问来源于stack exchange,提问作者dotdev

