1. 程式人生 > >C#回顧學習筆記三十九:事務

C#回顧學習筆記三十九:事務

1)事務是什麼?
事務是保證多個操作全部成功時才認為是一次有效操作,當有一個操作失敗時就會認為全部操作無效,並且回到執行操作之前的狀態只有資料改變時(增加、修改、刪除)時才會引發事務,查詢不會引發事務。如果在寫入一個記錄時出現失敗,則事務會讓其他已經寫入的資料回滾,讓資料恢復到修改前的狀態。事務的作用就是:要麼讓任務都執行成功,要麼讓任務都執行失敗。事務的四大特點有:原子性、一致性、隔離性、永續性。

2)為什麼使用事務?
假設有一張使用者賬號餘額表,使用者A向用戶B轉賬100,則資料庫程式碼可以這樣執行:
update GongZiBiao set money=money-100 where name='使用者A'
update GongZiBiao set money=money+100 where name='使用者B'
然而這樣做有個缺點:假設執行了第一條語句後出現什麼異常而導致第二條語句無法執行,則意味著使用者A扣掉了100,而使用者B沒有得到該有的100元。因此引用事務來解決這種問題,有了事務後,若執行一連串資料庫語句時出現錯誤,則前面的語句執行都無效,必須讓所有語句都沒有錯誤,才算真正的執行成功。事務保證了這種重要的資料處理變得合理與安全,對金融和搶票系統來說很重要。

3)如何使用ADO.NET操作事務?

這次的練習就使用SQLserver和VS2013的一個控制檯應用程式來做演示。
第1步,新建控制檯應用程式,在App.config中配置資料庫連線字串。在<configuration>標籤裡配置如下所示的程式碼:
<connectionStrings>
    <add name="testConn" connectionString="server=.;database=IsNothing;uid=sa;pwd=145124"/>
  </connectionStrings>
其中name屬性的值可以任意設定。server表示資料庫地址,小數點代表就是本地。database表示資料庫名,這裡是IsNothing。uid是資料庫使用者名稱。pwd是資料庫密碼。
第2步,新建資料庫,名字就是IsNothing。新建表UserInfo,表字段設計和表內資料如下圖:


第3步:現在讓Id為1的使用者把年齡轉讓1歲給Id為2的使用者,也就是Id為1的使用者年齡-1,Id為2的使用者年齡+1。必須讓兩次資料操作都執行成功,如果其中任何一個失敗,則操作無效。主函式程式碼如下:
class Program
    {
        static void Main(string[] args)
        {
            //1.讀取資料庫連線字串,"testConn"與App.config中的name屬性值保持一致
            string connStr = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString;


            //2.宣告連線資料庫的物件,並開啟資料庫連線
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                using (SqlTransaction tx = conn.BeginTransaction())//3.宣告事務
                {
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.Transaction = tx;
                        try
                        {
                            cmd.CommandText = "update UserInfo set Age=Age-1 where Id=1";
                            int row1 = cmd.ExecuteNonQuery();
                            cmd.CommandText = "update UserInfo set Age=Age+1 where Id=2";
                            int row2 = cmd.ExecuteNonQuery();
                            tx.Commit();//注意這裡,如果上面兩句sql語句都執行成功,這裡才完成提交。
                            Console.WriteLine("執行語句1影響的行數是{0}", row1);
                            Console.WriteLine("執行語句2影響的行數是{0}", row2);
                        }
                        catch (Exception ex)
                        {
                            tx.Rollback();//try內部程式碼發生異常,就執行回滾讓資料回到原始狀態
                            Console.WriteLine("發生異常,異常資訊:", ex.ToString());
                    }
                }
            }
            Console.ReadKey();
        }
    }
解釋:在上面程式碼中,最重要的程式碼是註釋3開始,從這裡宣告事務並使得裡面所有程式碼都屬於事務管理範圍,這樣只要資料操作中途出現異常,則事務讓資料回滾。
第4步,執行程式。如果程式碼沒有問題,則會出現如下圖所示的情況:

第5步,以上操作似乎還是看不出事務的好處是什麼,因為就算不使用事務,這樣的資料操作似乎也能執行成功。那麼,為了驗證事務的可靠性,先人為地製造一個異常。在如下圖所示的位置新增一句程式碼,這句程式碼絕對會丟擲異常。執行程式,然後觀察看資料庫的值是否改變。如果沒問題,則此時資料庫的值依然跟上面第4步的執行結果一樣。因為在丟擲異常時,事務就讓資料回滾到修改前的狀態了。

第6步,現在把上面程式碼做一下修改,去掉跟事務相關的程式碼然後再執行試一試:
class Program
    {
        static void Main(string[] args)
        {
            string connStr = ConfigurationManager.ConnectionStrings["testConn"].ConnectionString;


            using (SqlConnection conn = new SqlConnection(connStr))
            {
                conn.Open();
                using (SqlCommand cmd = new SqlCommand())
                {
                    cmd.Connection = conn;
                    try
                    {
                        cmd.CommandText = "update UserInfo set Age=Age-1 where Id=1";
                        int row1 = cmd.ExecuteNonQuery();
                        int exp = Convert.ToInt32("abc");//注意這裡,故意製造一個異常
                        cmd.CommandText = "update UserInfo set Age=Age+1 where Id=2";
                        int row2 = cmd.ExecuteNonQuery();
                        Console.WriteLine("執行語句1影響的行數是{0}", row1);
                        Console.WriteLine("執行語句2影響的行數是{0}", row2);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("發生異常,異常資訊:", ex.ToString());
                    }
                }
            }
            Console.ReadKey();
        }
    }

上面的程式碼會在執行成功第一個sql語句後丟擲異常,這樣就造成第二個sql語句無法執行。所以觀察資料庫可以發現,第一個使用者的年齡減了1歲,而第二個使用者的年齡依然沒變。這就是沒有事務時的壞處,如果是金融轉賬,沒有事務控制的話就會造成虧損。

4)如何使用SQLserver處理事務

SQLserver裡自帶的語句也可以完成事務處理,就拿上面的練習來說,在SQLserver裡可以這麼做:

begin try
begin transaction--設定反悔點,開啟事務(出錯時可以回到這裡),可以簡寫為tran
delete from T_Persons where Id=8
delete from T_Classes
commit transaction--提交事務,不反悔(沒問題,直接走的意思)
end try
begin catch
rollback transaction--回滾事務,如果出錯就回到反悔點(反悔了要回去起點的意思)
end catch