悲观锁、乐观锁

悲观锁、乐观锁用来处理并发情况下出现的问题

模拟一个抢单的业务场景,一个乘客发了一个打车订单,很多司机去抢这个订单,执行的业务简单点来说是,先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支持乐观锁

posted @ 2020-01-06 14:31  .Neterr  阅读(279)  评论(0)    收藏  举报