悲观锁、乐观锁
悲观锁、乐观锁用来处理并发情况下出现的问题
模拟一个抢单的业务场景,一个乘客发了一个打车订单,很多司机去抢这个订单,执行的业务简单点来说是,先select出这条数据,然后update这个条数据中的driveName字段为自己的名字,但是现在会有这么一种现象,同时select出这条订单,先后更新driveName这个字段,先抢到订单的乘客会发现最后订单没了,实际上是数据库中Update第二次的操作覆盖了第一次的操作了,这就是并发操作带来的尴尬场景。
悲观锁:
悲观的认为每次去拿数据的时候都会被别人修改,所以每次在拿数据的时候都会“上锁”,操作完成之后再“解锁”。 在数据加锁期间,其他人(其他线程)如果来拿数据就会等待,直到去掉锁。
实现原理:开启事务,利用排它锁和行锁将该条数据锁住,其他线程如果要访问,必须得该线程提交完事务,锁释放后才能使用。
排它锁:如果在事务内使用排它锁,要等到事务结束后排它锁才会释放
共享锁:即使是在事务内使用共享锁,语句执行结束就释放锁(如果显式添加共享锁,会等事务完成后释放锁)
乐观锁:
乐观的认为该条数据不会被占用,自己先占了再说,占完了之后再看看是不是占上了,如果没占上就是操作失败,提示给用户。
实现原理:添加一个rowversion字段,凡是对该条数据进行过update操作,rowversion字段的值都会发生变化。在update时判断rowversion是不是和查询出来的一致,如果不一致就提示用户失败
两种锁进行对比:
悲观锁使用的体验更好,但是对系统性能的影响大,只适合并发量不大的场合。 乐观锁适用于“写少读多”的情况下,加大了系统的整个吞吐量,但是“乐观锁可能失败”给用户的体验很不好。
悲观锁案例
using MySqlConnection conn = new MySqlConnection();
conn.ConnectionString = "Server=127.0.0.1;Port=3306;Database=studentsdb; User=root;Password=123456;";
//conn.Insert(new User { LoginCount = 0 });
User user=null;
conn.Open();
using var tran = conn.BeginTransaction(System.Data.IsolationLevel.Serializable);
try
{
user = conn.QueryFirst<User>("SELECT * FROM Users WHERE ID=1 FOR UPDATE", transaction:tran);//conn.Get<User>(1, tran);
Thread.Sleep(100);
user.LoginCount += 1;
conn.Update(user, tran);
}
catch (Exception ex)
{
tran.Rollback();
}
finally
{
tran.Commit();
}
乐观锁案例1:(使用版本号)
{
try
{
Console.WriteLine("司机您好,请输入您的名字");
string driverName = Console.ReadLine();
using (LockDemoDBEntities1 ctx = new LockDemoDBEntities1())
{
Console.WriteLine("开始查询");
//一定要遍历一下 SqlQuery 的返回值才会真正执行 SQL
var orderInfor = ctx.Database.SqlQuery<OrderInfor2>("select * from OrderInfor2 where id=1").Single();
//表示该订单已经被抢了
if (orderInfor.isRobbed == "1" && !string.IsNullOrEmpty(orderInfor.driverName))
{
if (driverName == orderInfor.driverName)
{
Console.WriteLine("该订单早已经被我抢了");
}
else
{
Console.WriteLine($"该订单早已经被司机【{orderInfor.driverName}】抢了");
}
//不在往下执行
Console.ReadKey();
return;
}
Console.WriteLine("查询完成,按任意键进行抢单");
Console.ReadKey();
Console.WriteLine("正在抢单中。。。。。");
//休眠3s,模拟高并发抢单
Thread.Sleep(3000);
int affectRows = ctx.Database.ExecuteSqlCommand("Update OrderInfor2 set driverName={0},isRobbed={1} where id=1 and rowversion={2}", driverName, "1", orderInfor.rowversion);
if (affectRows == 0)
{
Console.WriteLine("抢单失败");
}
else if (affectRows == 1)
{
Console.WriteLine("抢单成功");
}
else
{
Console.WriteLine("见鬼了");
}
}
}
catch (Exception ex)
{
Console.WriteLine("失败了");
Console.WriteLine(ex.Message);
throw;
}
}
乐观锁案例2:(使用条件限制实现乐观锁,适用于减库存场景)
不需要添加版本字段,适用于库存场景,只需要在减库存操作时加条件限制即可
UPDATE Product SET stock=stock-[buyNumber] WHERE ID=1 stock-[buyNumber]>0
如果使用EF,EF支持乐观锁

浙公网安备 33010602011771号