共用方式為


ADO.NET 中的資料並行簡介

更新:2007 年 11 月

當多個使用者同時嘗試修改資料時,就需要建立控制項來避免任一使用者的修改對其他使用者同時進行的修改造成不利的影響。處理這種情況的系統就稱為並行控制項。

並行控制項的類型

一般來說,管理資料庫中並行狀況的方式有三:

  • 封閉式並行存取控制項 (Pessimistic Concurrency Control) :從資料錄被擷取開始一直到在資料庫中完成更新為止,使用者都無法使用資料列。

  • 開放式並行存取控制項 (Optimistic Concurrency Control):只有當資料實際上正在更新時,其他使用者才無法使用資料列。更新會檢查資料庫中的資料列並判斷是否有任何變更。嘗試更新已變更的資料錄會導致並行違規。

  • 後進先寫入 (Last In Wins):只有當資料實際上正在更新時,其他使用者才無法使用資料列。不過,這種方式並不會將更新與原始資料錄加以比較;而只會寫出資料錄,這有可能會覆寫其他使用者從您上次重新整理資料錄以來所做的變更。

封閉式並行存取

封閉式並行存取通常是用於以下兩個原因。首先,在一些情況下,相同資料錄的爭用機率極高。當並行衝突發生時,將資料鎖定的成本要比復原變更的成本來得低。

封閉式並行存取對於在交易時不方便改變資料錄的情況下也相當有用。範例之一就是存貨應用程式。假設現在有位公司業務代表要為潛在客戶檢查存貨。您一般會希望將資料錄鎖定到訂單產生為止,而這通常會將項目標示為已訂購並從可用存貨中移除。如果未產生訂單,則會解除鎖定來讓其他檢查存貨的使用者取得可用存貨的正確計數。

不過,封閉式並行存取控制項並不能用於中斷連接的架構。這裡的連接只開啟來讀取或更新資料,因此鎖定無法持久。除此之外,長期鎖定的應用程式是無法擴充的。

開放式並行存取

在開放式並行存取中,只有在存取資料庫時才會設定和保持鎖定。這種鎖定方式可避免其他使用者同時嘗試更新資料錄。資料在除了進行更新的這段期間之外都是可使用的。如需詳細資訊,請參閱開放式並行存取 (ADO.NET)

當嘗試更新時,變更資料列的原始版本會與資料庫中的現有資料列比較。如果兩者不同,則更新就會失敗並發生並行存取錯誤。這時就要由您決定是否要使用您建立的商務邏輯來調解兩資料列。

後進先寫入

在「後進先寫入」的情況中,並不會檢查原始資料,而只是將更新寫入資料庫。因此會發生下列情況是可理解的:

  • 使用者 A 從資料庫擷取一資料錄。

  • 使用者 B 從資料庫擷取同樣的資料錄,修改它並將更新的資料錄寫回資料庫。

  • 使用者 A 修改「舊」資料錄並將其寫回資料庫。

在以上案例中,使用者 A 永遠看不到使用者 B 所做的變更。如果您要使用並行控制項的「後進先寫入」方法,則請確定以上情況是可接受的。

ADO.NET 和 Visual Studio 中的並行控制項

ADO.NET 和 Visual Studio 都使用開放式並行存取,因為兩者的資料架構是以中斷連接資料為基礎。因此,您需要加入商務邏輯來解決使用開放式並行存取的問題。

如果您選擇使用開放式並行存取,通常有兩種方法來判斷是否發生變更:一是版本方法 (真正版本號碼或日期-時間戳記),另一種則是儲存所有值方法。

版本號碼方法

在版本號碼方法中,要更新的資料錄必須具有包含日期-時間戳記或版本號碼的資料行。當讀取資料錄時,就會在用戶端上儲存日期-時間戳記或版本號碼。接著這個值就會成為更新的一部分。

處理並行的方法之一是只有在 WHERE 子句中的值與資料錄上的值相符時才更新。這種方法以 SQL 表示如下:

UPDATE Table1 SET Column1 = @newvalue1, Column2 = @newvalue2 
WHERE DateTimeStamp = @origDateTimeStamp

除此之外,也可使用版本號碼來比較:

UPDATE Table1 SET Column1 = @newvalue1, Column2 = @newvalue2 
WHERE RowVersion = @origRowVersionValue

如果日期-時間戳記或版本號碼相符,資料存放區中的資料錄就不會變更,而且可放心使用資料集的新值來更新。如果不相符的話,就會傳回錯誤。您可編寫程式碼,在 Visual Studio 中實作這種形式的並行檢查。如此您也必須寫入程式碼來回應任何更新衝突。若要讓日期-時間戳記或版本號碼保持無誤,您需要在資料表上設定觸發程序在變更資料列時更新它。

儲存所有值方法

除了使用日期-時間戳記或版本號碼之外,另一個方法是在讀取資料錄時取得所有欄位的複本。ADO.NET 中的 DataSet 物件會維護每個修改之資料錄的兩個版本:一為原始版本 (原本從資料來源讀取的版本),另一個則是表示使用者更新的已修改版本。當嘗試將資料錄寫回資料來源時,會將資料列中的原始值與資料來源中的資料錄加以比較。如果兩者相符,則表示資料庫資料錄從讀取之後並未變更。在這種情況下,就會將資料集的變更值成功寫入資料庫。

每個資料配接器命令的四個命令 (DELETE、INSERT、SELECT 和 UPDATE) 都有個別的參數集合。每個命令的原始值和目前 (或修改) 值都有參數。

注意事項:

加入新的資料錄 (INSERT 命令) 只需要目前值,因為並沒有原始資料錄存在,而移除資料錄 (DELETE 命令) 則只需要原始值來找出要刪除的資料錄。

以下範例將示範更新一般 Customers 資料表的資料集命令所需的命令文字。這個命令是為動態 SQL 和開放式並行存取指定的。

UPDATE Customers SET CustomerID = @currCustomerID, CompanyName = @currCompanyName, ContactName = @currContactName,
       ContactTitle = currContactTitle, Address = @currAddress, City = @currCity, 
       PostalCode = @currPostalCode, Phone = @currPhone, Fax = @currFax
WHERE (CustomerID = @origCustomerID) AND (Address = @origAddress OR @origAddress IS NULL AND Address IS NULL) AND (City = @origCity OR @origCity IS NULL AND City IS NULL)
      AND (CompanyName = @origCompanyName OR @origCompanyName IS NULL AND CompanyName IS NULL) AND (ContactName = @origContactName OR @origContactName IS NULL AND ContactName IS NULL) AND (ContactTitle = @origContactTitle OR @origContactTitle IS NULL AND ContactTitle IS NULL) 
      AND (Fax = @origFax OR @origFax IS NULL AND Fax IS NULL) AND (Phone = @origPhone OR @origPhone IS NULL AND Phone IS NULL) AND (PostalCode = @origPostalCode OR @origPostalCode IS NULL AND PostalCode IS NULL);
SELECT CustomerID, CompanyName, ContactName, ContactTitle, Address, City,
       PostalCode, Phone, Fax
FROM Customers WHERE (CustomerID = @currCustomerID)

請注意,其中九個 SET 陳述式參數是代表將寫入資料庫的目前值,而九個 WHERE 陳述式參數則代表用來找出原始資料錄的原始值。

SET 陳述式中的前九個參數與參數集合中的前九個參數對應。這些參數的 SourceVersion 屬性都已設為 Current

WHERE 陳述式中接下來的九個參數是用於開放式並行存取。這些替代符號會與參數集合中接下來的九個參數相對應,而每個參數的 SourceVersion 屬性都設為 Original

SELECT 陳述式是用來在發生更新之後重新整理資料集。當您在 [進階 SQL 產生選項] 對話方塊中設定 [重新整理資料集] 選項時,就會產生這個陳述式。

注意事項:

上述的 SQL 陳述式使用具名參數,而 OleDbDataAdapter 命令則使用問號 (?) 來當做參數替代符號。

如果您在 [DataAdapter 組態精靈] 中選取 [開放式並行存取] 選項,則 Visual Studio 會依預設為您建立這些參數。您可以自己決定是否要依據您的業務需求來加入處理錯誤的程式碼。ADO.NET 提供 DBConcurrencyException 物件,可傳回違反並行規則的資料列。如需詳細資訊,請參閱 HOW TO:處理並行存取錯誤

請參閱

工作

HOW TO:處理並行存取錯誤

概念

開放式並行存取 (ADO.NET)

參考

DBConcurrencyException