ADO.NET におけるデータ同時実行制御の概要
更新 : 2007 年 11 月
同時に複数のユーザーがデータを変更しようとした場合に、1 人のユーザーによる変更が、ほかのユーザーが同時に実行する変更に悪影響を与えないようにする制御を確立する必要があります。このような状況で発生する問題を処理するシステムを "同時実行制御" といいます。
同時実行制御の種類
一般に、データベースにおける同時実行を管理するのによく使われる方法が 3 つあります。
ペシミスティック同時実行制御: レコードがフェッチされた時点からデータベース中で更新されるまで、ユーザーは行を使用できません。
オプティミスティック同時実行制御: データが実際に更新されている間だけ、他のユーザーは行を使用できません。データベースの行は更新によりチェックされ、変更されているかを判断されます。既に変更されているレコードを更新しようとすると、同時実行違反になります。
"最新操作の優先": データが実際に更新されている間だけ、他のユーザーは行を使用できません。ただし、更新した場合、元のレコードとは比較されず、単にレコードの書き込みが行われるだけです。この場合、最後にレコードを更新した後でほかのユーザーによって加えられた変更はすべて上書きされる可能性があります。
ペシミスティック同時実行制御
ペシミスティック同時実行制御は、一般に次の 2 つの理由で使用されます。1 つは、同じレコードに対して高い競合があるような場合です。データにロックを配置する場合のコストは、同時実行の競合が発生したときに変更をロール バックする場合のコストほど大きくありません。
ペシミスティック同時実行制御は、トランザクション実行中にレコードを変更すると、悪影響を及ぼすような場合にも便利です。たとえば、在庫アプリケーションの例を考えてみます。企業の担当者が潜在的な顧客のために在庫をチェックしているとします。一般に、注文が生成されるまでレコードをロックしておく必要があるため、通常は該当する項目に注文済みというステータスが設定され、利用可能な在庫から削除されます。注文が生成されない場合はロックが解除されるため、在庫をチェックするほかのユーザーは利用可能な在庫の正確な数量を取得できます。
ただし、ペシミスティック同時実行制御は非接続型アーキテクチャでは使用できません。接続はデータの読み取りや更新に必要なだけの時間しか確立されないため、長い時間にわたってロックを維持することはできません。また、長い時間にわたってロックを維持するアプリケーションはスケーラブルではありません。
オプティミスティック同時実行制御
オプティミスティック同時実行制御の場合は、データベースにアクセスしている間だけロックが設定され、保持されます。ロックにより、ほかのユーザーがレコードを同時に更新するのを防ぎます。データを更新しているとき以外は、いつでもデータを利用できます。詳細については、「オプティミスティック同時実行制御 (ADO.NET)」を参照してください。
データを更新しようとすると、変更した行の元のバージョンがデータベースの既存の行と比較されます。2 つの行が異なっている場合、更新は同時実行エラーのために失敗します。そのときに、作成したビジネス ロジックを使って 2 つの行を調整するかどうかは任意です。
最新操作の優先
"最新操作の優先" の場合は元のデータのチェックは行われず、更新データがデータベースに単に書き込まれるだけです。以下のような状況が発生する可能性があることがわかっています。
ユーザー A がレコードをデータベースからフェッチする。
ユーザー B が同じレコードをデータベースからフェッチし、変更し、更新したレコードをデータベースに戻す。
ユーザー A が "古い" レコードを変更し、データベースに書き込む。
以上のような状況では、ユーザー B による変更はユーザー A から見えません。同時実行制御の "最新操作の優先" アプローチを使用する場合は、この状況が発生する可能性があることに注意してください。
ADO.NET および Visual Studio における同時実行制御
ADO.NET および Visual Studio はデータ アーキテクチャが非接続データに基づいているため、オプティミスティック同時実行制御を使用します。したがって、ビジネス ロジックを追加し、オプティミスティック同時実行制御による問題を解決する必要があります。
オプティミスティック同時実行制御の使用を選択する場合は、変更が発生しているかどうかを判断する一般的な方法が 2 つあります。バージョン番号アプローチ (真のバージョン番号または日付時刻スタンプ) と、すべての値を保存するアプローチです。
バージョン番号アプローチ
バージョン番号アプローチの場合、更新するレコードは日付時刻スタンプまたはバージョン番号を含む列を持っている必要があります。日付時刻スタンプまたはバージョン番号は、レコードが読み取られるときにクライアントに保存されます。この値は更新の一部になります。
同時実行を処理する 1 つの方法として、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 オブジェクトは、変更した各レコードについてそれぞれ 2 つのバージョンを保持します。元のバージョン (最初にデータ ソースから読み取られたバージョン) と、ユーザーが行った更新を含んだ変更されたバージョンです。レコードをデータ ソースに書き込もうとすると、データ行の元の値がデータ ソースのレコードと比較されます。2 つの値が一致した場合は、データベース レコードが読み取られた後で変更されていないことを意味しています。その場合は、データセットの変更された値をデータベースに正しく書き込むことができます。
各データアダプタ コマンドには 4 つのコマンド (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 ステートメントの 9 つのパラメータはデータベースに書き込まれる現在の値を表し、WHERE ステートメントの 9 つのパラメータは元のレコードを探すのに使用される元の値を表していることに注意してください。
最初の 9 つの SET ステートメント パラメータは、パラメータ コレクションの最初の 9 つのパラメータに対応しています。これらのパラメータにより SourceVersion プロパティが Current に設定されます。
次の 9 つの WHERE ステートメントのパラメータはオプティミスティック同時実行制御に使用されます。これらのプレースホルダはパラメータ コレクションの次の 9 つのパラメータに対応しており、各パラメータにより SourceVersion プロパティが Original に設定されます。
SELECT ステートメントは更新の発生後にデータセットを書き直すのに使用されます。[SQL 生成の詳細オプション] ダイアログ ボックスの [データセットの更新] オプションを設定すると、Select ステートメントが生成されます。
メモ : |
---|
上記の SQL ステートメントでは名前付きパラメータを使用していますが、OleDbDataAdapter コマンドはパラメータ プレースホルダとして疑問符 (?) を使用します。 |
Visual Studio では、データアダプタ構成ウィザードの [オプティミスティック同時実行制御] をクリックすると既定で上記のパラメータが作成されます。固有のビジネス ロジック要件に基づいてエラーを処理するコードを追加するかどうかは任意です。ADO.NET は、同時実行規則に違反する行を返す DBConcurrencyException オブジェクトを提供します。詳細については、「方法 : 同時実行エラーを処理する」を参照してください。