1. 程式人生 > >C#資料庫事務原理及實踐(下)

C#資料庫事務原理及實踐(下)

另一個走向極端的錯誤

滿懷信心的新手們可能為自己所掌握的部分知識陶醉不已,剛接觸資料庫庫事務處理的準開發者們也一樣,躊躇滿志地準備將事務機制應用到他的資料處理程式的每一個模組每一條語句中去。的確,事務機制看起來是如此的誘人——簡潔、美妙而又實用,我當然想用它來避免一切可能出現的錯誤——我甚至想用事務把我的資料操作從頭到尾包裹起來。

看著吧,下面我要從建立一個數據庫開始:

using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
  public class DbTran
  {
   file://執行事務處理
   public void DoTran()
   {
    file://建立連線並開啟
    SqlConnection myConn=GetConn();
    myConn.Open();

    SqlCommand myComm=new SqlCommand();
    SqlTransaction myTran;

    myTran=myConn.BeginTransaction();

    file://下面繫結連線和事務物件
    myComm.Connection=myConn;
    myComm.Transaction=myTran;

    file://試圖建立資料庫TestDB
    myComm.CommandText="CREATE database TestDB";
    myComm.ExecuteNonQuery();

    file://提交事務
    myTran.Commit();
   }

   file://獲取資料連線
   private SqlConnection GetConn()
   {
    string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
    SqlConnection myConn=new SqlConnection(strSql);
    return myConn;
   }
  }

  public class Test
  {
   public static void Main()
   {
    DbTran tranTest=new DbTran();
    tranTest.DoTran();
    Console.WriteLine("事務處理已經成功完成。");
    Console.ReadLine();
   }
  }
}

//---------------

  未處理的異常: System.Data.SqlClient.SqlException: 在多語句事務內不允許使用 CREATE DATABASE 語句。

at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
at Aspcn.DbTran.DoTran()
at Aspcn.Test.Main()

注意,如下的SQL語句不允許出現在事務中:

ALTER DATABASE 修改資料庫
BACKUP LOG 備份日誌
CREATE DATABASE 建立資料庫
DISK INIT 建立資料庫或事務日誌裝置
DROP DATABASE 刪除資料庫
DUMP TRANSACTION 轉儲事務日誌
LOAD DATABASE 裝載資料庫備份複本
LOAD TRANSACTION 裝載事務日誌備份複本
RECONFIGURE 更新使用 sp_configure 系統儲存過程更改的配置選項的當前配置(sp_configure 結果集中的 config_value 列)值。
RESTORE DATABASE 還原使用BACKUP命令所作的資料庫備份
RESTORE LOG 還原使用BACKUP命令所作的日誌備份
UPDATE STATISTICS 在指定的表或索引檢視中,對一個或多個統計組(集合)有關鍵值分發的資訊進行更新

除了這些語句以外,你可以在你的資料庫事務中使用任何合法的SQL語句。
事務回滾

事務的四個特性之一是原子性,其含義是指對於特定操作序列組成的事務,要麼全部完成,要麼就一件也不做。如果在事務處理的過程中,發生未知的不可預料的錯誤,如何保證事務的原子性呢?當事務中止時,必須執行回滾操作,以便消除已經執行的操作對資料庫的影響。

一般的情況下,在異常處理中使用回滾動作是比較好的想法。前面,我們已經得到了一個更新資料庫的程式,並且驗證了它的正確性,稍微修改一下,可以得到:

//RollBack.cs
using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
  public class DbTran
  {
   file://執行事務處理
   public void DoTran()
   {
    file://建立連線並開啟
    SqlConnection myConn=GetConn();
    myConn.Open();

    SqlCommand myComm=new SqlCommand();
    SqlTransaction myTran;

    file://建立一個事務
    myTran=myConn.BeginTransaction();
    file://從此開始,基於該連線的資料操作都被認為是事務的一部分
    file://下面繫結連線和事務物件
    myComm.Connection=myConn;
    myComm.Transaction=myTran;

    try
    {
     file://定位到pubs資料庫
     myComm.CommandText="USE pubs";
     myComm.ExecuteNonQuery();

     myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
     myComm.ExecuteNonQuery();

     file://下面使用建立資料庫的語句製造一個錯誤
     myComm.CommandText="Create database testdb";
     myComm.ExecuteNonQuery();

     myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.20 WHERE title_id LIKE 'Ps%'";
     myComm.ExecuteNonQuery();

     file://提交事務
     myTran.Commit();
    }
    catch(Exception err)
    {
     myTran.Rollback();
     Console.Write("事務操作出錯,已回滾。系統資訊:"+err.Message);
    }
   }

   file://獲取資料連線
   private SqlConnection GetConn()
   {
    string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
    SqlConnection myConn=new SqlConnection(strSql);
    return myConn;
   }
  }
  public class Test
  {
   public static void Main()
   {
    DbTran tranTest=new DbTran();
    tranTest.DoTran();
    Console.WriteLine("事務處理已經成功完成。");
    Console.ReadLine();
   }
  }
}

首先,我們在中間人為地製造了一個錯誤——使用前面講過的Create database語句。然後,在異常處理的catch塊中有如下語句:

myTran.Rollback();

當異常發生時,程式執行流跳轉到catch塊中,首先執行的就是這條語句,它將當前事務回滾。在這段程式可以看出,在Create database之前,已經有了一個更新資料庫的操作——將pubs資料庫的roysched表中的所有title_id欄位以“PC”開頭的書籍的royalty欄位的值都增加0.1倍。但是,由於異常發生而導致的回滾使得對於資料庫來說什麼都沒有發生。由此可見,Rollback()方法維護了資料庫的一致性及事務的原子性。

使用儲存點

事務只是一種最壞情況下的保障措施,事實上,平時系統的執行可靠性都是相當高的,錯誤很少發生,因此,在每次事務執行之前都檢查其有效性顯得代價太高——絕大多數的情況下這種耗時的檢查是不必要的。我們不得不想另外一種辦法來提高效率。

事務儲存點提供了一種機制,用於回滾部分事務。因此,我們可以不必在更新之前檢查更新的有效性,而是預設一個儲存點,在更新之後,如果沒有出現錯誤,就繼續執行,否則回滾到更新之前的儲存點。儲存點的作用就在於此。要注意的是,更新和回滾代價很大,只有在遇到錯誤的可能性很小,而且預先檢查更新的有效性的代價相對很高的情況下,使用儲存點才會非常有效。

使用.net框架程式設計時,你可以非常簡單地定義事務儲存點和回滾到特定的儲存點。下面的語句定義了一個儲存點“NoUpdate”:

myTran.Save("NoUpdate");

當你在程式中建立同名的儲存點時,新建立的儲存點將替代原有的儲存點。

在回滾事務時,只需使用Rollback()方法的一個過載函式即可:

   myTran.Rollback("NoUpdate");

下面這段程式說明了回滾到儲存點的方法和時機:
using System;
using System.Data;
using System.Data.SqlClient;

namespace Aspcn
{
  public class DbTran
  { 
   file://執行事務處理
   public void DoTran()
   {
    file://建立連線並開啟
    SqlConnection myConn=GetConn();
    myConn.Open();

    SqlCommand myComm=new SqlCommand();
    SqlTransaction myTran;

    file://建立一個事務
    myTran=myConn.BeginTransaction();
    file://從此開始,基於該連線的資料操作都被認為是事務的一部分
    file://下面繫結連線和事務物件
    myComm.Connection=myConn;
    myComm.Transaction=myTran;

    try
    {
     myComm.CommandText="use pubs";
     myComm.ExecuteNonQuery();

     myTran.Save("NoUpdate");

     myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
     myComm.ExecuteNonQuery();

     file://提交事務
     myTran.Commit();
    }
    catch(Exception err)
    {
     file://更新錯誤,回滾到指定儲存點
     myTran.Rollback("NoUpdate");
     throw new ApplicationException("事務操作出錯,系統資訊:"+err.Message);
    }
   }

   file://獲取資料連線
   private SqlConnection GetConn()
   {
    string strSql="Data Source=localhost;Integrated Security=SSPI;user id=sa;password=";
    SqlConnection myConn=new SqlConnection(strSql);
    return myConn;
   }
  }
  public class Test
  {
   public static void Main()
   {
    DbTran tranTest=new DbTran();
    tranTest.DoTran();
    Console.WriteLine("事務處理已經成功完成。");
    Console.ReadLine();
   }
  }
}

很明顯,在這個程式中,更新無效的機率是非常小的,而且在更新前驗證其有效性的代價相當高,因此我們無須在更新之前驗證其有效性,而是結合事務的儲存點機制,提供了資料完整性的保證。

隔離級別的概念

企業級的資料庫每一秒鐘都可能應付成千上萬的併發訪問,因而帶來了併發控制的問題。由資料庫理論可知,由於併發訪問,在不可預料的時刻可能引發如下幾個可以預料的問題:

髒讀 :包含未提交資料的讀取。例如,事務1 更改了某行。事務2 在事務1 提交更改之前讀取已更改的行。如果事務1 回滾更改,則事務2 便讀取了邏輯上從未存在過的行。

不可重複讀取 :當某個事務不止一次讀取同一行,並且一個單獨的事務在兩次(或多次)讀取之間修改該行時,因為在同一個事務內的多次讀取之間修改了該行,所以每次讀取都生成不同值,從而引發不一致問題。

幻象 :通過一個任務,在以前由另一個尚未提交其事務的任務讀取的行的範圍中插入新行或刪除現有行。帶有未提交事務的任務由於該範圍中行數的更改而無法重複其原始讀取。

如你所想,這些情況發生的根本原因都是因為在併發訪問的時候,沒有一個機制避免交叉存取所造成的。而隔離級別的設定,正是為了避免這些情況的發生。事務準備接受不一致資料的級別稱為隔離級別。隔離級別是一個事務必須與其它事務進行隔離的程度。較低的隔離級別可以增加併發,但代價是降低資料的正確性。相反,較高的隔離級別可以確保資料的正確性,但可能對併發產生負面影響。

根據隔離級別的不同,DBMS為並行訪問提供不同的互斥保證。在SQL Server資料庫中,提供四種隔離級別:未提交讀、提交讀、可重複讀、可序列讀。這四種隔離級別可以不同程度地保證併發的資料完整性:
隔離級別 髒 讀 不可重複讀取 幻 像
未提交讀
提交讀
可重複讀
可序列讀

可以看出,“可序列讀”提供了最高級別的隔離,這時併發事務的執行結果將與序列執行的完全一致。如前所述,最高級別的隔離也就意味著最低程度的併發,因此,在此隔離級別下,資料庫的服務效率事實上是比較低的。儘管可序列性對於事務確保資料庫中的資料在所有時間內的正確性相當重要,然而許多事務並不總是要求完全的隔離。例如,多個作者工作於同一本書的不同章節。新章節可以在任意時候提交到專案中。但是,對於已經編輯過的章節,沒有編輯人員的批准,作者不能對此章節進行任何更改。這樣,儘管有未編輯的新章節,但編輯人員仍可以確保在任意時間該書籍專案的正確性。編輯人員可以檢視以前編輯的章節以及最近提交的章節。這樣,其它的幾種隔離級別也有其存在的意義。

在.net框架中,事務的隔離級別是由列舉System.Data.IsolationLevel所定義的:

[Flags]
[Serializable]
public enum IsolationLevel

其成員及相應的含義如下:

成 員 含 義
Chaos 無法改寫隔離級別更高的事務中的掛起的更改。
ReadCommitted 在正在讀取資料時保持共享鎖,以避免髒讀,但是在事務結束之前可以更改資料,從而導致不可重複的讀取或幻像資料。
ReadUncommitted 可以進行髒讀,意思是說,不釋出共享鎖,也不接受獨佔鎖。
RepeatableRead 在查詢中使用的所有資料上放置鎖,以防止其他使用者更新這些資料。防止不可重複的讀取,但是仍可以有幻像行。
Serializable 在DataSet上放置範圍鎖,以防止在事務完成之前由其他使用者更新行或向資料集中插入行。
Unspecified 正在使用與指定隔離級別不同的隔離級別,但是無法確定該級別。

顯而意見,資料庫的四個隔離級別在這裡都有對映。

預設的情況下,SQL Server使用ReadCommitted(提交讀)隔離級別。

關於隔離級別的最後一點就是如果你在事務執行的過程中改變了隔離級別,那麼後面的命名都在最新的隔離級別下執行——隔離級別的改變是立即生效的。有了這一點,你可以在你的事務中更靈活地使用隔離級別從而達到更高的效率和併發安全性。

最後的忠告

無疑,引入事務處理是應對可能出現的資料錯誤的好方法,但是也應該看到事務處理需要付出的巨大代價——用於儲存點、回滾和併發控制所需要的CPU時間和儲存空間。

本文的內容只是針對Microsoft SQL Server資料庫的,對應於.net框架中的System.Data.SqlClient名稱空間,對於使用OleDb的情形,具體的實現稍有不同,但這不是本文的內容,有興趣的讀者可以到.net中華網(www.aspcn.com)的論壇裡找到答案。

 

相關推薦

C#資料庫事務原理實踐

另一個走向極端的錯誤 滿懷信心的新手們可能為自己所掌握的部分知識陶醉不已,剛接觸資料庫庫事務處理的準開發者們也一樣,躊躇滿志地準備將事務機制應用到他的資料處理程式的每一個模組每一條語句中去。的確,事務機制看起來是如此的誘人——簡潔、美妙而又實用,我當然想用它來避免一切可能

C#資料庫事務原理實踐

什麼是資料庫事務  資料庫事務是指作為單個邏輯工作單元執行的一系列操作。設想網上購物的一次交易,其付款過程至少包括以下幾步資料庫操作:· 更新客戶所購商品的庫存資訊· 儲存客戶付款資訊--可能包括與銀行系統的互動· 生成訂單並且儲存到資料庫中· 更新使用者相關資訊,例如購物數量等等正常的情況下,這些操

DDoS 攻擊與防禦:從原理實踐

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 DDoS 攻擊與防護實踐  DDoS 攻擊的實現方式主要有如下兩種:  自建 DDoS 平臺  現在有開源的 DDoS 平臺原始碼,只要有足夠機器和頻寬資源,隨時都能部署一套極具殺傷力的 DDoS 平臺,如下圖的第三

孫宇聰:來自Google的DevOps理念實踐 – 運維派

接下來聊一聊SRE的一些最佳實踐,我認為Google做得比較好的幾點。 SRE的最佳實踐 建設平臺化服務體系 首先, Google現在是一個六萬人的公司,涉及到的產品線可能一百多個,各個部門之間差距很大,而且Google全球幾百個資料中心,有很多機器,它如何管理?如果按照國內公司的方式去管理早就累死

深入解析SQL Server並行執行原理實踐

在成熟領先的企業級資料庫系統中,並行查詢可以說是一大利器,在某些場景下它可以顯著地提升查詢的相應時間,提升使用者體驗。如SQL Server、Oracle等, MySQL目前還未實現,而PostgreSQL在2015實現了並行掃描,相信他們也在朝著更健壯的企業級資料庫邁進。RDBMS中並行執行的實現

編碼原則實例------c++程序設計原理實踐進階篇

組類型 運算 奇怪 head 不能 gui 簡單的 版本 布局 編碼原則: 一般原則 預處理原則 命名和布局原則 類原則 函數和表達式原則 硬實時原則 關鍵系統原則 (硬實時原則、關鍵系統原則僅用於硬實時和關鍵系統程序設計) (嚴格原則都用一個大寫字母R及其編號標識,而

有符號數和無符號數------c++程序設計原理實踐進階篇

效果 進階 str 二進制位 bsp () 都是 有符號 重新 有符號數與無符號數的程序設計原則: 當需要表示數值時,使用有符號數(如 int)。 當需要表示位集合時,使用無符號數(如unsigned int)。 有符號數和無符號數混合運算有可能會帶來災難性的後果。例如

動態內存分配存在的問題內存空洞------c++程序設計原理實踐進階篇

我們 程序 動態 height ++ idt 很多 alt 空間 new的問題究竟在哪裏呢?實際上問題出在new和delete的結合使用上。考察下面程序中內存分配和釋放過程: while(1){ Big* p=new big;  //...... Smal

數值限制------c++程序設計原理實踐進階篇

c++程序 its positive size true 設置 malle 設計原理 硬件 每種c++的實現都在<limits>、<climits>、<limits.h>和<float.h>中指明了內置類型的屬性,因此程序

實現求解線性方程矩陣、高斯消去法------c++程序設計原理實踐進階篇

ipy 類型 cat sys sca solution gaussian 拷貝 img 步驟: 其中A是一個n*n的系數方陣 向量x和b分別是未知數和常量向量: 這個系統可能有0個、1個或者無窮多個解,這取決於系數矩陣A和向量b。求解線性系統的方法有很多,這裏使用一種經典

c++11隨機數------c++程序設計原理實踐進階篇

ber linear 而在 希望 double 元素 light eal 區間   隨機數既是一個實用工具,也是一個數學問題,它高度復雜,這與它在現實世界中的重要性是相匹配的。在此我們只討論隨機數哦最基本的內容,這些內容可用於簡單的測試和仿真。在<random>

K-mean原理實踐K值確定

kmeans一般在資料分析前期使用,選取適當的k,將資料聚類後,然後研究不同聚類下資料的特點。 演算法原理: (1) 隨機選取k箇中心點; (2) 在第j次迭代中,對於每個樣本點,選取最近的中心點,歸為該類; (3) 更新中心點為每類的均值; (4) j<

C++實現連連看程式碼原理解析——消子演算法

本文例項為大家分享了MFC實現連連看遊戲消子演算法的具體程式碼,供大家參考,具體內容如下 兩個位置的圖片能否消除,有三種情況: 【加入我們的學習群(C/C++群:892643663;Java群:957907127),大牛線上為您提供服務,還有免費編譯大禮包和視訊教程贈送哦】 1.

C++實現連連看程式碼原理解析——消除演算法

C++連連看消除程式碼的實現,過程比較複雜。 【加入我們的學習群(C/C++群:892643663;Java群:957907127),大牛線上為您提供服務,還有免費編譯大禮包和視訊教程贈送哦】 以下是程式碼: #include<iostream> #include&l

C++實現連連看程式碼原理解析——主程式核心程式碼

這兩天研究了一下連連看遊戲的原始碼,感覺它挺簡單的,主要就是判斷選中的兩張圖片能否消去。我參考了網上的原始碼(抱歉的是,不記得當時下載的網址了,在此對原作者表示深深的歉意!),然後自己把核心程式碼整理如下,與大家共享。需要說明的是,這只是核心演算法的程式碼,介面設計和操作的程式碼見我的部落格

資料庫事務的四大特性ACID事務的隔離級別

事務具有4大特性,一般稱為事務的ACID屬性: 原子性: 事務是一個原子操作單元,其對資料的修改,要麼全都執行,要麼全都不執行,在操作失敗後不能對資料庫中的資料有任何影響。 一致性: 在事務開始和完成時,資料必須保持一致狀態,這意味著所有相關的資料規則都必須應用於事務

機器學習演算法原理實踐、卡爾曼濾波器演算法淺析matlab實戰

卡爾曼濾波器是一種利用線性系統狀態方程,通過系統輸入輸出觀測資料,對系統狀態進行最優估計的演算法。而且由於觀測包含系統的噪聲和干擾的影響,所以最優估計也可看做是濾波過程。 卡爾曼濾波器的核心

拉重新整理、上拉載入更多控制元件實現原理解析

以前那個賬號,以後可能不用了,把文章搬過來!!! 效果預覽 接受hi大頭鬼hi的建議,來一個動態圖,方便大家知道這是個什麼東西。 動機 原理     無論是下拉重新整理還是上拉載入更多,原理都是在內容View(ListView、Re

超實用的JavaScript技巧最佳實踐

1.使用邏輯符號&&或者||進行條件判斷 var foo = 10;     foo == 10 && doSomething(); // is the same thing as if (foo == 10) doSomething(); 

C++智能指針剖析boost::shared_ptr&其他

剖析 smart_ptr mage open log gin 內部使用 聲明 虛基類 1. boost::shared_ptr 前面我已經講解了兩個比較簡單的智能指針,它們都有各自的優缺點。由於 boost::scoped_ptr 獨享所有權,當我們真真需要復制智能指針時,