(轉)多重 Transaction 的寫法範例

出處:https://dotblogs.com.tw/johnny/2010/01/18/13078

對於大部份不熟 Database 的網頁程式設計師,即使知道有 Trasnaction 這個功能,也很可能棄之而不用,甚至是從來都沒用過。然而,Trasaction 這個功能是絕對有必要的,在很多狀況下,如果你不用 Trasaction,我實在不知道你到底要怎樣把程式寫好。如果不用 Trasaction,等你讓網站上線,慢慢的使用者開始變多的時候,保證你會開始遇到許多莫名其妙的問題,而且你將發現你甚至無法追蹤問題,當然也無從解決...

 

對於大部份不熟 Database 的網頁程式設計師,即使知道有 Trasnaction 這個功能,也很可能棄之而不用,甚至是從來都沒用過。然而,Trasaction 這個功能是絕對有必要的,在很多狀況下,如果你不用 Trasaction,我實在不知道你到底要怎樣把程式寫好。如果不用 Trasaction,等你讓網站上線,慢慢的使用者開始變多的時候,保證你會開始遇到許多莫名其妙的問題,而且你將發現你甚至無法追蹤問題,當然也無從解決。

我希望你不要抱持一種心態,就是那種「反正我很快就要走了,這種問題就讓接手的人去傷腦筋好了」。說真的,抱持這樣心態的人我實在看多了。他們其實沒有想到一點,那就是「在下一個工作時,你很可能就是那個傷腦筋的人」呀!

所以,如果你從來不會寫 Stored Procedure,不會用 Trasaction,甚至在規畫資料庫時老是打死只會用單獨一個資料表來放資料,那麼我真的用良心勸你不要再幹程式設計師這一行了。不然的話,你應該花上一點時間,去把資料庫原理好好的學一下。請記住,不是只有 DBA 才需要學資料庫的。你不用學得像 DBA 那麼精通,但是你就是絕對不能對資料庫一竅不通。

Trasaction 真的有那麼難學嗎?麻煩等你看完以下這篇文章,再來下定論也不遲。

在寫入資料時使用 Trasaction 指令的方法很簡單,下面是一個簡單的例子:

    Protected Sub InsertData()
        Dim cmd As New SqlCommand
        Dim conn As New SqlConnection
        Dim trans As SqlTransaction
        conn.ConnectionString = "你的 Connectin String"
        connOpen()
        cmd.CommandText = "你的 Stored Procedure 名稱"
        cmd.CommandType = CommandType.StoredProcedure
        cmd.Connection = conn
        trans = conn.BeginTransaction
        cmd.Transaction = trans

        cmd.Parameters.Add("@ABC", SqlDbType.NChar, 30)
        cmd.Parameters("@ABC").Value = txtABC.Text
        ...

         If cmd.ExecuteNonQuery = 0 Then '如果有寫入錯誤,則整個交易取消
             trans.Rollback()
             lblMsg.Text = "由於資料庫問題,本次作業失敗!請聯絡管理人員。"
         Else
             trans.Commit()
         End If
         ...
         conn.Close()
    End Sub

我想,對於一個網頁設計師,以上的範例對你應該是很容易了解的,或許你根本已經不陌生。如果你連上面這個範例都很陌生的話,那麼你可能是真的連坊間的入門書都沒有在看喔!

不過,除非你已經是老手,不然對於腦筋動得快的人,一定很快會懷疑,如果只是做單純的資料新增的動作,真的有需要使用 Trasaction 嗎?在上面的程式中,其實你大可以把所有以粗體字標示的地方通通刪除,結果也不會有太大的差異!為什麼?因為上面 cmd.ExecuteNonQuery 的傳回值如果是 0,表示根本沒有資料被插入資料表裡面,那麼有什麼好 Rollback 的?

很不幸的,坊間的許多入門書都只教你到這個範例的程度而已。所以如果你只學到 Transaction 的用法,事實上你幾乎是等於什麼都沒有學到的。

使用同樣的例子,如果遇到稍為複雜一點的狀況,你的程式就不能這麼寫了。不過,我倒不是說 Trasaction 的寫法有任何改變,而是只有一點小小的技巧而已。我有個朋友就卡在這一關,因為遇到問題,在沒人可問的情況下,就以為是 SQL Server 或 ASP.NET 2.0 的 bug,然後也就很乾脆的不再使用 Transaction 了。其實這裡面的誤會可不小;因為這不是 SQL Serve 或 ASP.NET 的 bug,只是因為他太執著於範例程式的寫法而已。如果講白一點,問題的關鍵只有一句話,那就是 Connection 不要太早關閉!Connection 關閉之後,對於 Transaction 的任何作業都會失效。所以等到最後再來一起關就可以了。

我相信很多人看到這裡還是不懂那是什麼意思;因此我下面就再用一個範例來解釋。假設我有兩個資料表,tblA 與 tblB。如果你不使用 Relation 物件,或是兩個資料表之間根本沒有 Relationship,那麼 Transaction 使用在這個地方是很恰當的。萬一你已經把資料寫進 tblA,但在寫入 tblB 時失敗了,那麼單獨寫入一個資料表就變成錯誤了。所以這時候你可以為 tblA 的寫入作業建立一個 Transaction 物件,再為 tblB 的寫入作業建立另一個 Transaction。唯有當兩個寫入作業都成功的時候才一起 Commit,否則就一起 Rollback。

由於程式沒有什麼太大的不同,所以我就不列範例程式了。不過要記得,你必須在兩個 Transaction 都 Commit 或 Rollback 之後,再讓個別的 Connection 同時關閉。換句話說,你可以將上面的 insertData 改成 Function insertTblA As Boolean 和 Function insertTblB As Booltean,然後寫一個 CouumitTransaction() 是專門用來 Commit 兩個 Transaction 物件的,還有一個 RollbackTransactions() 是專門用來 Rollback 兩個 Transaction 物件的,以及一個 CloseConnections() 專門拿來將 Connection 物件關閉。然後把寫入資料的程式寫成如下的樣子:

If insertTblA Then
   If insertTblB Then
     commitTransaction()
   Else
     RollbackTransactions()
   End If
Else
   RollbackTransactions()
End If
CloseConnections()

看到這裡,或許有人會問,既然已經寫成 StoredProcedure,為什麼還要這麼麻煩?直接在 Stored Procedure 裡面寫入 tblA 和 tblB 不就好了嗎?Transaction 也只需要建立一個!

話是沒錯,但是如果 tblA 和 tblB 的資料永遠是一對一的話,那麼你應該做反正規化,讓兩個資料表簡化成一個,而不該分成兩個!就是因為它們不是一對一,才有需要做成兩個資料表不是嗎?換句話說,在最常見的情況下,你可能對 tblA 插入一筆資料,對 tblB 卻要插入十筆資料。在這個時候,建立兩個 Transaction 反而是有其需要了。