EFCore_悲观锁与乐观锁(MySQL)

简述

**悲观锁:**对并发可能导致的资源抢占问题持悲观态度,总是认为会有其他的线程来抢占资源,所以会在实际上对资源上锁

**乐观锁:**对并发可能导致的资源抢占问题持乐观态度,并不对资源实际上锁,而是在对资源进行更新时,对表中的并发令牌(单字段并发控制)或行版本字段(多字段并发控制)进行一致性检测(是否与开始读取该资源时一致),若一致则认为此时不存在资源抢占问题,进而更新数据(更新数据时会一同更新行版本字段)

悲观锁

悲观锁实际上是通过MySQL的 for update 语句实现的

优点: 简单

缺点: 使用不当会严重影响性能甚至造成死锁

所以实际上更推荐使用乐观锁

示例:

cs 复制代码
internal class Program
{
    static void Main(string[] args)
    {
        using ThisDbContext dbContext = new();
        
        using var tr = dbContext.Database.BeginTransaction(); // 开启事务
        
        // 加锁(for update语法会添加行锁)
        var result = dbContext.Books.FromSqlInterpolated($"select * from books where price > 0 for update");
        ///
        Thread.Sleep(3000);
        if (result.Any())
        {
            result.ForEachAsync(e =>
            {
                e.Price += 1;
                Console.WriteLine(e.ToString());
            });
        }
        else
        {
            Console.WriteLine("books not found");
        }
        ///
        // 解锁
        dbContext.SaveChanges();
        // 提交事务
        tr.Commit();
        Console.ReadLine();
    }
}

乐观锁:单字段并发控制

实体类对应的配置类中设置并发令牌:

cs 复制代码
builder.Property(e => e.Price).IsConcurrencyToken(); // 设置并发令牌

对字段执行更新前,会检测字段的当前值与读取数据时的值是否相等,若相等则认为不存在并发冲突,可以进行更新,否则认为存在并发冲突,抛出异常(DbUpdateConcurrencyException)

示例:

cs 复制代码
internal class Program
{
    static void Main(string[] args)
    {
        using ThisDbContext dbContext = new();

        var result = dbContext.Books.Where(e => e.Price >= 1).SingleOrDefault();
        Thread.Sleep(3000);
        if (result != null)
        {
            try
            {
		        // 此处进行更新操作

		        // ---------------
                dbContext.SaveChanges();
                Console.WriteLine("数据更新完成");
            }
            catch (DbUpdateConcurrencyException ex)
            {
                Console.WriteLine("并发冲突");
                // 获取发生并发冲突时的数据实体
                var enrty = ex.Entries[0];
                var nowId = enrty.GetDatabaseValues().GetValue<int>("Id");
                //var nowId = enrty.GetDatabaseValuesAsync().Result.GetValue<int>("Id");
                Console.WriteLine($"并发冲突发生于Id: {nowId}的数据");
            }
        }
        else
        {
            Console.WriteLine("不存在符合条件的数据");
        }

        Console.ReadLine();
    }
}

注意点:

  1. 捕获DbUpdateConcurrencyException异常并处理

  2. 通过异常实例可以获取发生并发冲突时的数据实体

单字段并发控制无法识别ABA问题,且单字段的并发控制存在其局限性,所以通常推荐使用多字段并发控制

乐观锁:多字段并发控制

1.实体类添加行版本属性:

cs 复制代码
public byte[] RowVersion { get; set; }

版本控制属性的类型必需为byte\[\],在表中对应的行版本字段类型为timestamp(6)

2.实体类对应的配置类中设置行版本字段:

cs 复制代码
builder.Property(e => e.RowVersion).IsRowVersion();

当任意数据被更新时,行版本属性对应的表字段---行版本字段也会被更新(行版本字段的更新EFCore控制,无需人工更新)

并发状态下EFCore通过比对前后获得的行版本是否相同来判断是否存在并发冲突,以此实现并发控制

示例略

题外话

timestamp(6)在极高的并发情况下存在精度隐患,若想规避该隐患可将并发令牌设置为Guid,并在执行SaveChanges()之前人工更新并发令牌(注意,是并发令牌,在实体类对应的配置类中通过IsConcurrencyToken()设置)

该方式实质上是单并发控制,但只要记得在每次更新数据时都更新并发令牌,是可以实现多并发控制的效果的

相关推荐
.NET修仙日记15 天前
.NET EFCore批量插入性能优化实战:30秒 → 0.5秒
性能优化·c#·.net·.netcore·微软技术·efcore·踩坑实录
风口旁的猪1 个月前
一套可落地的 .NET 8 微服务/分布式工程实践
docker·consul·.net core·efcore·refit
CSharp精选营1 个月前
序列化 JSON 时崩了?99% 是 EF 延迟加载惹的祸,三种解法拿走不谢
efcore·延迟加载·json序列化·实战技巧
SunflowerCoder5 个月前
EF Core + PostgreSQL 配置表设计踩坑记录:从 23505 到 ChangeTracker 冲突
数据库·postgresql·c#·efcore
一个帅气昵称啊5 个月前
.Net通过EFCore和仓储模式实现统一数据权限管控并且相关权限配置动态生成
.net·efcore·仓储模式
wstcl6 个月前
通过EF Core将Sql server数据表移植到MySql
数据库·mysql·sql server·efcore
薛家明7 个月前
easy-query暴打efcore(包括其他所有orm),隐式Group看我如何在子查询做到极致的性能天花板
java·orm·efcore·easy-query·entityframeworkcore·dotnetcore
ArabySide10 个月前
【ASP.NET Core】探讨注入EF Core的DbContext在HTTP请求中的生命周期
后端·http·asp.net·asp.net core·efcore
ArabySide1 年前
【EF Core】 EF Core 批量操作的进化之路——从传统变更跟踪到无跟踪更新
数据库·.net·efcore
woflyoycm1 年前
EFcore8和Sql Server 2014冲突
sqlserver·asp.net·efcore·ef8·ef9