文章目錄
- 前言
- 一、樂觀并發的核心思想
- 二、實現方法
- 1)使用并發令牌(Concurrency Token)
- 2)處理并發沖突
- 三、工作原理
- 四、適用場景
- 五、與悲觀并發的對比
- 六、最佳實踐
- 總結
前言
Entity Framework (EF) Core 默認支持 樂觀并發控制(Optimistic Concurrency Control),它通過檢測數據沖突(而不是顯式加鎖)來保證數據一致性。
一、樂觀并發的核心思想
- 無鎖機制:允許多個事務同時讀取和修改數據,提交時檢查數據是否被其他事務修改。
- 沖突檢測:通過版本號(RowVersion)或字段值比較,如果數據已被修改,則拋出
DbUpdateConcurrencyException。
二、實現方法
1)使用并發令牌(Concurrency Token)
-
為實體添加一個并發標記字段(如 RowVersion),每次更新時檢查該字段是否與數據庫中的值一致。
-
示例:通過 [ConcurrencyCheck] 特性標記字段
public class House{public long Id { get; set; }public string Name{ get; set; }[ConcurrencyCheck] // 標記為并發令牌public string Owner{ get; set; }}
-
示例:或通過 Fluent API 配置
protected override void OnModelCreating(ModelBuilder modelBuilder) {modelBuilder.Entity<House>().Property(p => p.Owner)//.IsRowVersion() // 自動映射為 SQL Server 的 `rowversion` 類型.IsConcurrencyToken(); // 標記為并發令牌 }
public class House {public long Id { get; set; }public string Name{ get; set; } public string Owner{ get; set; }public byte[] RowVersion { get; set; } } public void Configure(EntityTypeBuilder<House> builder) {builder.ToTable("T_Houses");builder.Property(h=>h.Name).IsRequired();//builder.Property(h=>h.Owner).IsConcurrencyToken();builder.Property(h=>h.RowVersion).IsConcurrencyToken().IsRowVersion(); }
2)處理并發沖突
-
當檢測到數據已被修改時,EF Core 會拋出 DbUpdateConcurrencyException,開發者需捕獲并處理沖突。
try {var house = await context.Houses.FindAsync(houseId);house.Owner = "Tom";await context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) {// 處理沖突var entry = ex.Entries.Single();var databaseValues = await entry.GetDatabaseValuesAsync();if (databaseValues == null){// 數據已被刪除Console.WriteLine("數據已被刪除!");}else{// 獲取當前數據庫中的值var currentValues = databaseValues.ToObject() as House;// 策略1:使用數據庫最新值覆蓋當前修改entry.OriginalValues.SetValues(databaseValues);// 策略2:合并值(手動處理沖突)// currentValues 是數據庫中的最新值// entry.Entity 是當前嘗試提交的值// 例如:保留用戶修改的某些字段,合并其他字段entry.Entity.Owner= "Tom";entry.Entity.RowVersion = currentValues.RowVersion;// 重新提交await context.SaveChangesAsync();} }
三、工作原理
-
查詢數據:讀取數據時,EF Core 會記錄并發令牌的原始值(如 RowVersion)。
-
更新數據:提交修改時,生成的 SQL 會包含 WHERE 條件,檢查并發令牌是否未被修改。
UPDATE [T_Houses] SET [Owner] = @p0 WHERE [Id] = @p1 AND [Owner] = @p2;
-
沖突檢測:如果受影響的行數為 0(即 RowVersion 不匹配),拋出異常。
四、適用場景
- 低沖突概率:適合大部分時間數據競爭較少的場景。
- 高吞吐需求:避免鎖機制的開銷,提升性能。
- 分布式系統:無鎖機制更適合跨服務的并發操作。
五、與悲觀并發的對比
樂觀并發 | 悲觀并發 | |
---|---|---|
實現方式 | 版本號或字段檢查(RowVersion、并發令牌) | 顯式加鎖(事務+鎖機制) |
性能 | 低沖突時更高效 | 高競爭時可能更高效 |
復雜度 | EF Core 內置支持,自動檢測沖突 | 需要手動管理鎖和事務 |
數據競爭 | 可能需重試或合并數據 | 強制串行化,避免沖突 |
六、最佳實踐
- 選擇并發令牌
優先使用 RowVersion(自動遞增的二進制字段),而非業務字段。
若使用業務字段(如 LastUpdatedTime),需確保其值在每次更新時被修改。 - 沖突處理策略
客戶端優先:強制覆蓋數據庫的值(需謹慎)。
數據庫優先:放棄當前修改,使用最新值。
合并值:手動合并沖突字段(如用戶編輯的字段優先)。 - 重試機制
在分布式系統中,可為關鍵操作添加重試邏輯(如 Polly 庫)。
總結
EF Core 的樂觀并發通過版本號或字段值檢測沖突,無需顯式加鎖,適合低競爭場景。通過 ConcurrencyCheck 或 IsRowVersion() 配置并發令牌,并在沖突時通過 DbUpdateConcurrencyException 實現靈活的數據合并或重試邏輯。
- 樂觀并發控制能夠避免悲觀鎖帶來的性能、死鎖等問題,因此推薦使用并發控制而不是悲觀鎖。
- 如果有一個確定的字段要被進行并發控制,那么使用IsConcurrencyToken()把這個字段設置為并發令牌即可;
- 如果無法確定一個唯一的并發令牌列,那么就可以引入一個額外的屬性設置為并發令牌,并且在每次更新數據庫的時候,手動更新這一列的值;如果用的是SQL Server數據庫,那么也可以采用RowVersion列,設置為并發令牌列。