事務是個很精妙的存在,我們在數據層、服務層、業務邏輯層等多處地方都會使用到。
在這里我只說下TransactionScope這個微軟推薦使用的隱式事務。它是從Framework 2.0開始引入的一個事務管理類,在使用隱式事務時,事務完成前 程序應調用TransactionScope的Complete()方法,將事務提交,然后利用Dispose()釋放事務對象。若執行期間出現錯誤,事務將自動回滾。
比如:
using (ransactionScope scope = new TransactionScope())
{
//to do something
scope.Complete();
}
在這里個人建議用using來創建,因為using實現了IDispose接口,它會隱式的調用TransactionScope對象的Dispose方法,即使發生異常時也是如此,能確保在事務結束或者異常的時候也能正確的釋放資源。其實我們反編譯一下,它的內部實現就是一個try...finally代碼塊,這樣也就不難理解using的作用了。
說主題,在某地市的某庫升級中,為避免程序運行中產生臟數據以及數據更新不一致導致的重復同步情況,在可能產生上述問題的考慮下,我用這個TransactionScope來對上述的操作進行事務處理。在本機的測試環境中,運行結果是正常的,當然這個運行正常的前提是數據量較小的情況下,我每次只對一條或者十幾條數據的不同表進行insert和update。然而部署到生產環境針對真實數據運行之后,發現這個事務總是回滾,一直無法正常提交。程序也就沒法正常跑起來。因為生產環境中的數據有60W左右,insert一次、update一次,最后再insert一條同步語句,前2個操作都是比較耗時的。我切換回測試環境調試了一下,逐行運行,發現當執行完第一個insert之后,執行第二個update時發生異常了。這個異常由TransactionScope拋出,異常提示是:事務已中止。這個錯誤,在數據量小的情況下不會發生,數據量大一些就出現了,這個是不是和事務處理的時間長短有關呢?因為我明顯感覺到在這次調試的時候,執行的時間比之前數據量只有一條的時候長了很多,至少花費1分鍾以上。於是google一下,驗證了我的想法。
TransactionScope有些重載函數是可以接受TimeSpan類型的值,這個就是事務的超時時間了。當事務實現隔離的時候,事務范圍內的資源將會被鎖定,如果一些事務長期占有資源,那將很容易造成死鎖,為了避免這個問題,TransactionScope事務會定義一個超時限制,這個超時默認值為60秒。如果事務超過此時間,即使沒有發生異常,也會自動中止。上面問題的原因算是找到了,知道了原因,那么也就很好解決了。我們可以在Web.Config 中配置:
<configuration>
<system.transactions>
<defaultSettings timeout="00:05:00" />
</system.transactions>
</configuration>
或者在using的時候就定義好超時時間:
new TimeSpan(0, 5, 0)))
或者先初始化事物行為的附加信息,然后定義超時時間:
TransactionOptions tOpt = new TransactionOptions();
tOpt.IsolationLevel = IsolationLevel.ReadCommitted; //設置TransactionOptions模式
tOpt.Timeout = new TimeSpan(0, 5, 0); // 設置超時時間為5分鍾
using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, tOpt))
我這里定義的是5分鍾,其實整個過程處理起來也就第一次處理歷史數據需要1到2分鍾時間,以后每天只需處理幾十條數據,這個時間基本是秒級別的。
這里說明下, 超時時間如果設置為0時表示超時無限長。無限長的設置主要對調試有用,調試過程中可能要逐步通過代碼來隔離業務邏輯中的問題,並且在嘗試確定問題期間不希望所調試的事務超時。在所有其他情況下使用無限長的超時時一定要格外小心,因為它會覆蓋防止事務死鎖的保護。[這段說明摘抄至MSDN]
1、SQL事務
優點:執行效率最佳
限制:事務上下文僅在數據庫中調用,難以實現復雜的業務邏輯。
- CREATE PROCEDURE Tran1
- as
- begin tran
- set xact_abort on
- Insert Into trantest (id,test)values(1,'test')
- Insert Into trantest (id,test)values(2,'test')
- commit tran
- GO
- --set xact_abort on 表示遇到錯誤立即回滾
- --當然你也可以這么寫
- CREATE PROCEDURE tran1
- as
- begin tran
- insert into trantest(id,test)values(1,'test')
- if(@@error<>0)
- rollback tran
- else
- begin
- insert into trantest(id,test)values(2,'test')
- if(@@error<>0)
- rollback tran
- else
- commit tran
- end
- GO
- CREATE PROCEDURE Tran1
- as
- begin tran
- set xact_abort on
- Insert Into trantest (id,test)values(1,'test')
- Insert Into trantest (id,test)values(2,'test')
- commit tran
- GO
- --set xact_abort on 表示遇到錯誤立即回滾
- --當然你也可以這么寫
- CREATE PROCEDURE tran1
- as
- begin tran
- insert into trantest(id,test)values(1,'test')
- if(@@error<>0)
- rollback tran
- else
- begin
- insert into trantest(id,test)values(2,'test')
- if(@@error<>0)
- rollback tran
- else
- commit tran
- end
- GO
2、ADO.NET 事務
在ADO.NET 中,可以使用Connection 和Transaction 對象來控制事務。若要執行事務,請執行下列操作:
調用Connection 對象的BeginTransaction 方法來標記事務的開始。
將Transaction 對象分配給要執行的Command的Transaction 屬性。
執行所需的命令。
調用Transaction 對象的Commit 方法來完成事務,或調用Rollback 方法來取消事務。
優點:簡單,效率和數據庫事務差不多快。
缺點:事務執行在數據庫連接層上,所以你需要在事務過程中手動的維護一個連接。
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
- conn.Open();
- SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
- SqlCommand cmd = new SqlCommand();
- cmd.Connection = conn;
- cmd.Transaction = tx;
- try
- {
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試2','2')";
- cmd.ExecuteNonQuery();
- tx.Commit();
- }
- catch (Exception ex)
- {
- tx.Rollback();
- throw new Exception(ex.Message, ex);
- }
- finally
- {
- conn.Close();
- }
- SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString);
- conn.Open();
- SqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);
- SqlCommand cmd = new SqlCommand();
- cmd.Connection = conn;
- cmd.Transaction = tx;
- try
- {
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試2','2')";
- cmd.ExecuteNonQuery();
- tx.Commit();
- }
- catch (Exception ex)
- {
- tx.Rollback();
- throw new Exception(ex.Message, ex);
- }
- finally
- {
- conn.Close();
- }
3、TransactionScope事務
在.NET 2.0中新添加了一個名為System.Transactions的命名空間,其提供了一個“輕量級”的、易於使用的事務框架,通過這個框架可以大大簡化事務的操作。
這個框架提供了如下優點:
(1)在簡單(不涉及分布式)事務中也可以使用聲明式的事務處理方法,而不必使用Com+容器和目錄注冊。
(2)用戶根本不需要考慮是簡單事務還是分布式事務。它實現一種所謂自動提升事務機制(Promotable Transaction),會自動根據事務中涉及的對象資源判斷使用何種事務管理器。
TransactionScope事務類,它可以使代碼塊成為事務性代碼。並自動提升為分布式事務
優點:實現簡單,同時能夠自動提升為分布式事務
- TransactionOptions option = new TransactionOptions();
- option.IsolationLevel = IsolationLevel.ReadCommitted;
- using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
- {
- using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
- {
- conn.Open();
- SqlCommand cmd = new SqlCommand(conn);
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試2','2')";
- cmd.ExecuteNonQuery();
- }
- ts.Complete();
- }
- TransactionOptions option = new TransactionOptions();
- option.IsolationLevel = IsolationLevel.ReadCommitted;
- using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required,option))
- {
- using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["Test"].ConnectionString))
- {
- conn.Open();
- SqlCommand cmd = new SqlCommand(conn);
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試1','1')";
- cmd.ExecuteNonQuery();
- cmd.CommandText = "INSERT INTO [Test]([Name],[Value]) VALUES ('測試2','2')";
- cmd.ExecuteNonQuery();
- }
- ts.Complete();
- }
4、EnterpriseServices實現事務
就是利用com+實現自動處理事務。
添加引用System.EnterpriseServices.dll
using System.EnterpriseServices;
使用方法參考:http://support.microsoft.com/default.aspx?scid=kb;zh-cn;816141
隨便建立一個按鈕,在按鈕中進行如下操作:
- try
- {
- work1();
- work2();
- ContextUtil.SetComplete();
- }
- catch(System.Exception except)
- {
- ContextUtil.SetAbort();
- Response.Write(except.Message);
- }
- try
- {
- work1();
- work2();
- ContextUtil.SetComplete();
- }
- catch(System.Exception except)
- {
- ContextUtil.SetAbort();
- Response.Write(except.Message);
- }
然后在頁面中添加2個操作,模擬一下在邏輯層調用不同類中的操作的情況 :
- private void work1()
- {
- SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
- SqlCommand cmd1=new SqlCommand("Insert Into trantest (id,test)values(1,'test')",conn);
- conn.Open();
- cmd1.ExecuteNonQuery();
- conn.Close();
- }
- private void work2()
- {
- SqlConnection conn=new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["conn"]);
- SqlCommand cmd2=new SqlCommand("Insert Into trantest (id,test)values(2,'test')",conn);
- conn.Open();
- cmd2.ExecuteNonQuery();
- conn.Close();
- }
- 修改前台頁面在<%Page后面添加 Transaction="Required" 即可