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()设置)

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

相关推荐
ArabySide16 天前
【ASP.NET Core】探讨注入EF Core的DbContext在HTTP请求中的生命周期
后端·http·asp.net·asp.net core·efcore
ArabySide3 个月前
【EF Core】 EF Core 批量操作的进化之路——从传统变更跟踪到无跟踪更新
数据库·.net·efcore
woflyoycm3 个月前
EFcore8和Sql Server 2014冲突
sqlserver·asp.net·efcore·ef8·ef9
CSharp精选营3 个月前
ASP.NET Core EFCore 属性配置与DbContext 详解
.net core·efcore·dbcontext
tiankong12138 个月前
个人笔记:ROM数据库框架EFCore使用示例,运行通过,附源码
数据库·笔记·efcore
GISer_Qing8 个月前
ASP.NET Core8.0学习笔记(二十四)——EF Core级联插入与删除
.netcore·linq·efcore
万雅虎9 个月前
.NET9 EFcore支持早期MSSQL数据库 ROW_NUMBER()分页
sqlserver·efcore·net9
csdn_aspnet9 个月前
.NET 8 中 Entity Framework Core 的使用
efcore·ef·.net8.0
以明志、1 年前
efCore+Mysql8,使用及表的迁移
.netcore·orm·efcore