EF Core併發控制

来源:https://www.cnblogs.com/ZYPLJ/archive/2023/09/03/17675494.html
-Advertisement-
Play Games

# EF Core併發控制 # 併發控制概念 1. 併發控制:避免多個用戶同時操作資源造成的併發衝突問題。 2. 最好的解決方案:非資料庫解決方案 3. 資料庫層面的兩種策略:悲觀、樂觀 # 悲觀鎖 悲觀併發控制一般採用行鎖 ,表鎖等排他鎖對資源進行鎖定,確保同時只有一個使用者操作被鎖定的資源。 E ...


EF Core併發控制

併發控制概念

  1. 併發控制:避免多個用戶同時操作資源造成的併發衝突問題。
  2. 最好的解決方案:非資料庫解決方案
  3. 資料庫層面的兩種策略:悲觀、樂觀

悲觀鎖

悲觀併發控制一般採用行鎖 ,表鎖等排他鎖對資源進行鎖定,確保同時只有一個使用者操作被鎖定的資源。

EF Core沒有封裝悲觀併發控制的使用,需要開發人員編寫原生SQL語句來使用悲觀併發控制。不同資料庫語法不一樣。

MySQL方案:select * from T_Houses where Id = 1 for update

如果有其他查詢操作也使用for update來查詢Id=1的這條數據的話,那些查詢就會被掛起,一直到針對這條數據的更新操作完成從而釋放這個行鎖,代碼才會繼續執行。

代碼實現

根據資料庫安裝對應Nuget包,Mysql如下:

也可以使用官方的,沒什麼影響

Pemelo.EntityFrameworkCore.MySql

House類

class House
{
	public long Id { get; set; }
	public string Name {get;set;}	
	public string Owner {get;set;}
}

HouseConfig類

public class HouseConfig:IEntityTypeConfiguration<House>
{
    public void Configure(EntityTypeBuilder<House> builder)
    {
        builder.ToTable("T_Houses");
        builder.Property(b => b.Name).IsRequired();
    }
}

DbContext類

public class MyDbContext:DbContext
{
     public DbSet<House> Houses { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
        var connString = "server=localhost;user=root;password=root;database=ef1";
        var serverVersion = new MySqlServerVersion(new Version(5, 7, 35));
        optionsBuilder.UseMySql(connString, serverVersion);
    }
}

遷移資料庫

然後執行資料庫遷移

安裝Nuget:Microsoft.EntityFrameworkCore.Design,Microsoft.EntityFrameworkCore.Tools

  • Add-Migration Init
  • Update-database

隨便給資料庫添加幾條信息

沒有悲觀版本

    public static void Main(string[] args)
    {
        Console.WriteLine("請輸入您的名字");
        string name = Console.ReadLine();
        using (MyDbContext db = new MyDbContext())
        {
            var h = db.Houses.Single(h => h.Id == 1);
            if (!string.IsNullOrEmpty(h.Owner))
            {
                if (h.Owner == name)
                {
                    Console.WriteLine("房子已經被你搶到了");
                }
                else
                {
                    Console.WriteLine($"房子已經被【{h.Owner}】占了");
                }
                return;
            }
            h.Owner = name;
            Thread.Sleep(10000);
            Console.WriteLine("恭喜你,搶到了");
            db.SaveChanges();
            Console.ReadLine();
        }
    }

image

image

可以看到實際上是jack搶到了,但是tom也列印了搶到!

有悲觀鎖的版本

鎖和事務是相關的,因此通過BeginTransactionAsync()創建一個事務,並且在所有操作完成後調用CommitAsync()提交事務

Console.WriteLine("請輸入您的名字");
string name = Console.ReadLine();
using MyDbContext db = new MyDbContext();
using (var tx = db.Database.BeginTransaction())
{
    Console.WriteLine($"{DateTime.Now}準備select from update");
    //加鎖
    var h = db.Houses.FromSqlInterpolated($"select * from T_houses where Id = 1 for update").Single();
    Console.WriteLine($"{DateTime.Now}完成select from update");
    if (!string.IsNullOrEmpty(h.Owner))
    {
        if (h.Owner == name)
        {
            Console.WriteLine("房子已經被你搶到了");
        }
        else
        {
            Console.WriteLine($"房子已經被【{h.Owner}】占了");
        }
        Console.ReadKey();
        return;
    }
    h.Owner = name;
    Thread.Sleep(5000);
    Console.WriteLine("恭喜你,搶到了");
    db.SaveChanges();
    Console.WriteLine($"{DateTime.Now}保存完成");
    //提交事務
    tx.Commit();
    Console.ReadKey();
}

image

可以看到tom 在27:58秒的時候完成了鎖,所以程式提交的時候是tom搶到了,而不是jack,當執行SaveChanges()之前,行的鎖會一直存在,直到SaveChanges()運行完成才會釋放鎖,這時jack才會完成鎖。

image

問題

  1. 悲觀併發控制的使用比較簡單。
  2. 鎖是獨占、排他的,如果系統併發量很大的話,會嚴重影響性能,如果使用不當的話,甚至會導致死鎖。
  3. 不同資料庫的語法不一樣。

樂觀鎖

原理

Update T_House set Owner = 新值 where Id = 1 and Owner = 舊值

當Update的時候,如果資料庫中的Owner值已經被其他操作更新為其他值了,那麼where語句的值就會為false,因此這個Update語句影響的行數就是0,EF Core就知道發生併發衝突了,因此SaveChanges()方法就會拋出DbUpdateConcurrencyException異常。

EF Core配置

  1. 把被併發修改的屬性使用IsConcurrencyToken()設置為併發令牌,

  2. public class HouseConfig:IEntityTypeConfiguration<House>
    {
        public void Configure(EntityTypeBuilder<House> builder)
        {
            builder.ToTable("T_Houses");
            builder.Property(b => b.Name).IsRequired();
            builder.Property(h => h.Owner).IsConcurrencyToken(); //這裡設置列
        }
    }
    
  3. Console.WriteLine("請輸入您的名字");
    string name = Console.ReadLine();
    using (MyDbContext db = new MyDbContext())
    {
        var h = db.Houses.Single(h => h.Id == 1);
        if (!string.IsNullOrEmpty(h.Owner))
        {
            if (h.Owner == name)
            {
                Console.WriteLine("房子已經被你搶到了");
            }
            else
            {
                Console.WriteLine($"房子已經被【{h.Owner}】占了");
            }
    
            Console.ReadKey();
            return;
        }
        h.Owner = name;
        Thread.Sleep(5000);
        try
        {
            db.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            Console.WriteLine("併發訪問衝突");
            var entry1 = ex.Entries.First();
            string newValue = entry1.GetDatabaseValues().GetValue<string>("Owner");
            Console.WriteLine($"被{newValue}搶先了");
        }
        Console.ReadLine();
    }
    

效果截圖

EF 生成的sql語句

image

image

多欄位RowVersion

  1. SQLServer資料庫可以用一個byte[]類型的屬性做併發令牌屬性,然後使用IsRowVersion()把這個屬性設置為RowVersion類型,這樣這個屬性對應的資料庫列就會被設置為ROWVERSION類型。對於這個類型的列,在每次插入或更新行時,資料庫會自動為這一行的ROWVERSION類型的列其生成新值。
  2. 在SQLServer中,timestamp和rowversion是同一種類型的不同別名而已。

註意這裡換成SQLServer資料庫了!

實體類及配置

public class House
{
    public long Id { get; set; }
    public string Name { get; set; }
    public string? Owner {get;set;}
    public byte[]? RowVer{get;set;}
}
//builder.Property(h => h.Owner).IsConcurrencyToken(); //刪除掉
builder.Property(h=>h.RowVer).IsRowVersion();

效果截圖

image

image

概念

  1. 在MySQL(某些版本)等資料庫中雖然也有類似的timestamp類型,但是由於timestamp類型的精度不夠,並不適合在高併發的系統。
  2. 非SQLServer中,可以將併發令牌列的值更新為Guid的值
  3. 修改其他屬性值的同時,使用h1.Rowver = Guid.NewGuid()手動更新併發令牌屬性的值。

總結

  1. 樂觀併發控制能夠避免悲觀鎖帶來的性能、死鎖等問題,因此推薦使用樂觀併發控制而不是悲觀鎖。
  2. 如果有一個確定的欄位要被進行併發控制,那麼使用IsConcurrencyToken()把這個欄位設置為併發令牌即可。
  3. 如果無法確定一個唯一的併發令牌列,那麼就可以引入一個額外的屬性設置為併發令牌,並且在每次更新數據的時候,手動更新這一列的值。如果用的是SQLServer資料庫,那麼也可以採用RowVersion列,這樣就不用開發者手動來在每次更新數據的時候,手動更新併發令牌的值了。

參考鏈接

每日一道面試題

  1. 什麼是裝箱和拆箱?

    答:從值類型介面轉換到引用類型裝箱。從引用類型轉換到值類型拆箱。

  2. 抽象類和介面的相同點和不同點有哪些?何時必須聲明一個類為抽象類?

    相同點:

    1. 都是用來實現抽象和多態的機制。
    2. 都不能被實例化,只能被繼承或實現。
    3. 都可以包含抽象方法,即沒有具體實現的方法。
    4. 都可以被子類繼承或實現,併在子類中實現抽象方法。

    不同點:

    1. 抽象類可以包含非抽象方法,而介面只能包含抽象方法。
    2. 類只能繼承一個抽象類,但可以實現多個介面。
    3. 抽象類的子類可以選擇性地覆蓋父類的方法,而介面的實現類必須實現介面中定義的所有方法。
    4. 抽象類可以有構造方法,而介面不能有構造方法。、

    一個類必須聲明為抽象類的情況:

    1. 當類中存在一個或多個抽象方法時,類必須聲明為抽象類。
    2. 當類需要被繼承,但不能被實例化時,類必須聲明為抽象類。
    3. 當類中的某些方法需要在子類中實現,而其他方法已經有了具體實現時,類可以聲明為抽象類。

    總結:抽象類和介面都是實現抽象和多態的機制,但抽象類更適合用於一些具有公共實現的類,而介面更適合用於定義一組相關的方法,供多個類實現。抽象類可以包含非抽象方法和構造方法,而介面只能包含抽象方法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## 1、使用互斥量 在C++中,我們通過構造`std::mutex`的實例來創建互斥量,調用成員函數`lock()`對其加鎖,調用`unlock()`解鎖。但通常更推薦的做法是使用標準庫提供的類模板`std::lock_guard`,它針對互斥量實現了RAII手法:在構造時給互斥量加鎖,析構時解鎖 ...
  • ## 預編譯頭文件 在 Visual Studio 中創建新項目時,會在項目中添加一個名為 pch.h 的“預編譯標頭文件”。 (在 Visual Studio 2017 及更高版本中,該文件名為 stdafx.h)此文件的目的是加快生成過程。 應在此處包含任何穩定的標頭文件,例如標準庫標頭(如 ) ...
  • 集合是一種無序、可變的數據結構,它也是一種變數類型,集合用於存儲唯一的元素。集合中的元素不能重覆,並且沒有固定的順序。在Python 提供了內置的 `set` 類型來表示集合,所以關鍵字`set`就是集合的意思。 你可以使用大括弧 `{}` 或者 `set()` 函數來創建一個集合。 ```pyth ...
  • `Matplotlib`的**坐標軸**是用於在繪圖中表示數據的位置的工具。 坐標軸是圖像中的水平和垂直線,它們通常表示為 x 軸和 y 軸。坐標軸的作用是幫助觀察者瞭解圖像中數據的位置和大小,通常標有數字或標簽,以指示特定的值在圖像中的位置。 # 1. 坐標軸範圍 `Matplotlib`繪製圖形 ...
  • 本文主要介紹 RocketMQ 的安裝部署,文中所使用到的軟體版本:RocketMQ 5.1.3、CentOS 7.9.2009。 1、RocketMQ 部署模型 1.1、部署模型說明 Apache RocketMQ 部署架構上主要分為四部分: A、生產者 Producer 發佈消息的角色。Prod ...
  • > 講解Go語言從編譯到執行全周期流程,每一部分都會包含豐富的技術細節和實際的代碼示例,幫助大家理解。 > 關註微信公眾號【TechLeadCloud】,分享互聯網架構、雲服務技術的全維度知識。作者擁有10+年互聯網服務架構、AI產品研發經驗、團隊管理經驗,同濟本復旦碩,復旦機器人智能實驗室成員,阿 ...
  • 最近在公司的項目中,編寫了幾個自定義的 Exception 類。提交 PR 的時候,sonarqube 提示這幾個自定義異常不符合 ISerializable patten. 花了點時間稍微研究了一下,把這個問題解了。今天在此記錄一下,可能大家都會幫助到大家。 ## 自定義異常 編寫一個自定義的異常 ...
  • 我們在使用一些需要購買版權的軟體產品時,或者我們做的商業軟體需要進行售賣,為了收取費用,一般需要一個軟體使用許可證,然後輸入這個許可到軟體里就能夠使用軟體。簡單的是一串序列碼或者一個許可證文件,複雜的是一個定製化插件包。於是有的小伙伴就開始好奇這個許可是怎麼實現的,特別是在離線情況下它是怎麼給軟體授... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...