数据库锁 悲观锁| 乐观锁 (关系到事物)

数据库的并发问题:

什么是高并发:
高并发是指:在同一时间有很多人执行一个操作
例如:在某个时间段,同时有2个用户A和B要购买火车票,在购买火车票前比如要查询下火车票数据库是否有票,
与是会有一个查询的操作 select * from chepiao where chepiaoCount>0 
如果查询到还有火车票的时候,就会下订单,于是就会下订单,下订单就是将数据库的车票数量减1
Update chepiao set chepiaoCount=chepiao-1


高并发出现的问题:
假如说就剩下一张火车票了,如果A,B同准备购买火车票,A先查询了一下chepiao表,发现还有一张车票,于是他就要下订单,准备更新车票表的车票数量减-1,可是就在他准备减1的时候(还没有减1),此时B用户也查询了一下车票表,发现也有一张车票,于是也准备下订单,将车票表的车票数量减1。此时A已经将车票表的车票数量减1了(更新完毕),那么车票表的实际车票数量已经为0了,因为B刚刚也查询到有一张票,于是也将车票表的车票数量减去1,这样车票表的数量就为-1了。 于是就造成了车票超卖的情况
【这种情况其实就是数据的脏读】

数据库的并发控制一:悲观锁

悲观锁的的缺点是容易引起死锁,而且性能低(一个用户读取一行数据的时候即上锁,第二个用户来读的时候需要等待第一个用户释放锁,性能能不低吗!) 一般的系统都是读数据的多,写数据的少,所以推荐用乐观锁【EF不支持悲观锁】

namespace DatabaseLock
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入名字");
            var myname = Console.ReadLine().Trim();

            string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;

            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                using (SqlTransaction st = conn.BeginTransaction()) //开启一个事物
                {
                    try
                    {
                        using (SqlCommand selectCmd = conn.CreateCommand())//创建查询命令
                        {
                            selectCmd.Transaction = st;
                            //with(xlock,ROWLOCK)表示对这一行数据加一行排他锁
                            //xlock表示排它锁
                            //ROWLOCK表示行锁
                            //效果:加了这个锁后,别人既不可读这行数据,也不可以写这行数据
                            selectCmd.CommandText = "select * from T_Girls with(xlock,ROWLOCK) where id=1";
                            Console.WriteLine("开始执行查询");
                            string bf = null;
                            using (SqlDataReader dr = selectCmd.ExecuteReader())
                            {
                                if (!dr.Read())
                                {
                                    Console.WriteLine("没有查询到id=1的女孩");
                                    return;
                                }
                                else
                                {

                                    if (!dr.IsDBNull(dr.GetOrdinal("BF")))
                                    {
                                        bf = dr.GetString(dr.GetOrdinal("BF"));
                                    }

                                    if (!string.IsNullOrEmpty(bf))
                                    {
                                        if (bf == myname)
                                        {
                                            Console.WriteLine("早已经是我的人了");
                                            Console.ReadKey();
                                            return;
                                        }
                                        else
                                        {
                                            Console.WriteLine("真是一个悲伤的故事,id=1的女孩被" + bf + "抢走啦");
                                            Console.ReadKey();
                                            return;
                                        }
                                    }
                                }
                            }

                            using (SqlCommand updateCmd = conn.CreateCommand())//创建更新命令
                            {
                                Console.WriteLine("开始抢媳妇");
                                updateCmd.Transaction = st;
                                updateCmd.CommandText = string.Format("update T_Girls set BF='{0}' where id=1", myname);
                                updateCmd.ExecuteNonQuery();

                                Console.WriteLine("成功抢到媳妇,请按任意键结束抢女朋友这件事的事物");
                                Console.ReadKey();
                            }
                            st.Commit();
                        }
                    }
                    catch (Exception e)
                    {
                        st.Rollback();//回滚
                    }
                }
            }
        }
    }
}

数据库的并发控制二:乐观锁

ADO.NET版

乐观锁是一种思想,它的优点是大家性能高(大家都可以同时读,在大家都读在同一条数据的情况下,谁能更新这条数据就不一定了,最先的那个人更新这条数据后,其他人就无法更新了)

乐观锁的使用:
 数据库中有一个特殊的字段类型timestamp ,列名叫什么都无所谓。这个字段的值不需要程序员去维护

 每次修改这行数据的时候,对于timestamp类型的列都会自动变化(一般是增加),我们就是利用这个特性来做乐观锁

乐观锁其实根本就没有锁,它是一种思想,只是根据timestamp类型字段的特特点做了逻辑查询而已。

【火车票表chepiao字段有:id(编号), checi(车次), pnumber(车票剩余数量),rowver(timestamp类型的字段)】 

假如:A和B同一时间段购买火车票,A先查询了一下数据库表

select * from chepiao   where checi='G506'  【假设查出来的pnumber=1 rowver=2002】

 发现还有一张票,于是准备更新车票表的数据,将G506车次的车票剩余数量减1,就在它正准备更新,还未更新的时候,B用户查询了一下车票数据表,发现也有一张票【当然与A用户查询出来的数据是一样的pnumber=1 rowver=2002】,当B用户刚刚查询完毕,A用户已经在执行更新操作了,更新语句是这样的

update chepiao set pnumber=pnumber-1 where checi='G506' and rowver='2002'

当这条语句更新完毕,这条数据的rowver字段的值会立即改变,由原来的2002 改变成2003(有可能改变成其他的值,但是一定会变就是了)

此时B用户也准备更新数据了,于是也执行了以下这段代码

update chepiao set pnumber=pnumber-1 where checi='G506' and rowver='2002'

可是此时这条数据的rowver值已经由原来的2002变成现在的2003了  因为表中没有rowver为2002的数据啊,所有这条数据执行更新的结果是“执行受影响的行数是0” 即没有更新。即B用户购买火车票失败。这就是乐观锁。


根据原理,我们用做一个抢老婆的程序体验下 

 乐观锁“抢老婆”的思路很简单,抢之前先查一下老婆的timestamp的值,如果查询出来的是2002,那么就执行
 update T_Girls set BF='张三' where id=1 and RowVersion=2002

 执行之后如果发现“执行受影响的行数是0” 就说明这个老婆已经被其他人抢走了,因此,抢老婆失败

要想使用乐观锁,需要在表中加一个类型为timestamp 类型的列(名字叫啥都为所谓,我这里就给它取名RowVer吧)


namespace DatabaseLock
{
    class Program
    {
        static void Main(string[] args)
        { 
         Console.WriteLine("请输入名字");
            var myname = Console.ReadLine().Trim();

            string connStr = ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;

            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                long rowver = 0;
                using (SqlCommand selectCmd = conn.CreateCommand())
                {
                    string bf=null;
                    Console.WriteLine("开始执行查询");
					//RowVersion 是timestamp类型的,将它转换成bigint类型,好方便后面的reader.GetInt64(reader.GetOrdinal("RowVersion"));获取值
                    selectCmd.CommandText = "select id,name,bf,CONVERT(BIGINT,RowVersion) AS 'RowVersion'  from T_Girls where id=1";
                    using (SqlDataReader reader = selectCmd.ExecuteReader())
                    {
                        if (!reader.Read())
                        {
                            Console.WriteLine("没有查询到id=1的女孩");
                            return;
                        }
                        rowver = reader.GetInt64(reader.GetOrdinal("RowVersion")); //获取到已经由timestamp类型转换成long类型的RowVersion列的值
                        if(!reader.IsDBNull(reader.GetOrdinal("BF"))) //如果BF的值不为空
                        {
                            bf=reader.GetString(reader.GetOrdinal("BF"));//获取BF列的值
                        }
                        if(!string.IsNullOrEmpty(bf))
                        {
                            if(bf==myname)
                            {
                                Console.WriteLine("早就是自己的人了,还抢啥?");
                            }
                            else
                            {
                                Console.WriteLine("糟糕!早就被"+bf+"抢走了");
                            }
                            Console.ReadKey();
                            return;
                        }
                        

                    }
                    using (SqlCommand updateCmd = conn.CreateCommand())
                    {
                        updateCmd.CommandText = string.Format("update T_Girls set BF='{0}' where id=1 and RowVersion={1}", myname, rowver);

                        if (updateCmd.ExecuteNonQuery() > 0)
                        {
                            Console.WriteLine("成功抢到媳妇");
                        }
                        else
                        {
                            Console.WriteLine("好可惜,媳妇被别人抢走了");
                        }
                        Console.ReadKey();
                    }

                }
            }
        }
    }
}

EF(code first)版

首先创建数据库表

namespace BF.Entities.EntityConfig
{
    public class GirlsConfig : EntityTypeConfiguration<Girls>
    {
        public GirlsConfig()
        {
            ToTable("T_Girls").HasKey(r => r.Id);
            this.Property(r => r.Name).IsRequired().HasMaxLength(10);
            this.Property(r => r.BF).HasMaxLength(10);
            this.Property(r => r.RowVersion).IsRowVersion();//注意这里将RowVersion字段设置成IsRowVersion 那么它就会在表中生成timestamp类型的数据了
        }
    }
}


模型类
namespace BF.Entities.Entitys
{
    public class Girls
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string BF { get; set; }
        public byte[] RowVersion { get; set; } //注意:在数据表中的timestamp类型的数据这里用byte[]来接收 
    }
}

正题:

namespace DatabaseLock
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("请输入名字");
            var myname = Console.ReadLine().Trim();

            using (MyDbContext db = new MyDbContext())
            {
                var row = db.Girls.SingleOrDefault();
                if (row != null)
                {
                    if (!string.IsNullOrEmpty(row.BF))
                    {
                        if (row.BF == myname)
                        {
                            Console.WriteLine("早已经是你的人了,还抢啥");
                        }
                        else
                        {
                            Console.WriteLine("真可惜,美女被" + row.BF + "抢走了");
                        }
                        Console.ReadKey();
                        return;
                    }
                    Console.WriteLine("开始抢美女啦!");
                    try
                    {
                        row.BF = myname;
                        db.SaveChanges(); //根据我们上面查询出来的row数据,如果在更新之前,有人修改了这条数据,那么RowVer列的数据也会自动更改,那么这条数据将无法更新保存,会抛出异常,于是就会判断是抢美女失败啦
                        Console.WriteLine("恭喜你,成功抢到美女,下订单成功");
                    }
                    catch (DbUpdateConcurrencyException ex)
                    {
                        Console.WriteLine("真可惜,来晚了,美女被别人抢走了");
                    }
                }
                else
                {
                    Console.WriteLine("没有查询到美女");
                }
            }
		}
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值