程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#數據庫事務原理及實踐

C#數據庫事務原理及實踐

編輯:關於C#

什麼是數據庫事務

數據庫事務是指作為單個邏輯工作單元執行的一系列操作。

設想網上購物的一次交易,其付款過程至少包括以下幾步數據庫操作:

· 更新客戶所購商品的庫存信息

· 保存客戶付款信息--可能包括與銀行系統的交互

· 生成訂單並且保存到數據庫中

· 更新用戶相關信息,例如購物數量等等

正常的情況下,這些操作將順利進行,最終交易成功,與交易相關的所有數據庫信息也成功地更新。但是,如果在這一系列過程中任何一個環節出了差錯,例如在更新商品庫存信息時發生異常、該顧客銀行帳戶存款不足等,都將導致交易失敗。一旦交易失敗,數據庫中所有信息都必須保持交易前的狀態不變,比如最後一步更新用戶信息時失敗而導致交易失敗,那麼必須保證這筆失敗的交易不影響數據庫的狀態--庫存信息沒有被更新、用戶也沒有付款,訂單也沒有生成。否則,數據庫的信息將會一片混亂而不可預測。

數據庫事務正是用來保證這種情況下交易的平穩性和可預測性的技術。

數據庫事務的ACID屬性

事務處理可以確保除非事務性單元內的所有操作都成功完成,否則不會永久更新面向數據的資源。通過將一組相關操作組合為一個要麼全部成功要麼全部失敗的單元,可以簡化錯誤恢復並使應用程序更加可靠。一個邏輯工作單元要成為事務,必須滿足所謂的ACID(原子性、一致性、隔離性和持久性)屬性:

· 原子性

事務必須是原子工作單元;對於其數據修改,要麼全都執行,要麼全都不執行。通常,與某個事務關聯的操作具有共同的目標,並且是相互依賴的。如果系統只執行這些操作的一個子集,則可能會破壞事務的總體目標。原子性消除了系統處理操作子集的可能性。

· 一致性

事務在完成時,必須使所有的數據都保持一致狀態。在相關數據庫中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。事務結束時,所有的內部數據結構(如 B 樹索引或雙向鏈表)都必須是正確的。某些維護一致性的責任由應用程序開發人員承擔,他們必須確保應用程序已強制所有已知的完整性約束。例如,當開發用於轉帳的應用程序時,應避免在轉帳過程中任意移動小數點。

· 隔離性

由並發事務所作的修改必須與任何其它並發事務所作的修改隔離。事務查看數據時數據所處的狀態,要麼是另一並發事務修改它之前的狀態,要麼是另一事務修改它之後的狀態,事務不會查看中間狀態的數據。這稱為可串行性,因為它能夠重新裝載起始數據,並且重播一系列事務,以使數據結束時的狀態與原始事務執行的狀態相同。當事務可序列化時將獲得最高的隔離級別。在此級別上,從一組可並行執行的事務獲得的結果與通過連續運行每個事務所獲得的結果相同。由於高度隔離會限制可並行執行的事務數,所以一些應用程序降低隔離級別以換取更大的吞吐量。

· 持久性

事務完成之後,它對於系統的影響是永久性的。該修改即使出現致命的系統故障也將一直保持。

DBMS的責任和我們的任務

企業級的數據庫管理系統(DBMS)都有責任提供一種保證事務的物理完整性的機制。就常用的SQL Server2000系統而言,它具備鎖定設備隔離事務、記錄設備保證事務持久性等機制。因此,我們不必關心數據庫事務的物理完整性,而應該關注在什麼情況下使用數據庫事務、事務對性能的影響,如何使用事務等等。

本文將涉及到在.net框架下使用C#語言操縱數據庫事務的各個方面。

體驗SQL語言的事務機制

作為大型的企業級數據庫,SQL Server2000對事務提供了很好的支持。我們可以使用SQL語句來定義、提交以及回滾一個事務。

如下所示的SQL代碼定義了一個事務,並且命名為"MyTransaction"(限於篇幅,本文並不討論如何編寫SQL語言程序,請讀者自行參考相關書籍):

DECLARE @TranName VARCHAR(20)
SELECT @TranName = 'MyTransaction'
BEGIN TRANSACTION @TranNameGOUSE pubs
GO
UPDATE roysched
SET royalty = royalty * 1.10
WHERE title_id LIKE 'Pc%'
GO
COMMIT TRANSACTION MyTransaction
GO

這裡用到了SQL Server2000自帶的示例數據庫pubs,提交事務後,將為所有暢銷計算機書籍支付的版稅增加 10%。

打開SQL Server2000的查詢分析器,選擇pubs數據庫,然後運行這段程序,結果顯而易見。

可是如何在C#程序中運行呢?我們記得在普通的SQL查詢中,一般需要把查詢語句賦值給SalCommand.CommandText屬性,這裡也就像普通的SQL查詢語句一樣,將這些語句賦給SqlCommand.CommandText屬性即可。要注意的一點是,其中的"GO"語句標志著SQL批處理的結束,編寫SQL腳本是需要的,但是在這裡是不必要的。我們可以編寫如下的程序來驗證這個想法:

//TranSql.csusing System;
using System.Data;
using System.Data.SqlClient;
namespace Aspcn
{
 public class DbTranSql
 {
  file://將事務放到SQL Server中執行
  public void DoTran()
  {
   file://建立連接並打開
   SqlConnection myConn=GetConn();myConn.Open();
   SqlCommand myComm=new SqlCommand();
   try
   {
    myComm.Connection=myConn;
    myComm.CommandText="DECLARE @TranName VARCHAR(20) ";
    myComm.CommandText+="SELECT @TranName = 'MyTransaction' ";
    myComm.CommandText+="BEGIN TRANSACTION @TranName ";
    myComm.CommandText+="USE pubs ";
    myComm.CommandText+="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%' ";
    myComm.CommandText+="COMMIT TRANSACTION MyTransaction ";
    myComm.ExecuteNonQuery();
   }
   catch(Exception err)
   {
    throw new ApplicationException("事務操作出錯,系統信息:"+err.Message);
   }
   finally
   {
    myConn.Close();
   }
  }
  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()
  {
   DbTranSql tranTest=new DbTranSql();
   tranTest.DoTran();
   Console.WriteLine("事務處理已經成功完成。");
   Console.ReadLine();
  }
 }
}

注意到其中的SqlCommand對象myComm,它的CommandText屬性僅僅是前面SQL代碼字符串連接起來即可,當然,其中的"GO"語句已經全部去掉了。這個語句就像普通的查詢一樣,程序將SQL文本事實上提交給DBMS去處理了,然後接收返回的結果(如果有結果返回的話)。

很自然,我們最後看到了輸出"事務處理已經成功完成",再用企業管理器查看pubs數據庫的roysched表,所有title_id字段以"PC"開頭的書籍的royalty字段的值都增加了0.1倍。

這裡,我們並沒有使用ADO.net的事務處理機制,而是簡單地將執行事務的SQL語句當作普通的查詢來執行,因此,事實上該事務完全沒有用到.net的相關特性。

了解.net中的事務機制

如你所知,在.net框架中主要有兩個命名空間(namespace)用於應用程序同數據庫系統的交互:System.Data.SqlClient和System.Data.OleDb。前者專門用於連接Microsoft公司自己的SQL Server數據庫,而後者可以適應多種不同的數據庫。這兩個命名空間中都包含有專門用於管理數據庫事務的類,分別是System.Data.SqlClient.SqlTranscation類和System.Data.OleDb.OleDbTranscation類。

就像它們的名字一樣,這兩個類大部分功能是一樣的,二者之間的主要差別在於它們的連接機制,前者提供一組直接調用 SQL Server 的對象,而後者使用本機 OLE DB 啟用數據訪問。 事實上,ADO.net 事務完全在數據庫的內部處理,且不受 Microsoft 分布式事務處理協調器 (DTC) 或任何其他事務性機制的支持。本文將主要介紹System.Data.SqlClient.SqlTranscation類,下面的段落中,除了特別注明,都將使用System.Data.SqlClient.SqlTranscation類。

事務的開啟和提交

現在我們對事務的概念和原理都了然於心了,並且作為已經有一些基礎的C#開發者,我們已經熟知編寫數據庫交互程序的一些要點,即使用SqlConnection類的對象的Open()方法建立與數據庫服務器的連接,然後將該連接賦給SqlCommand對象的Connection屬性,將欲執行的SQL語句賦給它的CommandText屬性,於是就可以通過SqlCommand對象進行數據庫操作了。對於我們將要編寫的事務處理程序,當然還需要定義一個SqlTransaction類型的對象。並且看到SqlCommand對象的Transcation屬性,我們很容易想到新建的SqlTransaction對象應該與它關聯起來。

基於以上認識,下面我們就開始動手寫我們的第一個事務處理程序。我們可以很熟練地寫出下面這一段程序:

//DoTran.csusing 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=new SqlTransaction();
   try
   {
    myComm.Connection=myConn;
    myComm.Transaction=myTran;
   
    file://定位到pubs數據庫 
    myComm.CommandText="USE pubs";
    myComm.ExecuteNonQuery();
    file://更新數據
    file://將所有的計算機類圖書
    myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
    myComm.ExecuteNonQuery();//提交事務
    myTran.Commit();
   }
   catch(Exception err)
   {
    throw new ApplicationException("事務操作出錯,系統信息:"+err.Message);
   }
   finally
   {
    myConn.Close();
   }
  }
  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();
 }
}
}

顯然,這個程序非常簡單,我們非常自信地編譯它,但是,出乎意料的結果使我們的成就感頓時煙消雲散:

error CS1501: 重載"SqlTransaction"方法未獲取"0"參數

是什麼原因呢?注意到我們初始化的代碼:

SqlTransaction myTran=new SqlTransaction();

顯然,問題出在這裡,事實上,SqlTransaction類並沒有公共的構造函數,我們不能這樣新建一個SqlTrancaction類型的變量。在事務處理之前確實需要有一個SqlTransaction類型的變量,將該變量關聯到SqlCommand類的Transcation屬性也是必要的,但是初始化方法卻比較特別一點。在初始化SqlTransaction類時,你需要使用SqlConnection類的BeginTranscation()方法:

SqlTransaction myTran; myTran=myConn.BeginTransaction();  

該方法返回一個SqlTransaction類型的變量。在調用BeginTransaction()方法以後,所有基於該數據連接對象的SQL語句執行動作都將被認為是事務MyTran的一部分。同時,你也可以在該方法的參數中指定事務隔離級別和事務名稱,如:

SqlTransaction myTran;
myTran=myConn.BeginTransaction(IsolationLevel.ReadCommitted,"SampleTransaction");

關於隔離級別的概念我們將在隨後的內容中探討,在這裡我們只需牢記一個事務是如何被啟動,並且關聯到特定的數據鏈接的。

先不要急著去搞懂我們的事務都干了些什麼,看到這一行:

myTran.Commit();

是的,這就是事務的提交方式。該語句執行後,事務的所有數據庫操作將生效,並且為數據庫事務的持久性機制所保持--即使系統在這以後發生致命錯誤,該事務對數據庫的影響也不會消失。

對上面的程序做了修改之後我們可以得到如下代碼(為了節約篇幅,重復之處已省略,請參照前文):

//DoTran.cs……}
file://執行事務處理
public void DoTran()
{
 file://建立連接並打開
 SqlConnection myConn=GetConn();
 myConn.Open();
 SqlCommand myComm=new SqlCommand();
 file://SqlTransaction myTran=new SqlTransaction();
 file://注意,SqlTransaction類無公開的構造函數
 SqlTransaction myTran;
 file://創建一個事務
 myTran=myConn.BeginTransaction();
 try
 {
  file://從此開始,基於該連接的數據操作都被認為是事務的一部分
  file://下面綁定連接和事務對象
  myComm.Connection=myConn;
  myComm.Transaction=myTran; file://定位到pubs數據庫
  myComm.CommandText="USE pubs";
  myComm.ExecuteNonQuery();//更新數據
  file://將所有的計算機類圖書
  myComm.CommandText="UPDATE roysched SET royalty = royalty * 1.10 WHERE title_id LIKE 'Pc%'";
  myComm.ExecuteNonQuery();
 
  file://提交事務
  myTran.Commit();
 }
 catch(Exception err)
 {
  throw new ApplicationException("事務操作出錯,系統信息:"+err.Message);
  }
 finally
 {
  myConn.Close();
  }
}
……

到此為止,我們僅僅掌握了如何開始和提交事務。下一步我們必須考慮的是在事務中可以干什麼和不可以干什麼。

另一個走向極端的錯誤

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

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

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)的論壇裡找到答案。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved