• EF Core 如何应对高并发


    1、高并发的情况,时常会发生数据不稳定的情况

      在看本节内容之前,请先看上一章SqlServer 高并发的情况下,如何利用锁保证数据的稳定性

      本节内容,也是具体讨论如何在EF中实现这些操作

    2、场景模拟,同上一章,抢券

      EF 不考虑高并发的情况下,抢券代码为:

    复制代码
    string _currOwner = Console.ReadLine();//当前用户
    using var ctx = new MyDBContext();
    var cop = ctx.Coupons.Single(x => x.Id == 2);
    if (!string.IsNullOrEmpty(cop.Owner))
    {
        Console.WriteLine($"券被抢了");
    }
    else
    {
        cop.Owner = _currOwner;
        Thread.Sleep(5000);
        ctx.SaveChanges();
        Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
    }
    Console.ReadLine();
    复制代码

      打开两个进程,让tom和jerry同时先后进行抢券,模拟出一个券同时被两个用户抢到的情况

      

      

      上图可用直观看出,都提示抢券成功,但是owner是晚一点点执行update的jerry,在实际生产中,无法给tom一个交代

    3、解决并发问题

      3.1 通过updlock,悲观并发控制

    复制代码
    string _currOwner = Console.ReadLine();//当前用户
    using var ctx = new MyDBContext();
    using var tx = ctx.Database.BeginTransaction();
    FormattableString sql = $@"select * from Coupons with(updlock) where id=2";
    var cop = ctx.Coupons.FromSqlInterpolated(sql).Single();
    if (!string.IsNullOrEmpty(cop.Owner))
    {
        Console.WriteLine($"券被抢了");
    }
    else
    {
        cop.Owner = _currOwner;
        Thread.Sleep(5000);
        ctx.SaveChanges();
        Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
    }
    tx.Commit();
    Console.ReadLine();
    复制代码

      解决:但这个是排他锁,有可能造成线程卡顿问题

        

      3.2 通过定义鉴权字段,乐观并发控制

        CouponConfig添加配置

                builder.Property(x => x.Owner).IsConcurrencyToken();

        抢券代码:

    复制代码
    string _currOwner = Console.ReadLine();//当前用户
    using var ctx = new MyDBContext();
    var cop = ctx.Coupons.Single(x => x.Id == 2);
    if (!string.IsNullOrEmpty(cop.Owner))
    {
        Console.WriteLine($"券被抢了");
    }
    else
    {
        Thread.Sleep(5000);
        try
        {
            cop.Owner = _currOwner;
            await ctx.SaveChangesAsync();
            Console.WriteLine($"恭喜{_currOwner}抢到券{cop.Id}了");
        }
        catch (DbUpdateConcurrencyException ex)
        {
            var entry = ex.Entries.First();
            var dbValues = entry.GetDatabaseValues();
            var newOwner = dbValues.GetValue<string>(nameof(Coupon.Owner));
            Console.WriteLine($"并发冲突,{newOwner}已经抢到该券了");
        }
    }
    复制代码

        结果:

          

        根据update语句,可用看出where加了owner=旧值,来判断是否发生过更改

      3.3 添加数据版本标识

        如果无法定义一个明确的鉴权字段,那么可用通过新增一个字段,来标识数据来进行鉴权

    复制代码
        public class Coupon
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public string? Description { get; set; }
            public string? Owner { get; set; }
            public byte[] RowVersion { get; set; } #迁移到数据库,类型为rowversion,当数据更新时,版本会自动递增
        }
    复制代码

        迁移后数据库表代码

    复制代码
    CREATE TABLE [dbo].[Coupons] (
        [Id]          INT            IDENTITY (1, 1) NOT NULL,
        [Name]        NVARCHAR (MAX) NOT NULL,
        [Description] NVARCHAR (MAX) NULL,
        [Owner]       NVARCHAR (MAX) NULL,
        [RowVersion]  ROWVERSION     NOT NULL,
        CONSTRAINT [PK_Coupons] PRIMARY KEY CLUSTERED ([Id] ASC)
    );
    复制代码

        CouponConfig添加配置

                builder.Property(x => x.RowVersion).IsRowVersion();

        抢券代码同3.2

        结果:

      

      理论和3.2相同,where会做一个rowversion的旧值判断

     

      总结:这三种方法由浅入深,各有利弊,在并发量不大的情况下使用3.1,并发量较大的情况下使用3.2&3.3

     

      感谢关注!!

     

     

     

     

     

     

     

     

     

       

     

  • 相关阅读:
    数据分析全貌
    Vivado生成sdf文件命令
    IO多路复用的理解/演变过程
    基于 Milvus 和 ResNet50 的图像搜索(部署及应用)
    抑制细胞代谢紊乱的抑制剂
    JUC并发编程——单例模式(基于狂神说的学习笔记)
    gofs使用教程-基于golang的开源跨平台文件同步工具
    ns3入门基础教程
    C语言如何做到四舍五入保留小数
    基于K-means(K均值)聚类算法的图像特征分割研究-含Matlab代码
  • 原文地址:https://www.cnblogs.com/wskxy/p/17228975.html