KB-Transaction in Linq to SQL
關於Ling to SQL如何處理交易,一直有個疑問 -- 當多筆資料的更新動作必須包成Transaction時,在Linq to SQL中應如何處理?
花了點時間研究,心得如下:
- 當連續進行多筆資料更新,再一次DataContext.SubmitChanges();,預設Linq to SQL會自動將這些INSERT/UPDATE/DELETE包成一個Transaction。例如:
var order1 = (
from o in db.Orders
where o.OrderID == 10248
select o).First();
order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
var order2 = (
from o in db.Orders
where o.OrderID == 10249
select o).First();
order2.ShipPostalCode = "123456789ABCDEF";
db.SubmitChanges();
第二筆訂單更新時,故意將郵遞區號設成123456789ABCDEF,會超出VARCHAR(10)的長度限而出錯。實際測試,可以發現連第一筆更新也不會寫入資料庫,證明兩筆UPDATE動作被自動包成Transaction。用SQL Profiler可以看到更明確的證據:
- 如果你希望能精確控制Transaction的Submit、Rollback,例如: 兩個DataContext共用一條Connection,可用類似以下的寫法:
SqlConnection cn = new SqlConnection(connString);
//兩個DataContext共用同一條Connection
Northwind dbA = new Northwind(cn);
Northwind dbB = new Northwind(cn);
//設定DataContext.Transaction
cn.Open();
DbTransaction trn = cn.BeginTransaction();
dbA.Transaction = trn;
dbB.Transaction = trn;
var order1 = (
from o in dbA.Orders
where o.OrderID == 10248
select o).First();
order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
var order2 = (
from o in dbB.Orders
where o.OrderID == 10249
select o).First();
order2.ShipPostalCode = DateTime.Now.ToString("HHmmss");
//若這裡才設定Transaction,則SELECT時不會包Transaction
//dbA.Transaction = trn;
//dbB.Transaction = trn;
dbA.SubmitChanges();
dbB.SubmitChanges();
//故意Rollback(白忙一場XD)
trn.Rollback();
cn.Close();
- 除了以上的兩種Transaction做法,如果要做跨資料庫的Distributed Transaction,就要靠好用的TransactionScope。
Northwind dbA = new Northwind(connString);
Northwind dbB = new Northwind(connString);
using (TransactionScope tx = new TransactionScope())
{
var order1 = (
from o in dbA.Orders
where o.OrderID == 10248
select o).First();
order1.ShipPostalCode = DateTime.Now.ToString("HHmmss");
var order2 = (
from o in dbB.Orders
where o.OrderID == 10249
select o).First();
order2.ShipPostalCode = DateTime.Now.ToString("HHmmss");
dbA.SubmitChanges();
dbB.SubmitChanges();
tx.Complete();
}
以上這三種做法,分別就是MSDN文件上提到的Implicit Transaction、Explicit Local Transaction以及Explicit Distributable Transaction。這裡做一下簡單的比較:
- Implicit Transaction: 當沒有設定DataContext.Transaction也沒有包TransactionScope時,Call SubmitChanges()會自動將新增/更新/刪除動作包成交易,最為簡便。但要注意,它限於同一個DataConext(當然,同一個DB Connection),且Transaction的範圍無法包含SELECT。
- Explicit Local Transaction: 前題是參與Transaction的DataContext要共用一條Connection,且要自己完成BeginTransaction、Commit、Rollback等動作。設定DataContext.Transaction的時機可以決定是否將SELECT也納入Transaction範圍。還有一點限制是,這種做法只限同一台DB,不支援分散式交易。
- Explicit Distributable Transaction: 彈性最大,可支援異種資料庫的分散式交易,但要注意所有包含在TransactionScope中的資料庫動作都會啟用LTM或OleTx,成本頗為昂貴(輕巧的LTM適用的條件很嚴苛,只限SQL 2005,如果TransactionScope中有涉及一台以上DB或對遠端SQL 2005開啟兩條以上連線,一定是用貴森森的OleTx),可以視需要將不必參與Transaction的部分隔離開來,以增進效能。(延伸閱讀: .NET分散式交易程式開發FAQ )
在探索Linq的過程中,我開始覺得,"擅長SQL"漸漸不再是程式開發者數一數二的重要技能,Linq To SQL讓開發者可以在完全不懂SQL的情況下寫出資料庫存取程式;表面上開發者的技術門檻變低是好事,但不免開始擔心這個進步背後隱藏的黑暗面。
未來,極有可能出現一行SQL都不懂的開發者拿著Linq這把新時代的機關槍,完全不瞄準就抱著四處掃射,任意寫幾行Code就發出一堆無效率的SQL查詢(大方地享用DataContext的自動關聯,一口氣由業務員查客戶,由客戶查訂單,再由訂單查明細)、成串混雜的Transaction(只要一路更動幾十個物件,再一次SubmitChanges()就可以辦到)。屆時,資料庫很可能被一堆無效率的查詢拖累而近癱瘓,一堆非必要的更新被自動包成Transaction則無疑開啟了Deadlock地獄的大門...
我想,一路從ADO、ADO.NET走來的老兵都深知前述的預言並非杞人憂天,所以記得要多說些鬼故事,提醒這群直接由Linq入門的菜鳥開發者一定要花時間了解Linq背後的SQL效能議題。Linq會變成化繁為簡的天使,還是拖垮資料庫的惡魔,關鍵握在他們手上。稍一不慎,Linq很可能就將變成聲名狼藉的資料庫殺手!