高并发数据的重复写入问题
一、情景
.Net Core部署为Web时,当多个线程(多个用户),同时调用后台接口,进行插入操作的话会导致数据库中插入重复的字段!下面把我的解决方法记录一下。
二、实例重现
接口Test 是很简单的功能:对表Demo进行 先判断是否存在,然后插入数据。
/// <summary>
/// 高并发 会出现重复插入 的问题
/// </summary>
/// <returns></returns>
[HttpGet, HttpPost]
public IActionResult Test()
{
var name = Request.Query["name"].ToString();
if (string.IsNullOrWhiteSpace(name))
{
return Json("name为空" + DateTime.Now);
}
using (TestDbContext db = new TestDbContext())
{
var entity = db.Demo.Where(u => u.Name == name).FirstOrDefault();
if (entity == null)
{
var model = new DemoModel();
model.Name = name;
model.CreateTime = DateTime.Now;
db.Demo.Add(model);
db.SaveChanges();
return Json("OK-录入成功");
}
else
{
return Json("Exist--已经存在");
}
}
}
当开启多个线程 同于并发的时候
(这里使用的 JMeter来测试的 :20线程 循环200次 重复数据插入)
结果如下:
三、问题解决方案 (锁)
既然问题是并发请求导致的,最简便的解决方法就是:通过lock()来处理。
互斥锁lock作用:将会锁住代码块的内容,并阻止其他线程进入该代码块,直到该代码块运行完成,释放该锁。
最终实现代码如下:
[HttpGet, HttpPost]
public IActionResult Test2()
{
var name = Request.Query["name"].ToString();
if (string.IsNullOrWhiteSpace(name))
{
return Json("name为空" + DateTime.Now);
}
var str = DemoHelper.Add(name);
return Json(str);
}
public class DemoHelper
{
private static object syncObject = new object();
public static string Add(string name)
{
lock (syncObject)
{
using (TestDbContext db = new TestDbContext())
{
var entity = db.Demo.Where(u => u.Name == name).FirstOrDefault();
if (entity == null)
{
var model = new DemoModel();
model.Name = name;
model.CreateTime = DateTime.Now;
db.Demo.Add(model);
db.SaveChanges();
return "OK-录入成功==" + name;
}
else
{
return "Exist--已经存在==" + name;
}
}
}
}
}
四、总结
针对这类请求并发问题,最简单的就是通过代码锁的方式,将特定功能的并发请求执行转化为队列请求执行,从而避免了问题的发生。
当然,处理并发还有其它方式,如通过数据库锁的方式,再如分布式部署情况下,我们用代码锁的方式也会失效。
加锁本身就有性能上的损耗,如果非必须 一般不加锁。