TransactionScope與COMMIT TRAN
3 |
手邊的專案遇到一個情境: TransactionScope中包含兩段SQL操作,因未共用連線,預期將啟動MSDTC分散式交易。而第二段SQL操作使用了T-SQL的BEGIN TRAN與COMMIT TRAN。若TrasactionScope未能呼叫TransactionScope.Complete(),第二段COMMIT TRAN的DB寫入動作是否會Rollback? 這個議題,引起討論。
依我的理解,BEGIN TRAN/COMMIT TRAN形成的子交易,仍會參與當下的環境交易(Ambient Transaction,包在using TransactionScope範圍的資料庫操作,預設會參與該TransactionScope所建立的交易,該交易即所謂環境交易[參考]),當環境交易Rollback(亦即TransactionScope未呼叫Complete()),子交易將會跟著Rollback。
由於信仰不夠堅定,無法100%確認自己的認知無誤,故設計實驗進行驗證:
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Transactions;
namespace TestTSqlCommit
{
class Program
{
static void truncateTable()
{
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = "TRUNCATE TABLE LockLab";
cmd.ExecuteNonQuery();
}
}
static string cs = "Data Source=...省略...;Application Name={0}";
//使用隨機Application Name建立連線字串
static string getDiffCnStr()
{
return string.Format(cs, Guid.NewGuid());
}
static void Main(string[] args)
{
//將LockLab資料表清空
truncateTable();
//建立TransactionScope
using (TransactionScope tx = new TransactionScope())
{
try
{
//以隨機式連線字串建立連線,確保觸發分散式交易
//參考: http://blog.darkthread.net/post-2010-11-12-msdtc-2008.aspx
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText =
"INSERT INTO LockLab VALUES ('TEST','201302', 1)";
cmd.ExecuteNonQuery();
}
//第二段SQL 使用COMMIT TRAN
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = @"
BEGIN TRAN
INSERT INTO LockLab VALUES ('JEFF','201302', 1)
COMMIT TRAN
";
cmd.ExecuteNonQuery();
}
throw new ApplicationException("刻意觸發錯誤!");
//tx.Complete()不會被執行,Transaction應Rollback
tx.Complete();
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
Console.Read();
}
}
}
測試結果如同預期,TEST與JEFF兩筆資料均未寫入,證實TransactionScope中即便BEGIN TRAN/COMMIT TRAN仍會參與交易,可被Rollback。
最後補充,如果我們希望子交易不要依附於外層TransactionScope,以實現不論外層Transaction Rollback與否,子交易需能自由控制是否Commit的結果。為此,我們可宣告一個TransactionScopeOption.RequiresNew模式的TransactionScope將子交易包起來,則其中將自成一個獨立交易範圍,不受環境交易影響。沿用上例,如要將第二段獨立自成交易,可修改為:
using (TransactionScope tx2 =
new TransactionScope(TransactionScopeOption.RequiresNew))
{
using (SqlConnection cn = new SqlConnection(getDiffCnStr()))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = @"
BEGIN TRAN
INSERT INTO LockLab VALUES ('JEFF','201302', 1)
COMMIT TRAN
";
cmd.ExecuteNonQuery();
}
tx2.Complete();
}
如此,即便tx.Complete()未執行,JEFF資料也會寫入DB。
Comments
# by Ivan
那麼如果子交易為TransactionScopeOption.RequiresNew, 當子交易rollback , 可會令母交易rollback呢?
# by Tom Tang
@Ivan 當使用 RequiresNew, 已經沒有所謂『母子』的關係。 兩個 scope 毫無相關,不論外層或內層誰 complete / rollback,都不影響另一者。
# by Ho.Chun
有點不太懂,所以 TransactionScope 可以用來處理跨 DB 的 Transaction 嗎 ?