悲观锁、乐观锁

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

模拟一个抢单的业务场景

  一个乘客发了一个打车订单,很多司机去抢这个订单,执行的业务简单点来说是,先select出这条数据,然后update这个条数据中的driveName字段为自己的名字,但是现在会有这么一种现象,同时select出这条订单,先后更新driveName这个字段,先抢到订单的乘客会发现最后订单没了,实际上是数据库中Update第二次的操作覆盖了第一次的操作了,这就是并发操作带来的尴尬场景。

悲观锁:

悲观的认为每次去拿数据的时候都会被别人修改,所以每次在拿数据的时候都会“上锁”,操作完成之后再“解锁”。 在数据加锁期间,其他人(其他线程)如果来拿数据就会等待,直到去掉锁。

实现原理:开启事务,利用排它锁和行锁将该条数据锁住,其他线程如果要访问,必须得该线程提交完事务,锁释放后才能使用。

排它锁:如果在事务内使用排它锁,要等到事务结束后排它锁才会释放

共享锁:即使是在事务内使用共享锁,语句执行结束就释放锁(如果显式添加共享锁,会等事务完成后释放锁)

乐观锁:

乐观的认为该条数据不会被占用,自己先占了再说,占完了之后再看看是不是占上了,如果没占上就是操作失败,提示给用户。

实现原理:添加一个rowversion字段,凡是对该条数据进行过update操作,rowversion字段的值都会发生变化。在update时判断rowversion是不是和查询出来的一致,如果不一致就提示用户失败

  

两种锁进行对比:

悲观锁使用的体验更好,但是对系统性能的影响大,只适合并发量不大的场合。 乐观锁适用于“写少读多”的情况下,加大了系统的整个吞吐量,但是“乐观锁可能失败”给用户的体验很不好。

悲观锁案例:

{
                Console.WriteLine("司机您好,请输入您的名字");
                string driverName = Console.ReadLine();
                string connstr = ConfigurationManager.ConnectionStrings["connstr"].ConnectionString;
                using (SqlConnection conn = new SqlConnection(connstr))
                {
                    conn.Open();
                    using (var tx = conn.BeginTransaction())
                    {
                        try
                        {
                            Console.WriteLine("开始查询");
                            using (var selectCmd = conn.CreateCommand())
                            {
                                selectCmd.Transaction = tx;
                                //排它锁和行锁,针对访问线程锁住该行,不能继续往下执行,只有事务提交完,其他线程才能访问
                                selectCmd.CommandText = "select * from OrderInfor with(xlock,ROWLOCK) where id=1";
                                using (var reader = selectCmd.ExecuteReader())
                                {
                                    if (!reader.Read())
                                    {
                                        Console.WriteLine("没有id为1的订单");
                                        return;
                                    }
                                    string dName = null;
                                    string isRobbed = null;
                                    if (!reader.IsDBNull(reader.GetOrdinal("driverName")))
                                    {
                                        dName = reader.GetString(reader.GetOrdinal("driverName"));
                                    }
                                    if (!reader.IsDBNull(reader.GetOrdinal("isRobbed")))
                                    {
                                        isRobbed = reader.GetString(reader.GetOrdinal("isRobbed"));
                                    }

                                    //表示该订单已经被抢了
                                    if (isRobbed == "1" && !string.IsNullOrEmpty(dName))
                                    {
                                        if (driverName == dName)
                                        {
                                            Console.WriteLine("该订单早已经被我抢了");
                                        }
                                        else
                                        {
                                            Console.WriteLine($"该订单早已经被司机【{dName}】抢了");
                                        }
                                        //不再往下执行
                                        Console.ReadKey();
                                        return;
                                    }
                                }
                                Console.WriteLine("查询完成,开始执行update操作");
                                using (var updateCmd = conn.CreateCommand())
                                {
                                    updateCmd.Transaction = tx;
                                    updateCmd.CommandText = "Update OrderInfor set driverName=@driverName,isRobbed=@isRobbed where id=1";
                                    updateCmd.Parameters.Add(new SqlParameter("@driverName", driverName));
                                    updateCmd.Parameters.Add(new SqlParameter("@isRobbed", "1"));
                                    updateCmd.ExecuteNonQuery();
                                }
                                Console.WriteLine("结束update操作");
                                Console.WriteLine("按任意键进行事务提交");
                                Console.ReadKey();
                            }
                            tx.Commit();
                            Console.WriteLine("事务提交成功");
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex);
                            tx.Rollback();
                        }
                    }
                }
            }

乐观锁案例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支持乐观锁

原文地址:https://www.cnblogs.com/fanfan-90/p/12156172.html