發(fā)文章
發(fā)文工具
撰寫
網(wǎng)文摘手
文檔
視頻
思維導(dǎo)圖
隨筆
相冊(cè)
原創(chuàng)同步助手
其他工具
圖片轉(zhuǎn)文字
文件清理
AI助手
留言交流
最近在園子里看到一篇關(guān)于TransactionScope的文章,發(fā)現(xiàn)事務(wù)和并發(fā)控制是新接觸Entity Framework和Transaction Scope的園友們不易理解的問題,遂組織此文跟大家共同探討。
首先事務(wù)的ACID特性作為最基礎(chǔ)的知識(shí)我想大家都應(yīng)該知道了。ADO.NET的SQLTransaction就是.NET框架下訪問SqlServer時(shí)最底層的數(shù)據(jù)庫事務(wù)對(duì)象,它可以用來將多次的數(shù)據(jù)庫訪問封裝為“原子操作”,也可以通過修改隔離級(jí)別來控制并發(fā)時(shí)的行為。TransactionScope則是為了在分布式數(shù)據(jù)節(jié)點(diǎn)上完成事務(wù)的工具,它經(jīng)常被用在業(yè)務(wù)邏輯層將多個(gè)數(shù)據(jù)庫操作組織成業(yè)務(wù)事務(wù)的場景,可以做到透明的可分布式事務(wù)控制和隱式失敗回滾。但與此同時(shí)也經(jīng)常有人提到TransactionScope有性能和部署方面的問題,關(guān)于這一點(diǎn),根據(jù)MSDN的 Using the TransactionScope Class 的說法,當(dāng)一個(gè)TransactionScope包含的操作是同一個(gè)數(shù)據(jù)庫連接時(shí),它的行為與SqlTransaction是類似的。當(dāng)它在多個(gè)數(shù)據(jù)庫連接上進(jìn)行數(shù)據(jù)操作時(shí),則會(huì)將本地?cái)?shù)據(jù)庫事務(wù)提升為分布式事務(wù),而這種提升要求各個(gè)節(jié)點(diǎn)均安裝并啟動(dòng)DTC服務(wù)來支持分布式事務(wù)的協(xié)調(diào)工作,它的性能與本地?cái)?shù)據(jù)庫事務(wù)相比會(huì)低很多,這也是CAP定律說的分布式系統(tǒng)的Consistency和Availability不可兼得的典型例子。所以當(dāng)我們選擇是否使用TransactionScope時(shí),一定要確認(rèn)它會(huì)不會(huì)導(dǎo)致不想發(fā)生的分布式事務(wù),也應(yīng)該確保事務(wù)盡快做完它該做的事情,為了確認(rèn)事務(wù)是否被提升我們可以用SQL Profiler去跟蹤相關(guān)的事件。
然后再來看一看Entity Framework,其實(shí)EF也跟事務(wù)有關(guān)系。它的Context概念來源于Unit of Work模式,Context記錄提交前的所有Entity變化,并在SaveChanges方法調(diào)用時(shí)發(fā)起真正的數(shù)據(jù)庫操作,SaveChanges方法在默認(rèn)情況下隱含一個(gè)事務(wù),并且試圖使用樂觀并發(fā)控制來提交數(shù)據(jù),但是為了進(jìn)行并發(fā)控制我們需要將Entity Property的ConcurrencyMode設(shè)置為Fixed才行,否則EF不理會(huì)在此Entity上面發(fā)生的并發(fā)修改,這一點(diǎn)可以參考MSDN Saving Changes and Managing Concurrency。微軟推薦大家使用以下方法來捕獲沖突的并發(fā)操作,并使用RefreshMode來選擇覆蓋或丟棄失敗的操作:
1 try 2 { 3 // Try to save changes, which may cause a conflict. 4 int num = context.SaveChanges(); 5 Console.WriteLine("No conflicts. " + 6 num.ToString() + " updates saved."); 7 } 8 catch (OptimisticConcurrencyException) 9 { 10 // Resolve the concurrency conflict by refreshing the 11 // object context before re-saving changes. 12 context.Refresh(RefreshMode.ClientWins, orders); 13 14 // Save changes. 15 context.SaveChanges(); 16 Console.WriteLine("OptimisticConcurrencyException " 17 + "handled and changes saved"); 18 }
當(dāng)然除了樂觀并發(fā)控制我們還可以對(duì)沖突特別頻繁、沖突解決代價(jià)很大的用例進(jìn)行悲觀并發(fā)控制。悲觀并發(fā)基本思想是不讓數(shù)據(jù)被同時(shí)離線修改,也就是像源碼管理里面“加鎖”功能一樣,碼農(nóng)甲鎖上了這個(gè)文件,乙就不能再修改了,這樣一來這個(gè)文件就不可能發(fā)生沖突,悲觀并發(fā)控制實(shí)現(xiàn)的方式比如數(shù)據(jù)行加IsLocked字段等。
最后為了進(jìn)行多Context事務(wù),當(dāng)然還可以混合使用TransactionScope和EF。
好了理論簡單介紹完,下面的例子是幾種不同的方法對(duì)并發(fā)控制的效果,需求是每個(gè)Member都有個(gè)HasMessage字段,初始為False,我們需要給其中一個(gè)Member加入唯一一條MemberMessage,并將Member.HasMessage置為True。
建庫腳本:
1 CREATE DATABASE [TransactionTest] 2 GO 3 4 USE [TransactionTest] 5 GO 6 7 CREATE TABLE [dbo].[Member]( 8 [Id] [int] IDENTITY(1,1) NOT NULL, 9 [Name] [nvarchar](32) NOT NULL, 10 [HasMessage] [bit] NOT NULL, 11 CONSTRAINT [PK_Member] PRIMARY KEY CLUSTERED 12 ( 13 [Id] ASC 14 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 15 ) ON [PRIMARY] 16 GO 17 SET IDENTITY_INSERT [dbo].[Member] ON 18 INSERT [dbo].[Member] ([Id], [Name], [HasMessage]) VALUES (1, N'Tom', 0) 19 INSERT [dbo].[Member] ([Id], [Name], [HasMessage]) VALUES (2, N'Jerry', 0) 20 SET IDENTITY_INSERT [dbo].[Member] OFF 21 22 CREATE TABLE [dbo].[MemberMessage]( 23 [Id] [int] IDENTITY(1,1) NOT NULL, 24 [Message] [nvarchar](128) NOT NULL, 25 [MemberId] [int] NOT NULL, 26 CONSTRAINT [PK_MemberMessage] PRIMARY KEY CLUSTERED 27 ( 28 [Id] ASC 29 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 30 ) ON [PRIMARY] 31 GO 32 33 ALTER TABLE [dbo].[MemberMessage] WITH CHECK ADD CONSTRAINT [FK_MemberMessage_Member] FOREIGN KEY([MemberId]) 34 REFERENCES [dbo].[Member] ([Id]) 35 GO 36 ALTER TABLE [dbo].[MemberMessage] CHECK CONSTRAINT [FK_MemberMessage_Member] 37 GO
方法1:不使用TransactionScope,只依賴Entity各字段的默認(rèn)并發(fā)控制。
Context和Entity定義
1 public class MyDbContext : DbContext 2 { 3 public MyDbContext() : base("TransactionTest") { } 4 5 public MyDbContext(string connectionString) : 6 base(connectionString) 7 { 8 9 } 10 11 public DbSet<Member> Members { get; set; } 12 } 13 14 [Table("Member")] 15 public class Member 16 { 17 [Key] 18 public int Id { get; set; } 19 20 public string Name { get; set; } 21 22 public bool HasMessage { get; set; } 23 24 public virtual ICollection<MemberMessage> Messages { get; set; } 25 } 26 27 [Table("MemberMessage")] 28 public class MemberMessage 29 { 30 [Key] 31 public int Id { get; set; } 32 33 public string Message { get; set; } 34 35 public int MemberId { get; set; } 36 37 [ForeignKey("MemberId")] 38 public virtual Member Member { get; set; } 39 }
測試代碼
1 try 2 { 3 using (var context = new MyDbContext()) 4 { 5 var tom = context.Members.FirstOrDefault(m => m.Id == 1); 6 if (tom != null && !tom.HasMessage) 7 { 8 Console.WriteLine("Press Enter to Insert MemberMessage..."); 9 Console.ReadLine(); 10 tom.Messages.Add(new MemberMessage() 11 { 12 Message = "Hi Tom!" 13 }); 14 tom.HasMessage = true; 15 context.SaveChanges(); 16 Console.WriteLine("Insert Completed!"); 17 } 18 } 19 } 20 catch (Exception ex) 21 { 22 Console.WriteLine("Insert Failed: " + ex); 23 }
同時(shí)運(yùn)行兩個(gè)程序,結(jié)果是無法確保不重復(fù)插入
通過分析不難發(fā)現(xiàn),該場景的并發(fā)控制關(guān)鍵就在于插入前檢查HasMessage如果是False,則插入MemberMessage后更新Member.HasMessage字段時(shí)需要再次檢查數(shù)據(jù)庫中HasMessage字段是否為False,如果為True就是有其他人并發(fā)的更改了該字段,本次保存應(yīng)該回滾或做其他處理。所以為此需要有針對(duì)性的加入并發(fā)控制。
方法2:給HasMessage字段加上并發(fā)檢查
1 [Table("Member")] 2 public class Member 3 { 4 [Key] 5 public int Id { get; set; } 6 7 public string Name { get; set; } 8 9 [ConcurrencyCheck] 10 public bool HasMessage { get; set; } 11 12 public virtual ICollection<MemberMessage> Messages { get; set; } 13 }
仍然使用方法1的測試代碼,結(jié)果則是其中一次數(shù)據(jù)插入會(huì)拋出OptimisticConcurrencyException,也就是說防止重復(fù)插入數(shù)據(jù)的目的已經(jīng)達(dá)到了。
那回過頭來看看是否可以使用TransactionScope對(duì)EF進(jìn)行并發(fā)控制,于是有方法3:使用TransactionScope但不給Entity加入并發(fā)檢查
Context和Entity的定義與方法1完全一致,測試代碼為
1 try 2 { 3 using (var scope = new System.Transactions.TransactionScope()) 4 { 5 using (var context = new MyDbContext()) 6 { 7 var tom = context.Members.FirstOrDefault(m => m.Id == 1); 8 if (tom != null && !tom.HasMessage) 9 { 10 Console.WriteLine("Press Enter to Insert MemberMessage..."); 11 Console.ReadLine(); 12 tom.Messages.Add(new MemberMessage() 13 { 14 Message = "Hi Tom!" 15 }); 16 tom.HasMessage = true; 17 context.SaveChanges(); 18 Console.WriteLine("Insert Completed!"); 19 } 20 } 21 scope.Complete(); 22 } 23 } 24 catch (Exception ex) 25 { 26 Console.WriteLine("Insert Failed: " + ex); 27 }
同樣啟動(dòng)兩個(gè)程序測試,發(fā)現(xiàn)其中一次保存操作拋出DbUpdateException,其內(nèi)部原因是Transaction死鎖導(dǎo)致該操作被作為犧牲者。所以看起來也可以達(dá)到并發(fā)控制的效果,這種方式的優(yōu)點(diǎn)是不需要去仔細(xì)辨別業(yè)務(wù)中哪些操作會(huì)導(dǎo)致字段的并發(fā)更新沖突,所有的Entity都可以不加ConcurrencyCheck,缺點(diǎn)則是當(dāng)沖突不多的時(shí)候這種死鎖競爭協(xié)調(diào)與樂觀并發(fā)控制相比性能會(huì)低些。
最后為了完備測試各種組合,我們?cè)囈辉?strong>方法4:既使用TransactionScope,又在HasMessage字段上加入ConcurrencyCheck,Entity代碼參考方法1,測試代碼則參考方法3,結(jié)果仍然是TransactionScope檢測到死鎖并選擇其中一個(gè)競爭者拋出異常。
結(jié)論:
來自: 昵稱10504424 > 《工作》
0條評(píng)論
發(fā)表
請(qǐng)遵守用戶 評(píng)論公約
Entity Framework中的批量提交與分布式事務(wù)處理
Entity Framework中的批量提交與分布式事務(wù)處理。using (TransactionScope scope = new TransactionScope()){ //Do something with context1 //Do something with context2 //Save and discard c...
僅此一文讓你明白事務(wù)隔離級(jí)別、臟讀、不可重復(fù)讀、幻讀
僅此一文讓你明白事務(wù)隔離級(jí)別、臟讀、不可重復(fù)讀、幻讀。在完全未隔離事務(wù)的情況下,兩個(gè)事務(wù)更新同一條數(shù)據(jù)資源,某一事務(wù)完成,另一...
C#
前面三節(jié)是事務(wù)的基礎(chǔ),主要介紹事務(wù)的由來以及事務(wù)管理器的總體結(jié)構(gòu),分別講述輕量級(jí)事務(wù)管理器(LTM)、核心事務(wù)管理器(KTM)、分布式事務(wù)協(xié)調(diào)器(DTC)的差別。事務(wù)以相互隔離的方式執(zhí)行,事務(wù)以外...
使用KTM(內(nèi)核事務(wù)管理器)進(jìn)行文件事務(wù)處理
在本人最近的幾篇關(guān)于事務(wù)處理的文章中,從事務(wù)處理的整體概念到具體的C#代碼的實(shí)踐操作基本上都已經(jīng)能滿足日常的開發(fā)需求。在前幾篇文章中都是使用的LTM本地事務(wù)管理器,然后進(jìn)行事務(wù)范圍類的多個(gè)持久...
WF4.0 基礎(chǔ)篇 (十五) TransactionScope 事物容器
WF4.0 基礎(chǔ)篇 (十五) TransactionScope 事物容器。WriteLine("aborted,Reason:{0}", e.Abort;多個(gè)[TransactionScope]嵌套的時(shí)...
網(wǎng)站建設(shè)后如何正確運(yùn)營?
1.web常見的優(yōu)化題目硬件投資不是主要瓶頸,主要的題目是網(wǎng)站的可維護(hù)性和可擴(kuò)展性。推薦采用延遲提交的策略,來避免分布式事務(wù)的題目。weblogic是一個(gè)好的J2EE產(chǎn)品,對(duì)這種有事務(wù)聯(lián)系關(guān)系的負(fù)載均衡,...
各種事務(wù)處理方式大比拼
數(shù)據(jù)庫事務(wù)是其他事務(wù)模型的基礎(chǔ),當(dāng)一個(gè)事務(wù)創(chuàng)建時(shí)不同數(shù)據(jù)庫系統(tǒng)都有自己的規(guī)則。COM+ 使用 Microsoft Distributed Transaction Coordinator(DTC)作為事務(wù)管理器和事務(wù)協(xié)調(diào)器在分布式環(huán)境中運(yùn)行事...
“可信數(shù)據(jù)庫”評(píng)測結(jié)果:阿里云、華為云、騰訊云、火山引擎等28家
本次通過基礎(chǔ)能力評(píng)測的包括6款搜索型數(shù)據(jù)庫、5款分布式事務(wù)型數(shù)據(jù)庫、3款分布式分析型數(shù)據(jù)庫、3款全密態(tài)數(shù)據(jù)庫、2款時(shí)序數(shù)據(jù)庫、2款時(shí)...
Masstransit開發(fā)基于消息傳遞的分布式應(yīng)用
簡單來說,Masstransit提供了使用消息隊(duì)列場景的一種抽象,也就是說,如果你有使用消息隊(duì)列的需求,都可以通過Masstransit來完成,當(dāng)然...
微信掃碼,在手機(jī)上查看選中內(nèi)容