Wednesday, May 14, 2008 - Posts

KB-Transaction in Linq to SQL

關於Ling to SQL如何處理交易,一直有個疑問 -- 當多筆資料的更新動作必須包成Transaction時,在Linq to SQL中應如何處理?

花了點時間研究,心得如下:

  1. 當連續進行多筆資料更新,再一次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可以看到更明確的證據:
     
  2. 如果你希望能精確控制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();
  3. 除了以上的兩種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。這裡做一下簡單的比較:

  1. Implicit Transaction: 當沒有設定DataContext.Transaction也沒有包TransactionScope時,Call SubmitChanges()會自動將新增/更新/刪除動作包成交易,最為簡便。但要注意,它限於同一個DataConext(當然,同一個DB Connection),且Transaction的範圍無法包含SELECT。
  2. Explicit Local Transaction: 前題是參與Transaction的DataContext要共用一條Connection,且要自己完成BeginTransaction、Commit、Rollback等動作。設定DataContext.Transaction的時機可以決定是否將SELECT也納入Transaction範圍。還有一點限制是,這種做法只限同一台DB,不支援分散式交易。
  3. 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很可能就將變成聲名狼藉的資料庫殺手!

Search

Go

<May 2008>
SunMonTueWedThuFriSat
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567
 
RSS
【工商服務】


BlogLook Score and Rank

Syndication