オプティミスティック同時実行制御Optimistic Concurrency

マルチユーザー環境には、データベースのデータを更新するための 2 つのモデルがあります。オプティミスティック同時実行制御とペシミスティック同時実行制御です。In a multiuser environment, there are two models for updating data in a database: optimistic concurrency and pessimistic concurrency. DataSet オブジェクトは、データをリモート処理するときや、データと対話するときのように長時間にわたる利用状況では、オプティミスティック同時実行制御の使用を奨励するように設計されています。The DataSet object is designed to encourage the use of optimistic concurrency for long-running activities, such as remoting data and interacting with data.

ペシミスティック同時実行制御では、他のユーザーが現在のユーザーに影響を与えるようなデータ変更を実行するのを防ぐために、データ ソースで行をロックする必要があります。Pessimistic concurrency involves locking rows at the data source to prevent other users from modifying data in a way that affects the current user. ペシミスティック モデルでは、あるユーザーがロックの適用される操作を実行すると、他のユーザーは、ロックが解除されるまで、競合する操作を実行できません。In a pessimistic model, when a user performs an action that causes a lock to be applied, other users cannot perform actions that would conflict with the lock until the lock owner releases it. このモデルは、同時実行の競合が発生するときに、トランザクションをロールバックするよりもデータをロックで保護する方が低コストに抑えられるため、データの競合が多い環境で主に使用されます。This model is primarily used in environments where there is heavy contention for data, so that the cost of protecting data with locks is less than the cost of rolling back transactions if concurrency conflicts occur.

したがって、ペシミスティック同時実行制御モデルでは、行を更新するユーザーがロックを設定します。Therefore, in a pessimistic concurrency model, a user who updates a row establishes a lock. 更新を終了してロックを解除するまでは、他のユーザーはその行を変更できません。Until the user has finished the update and released the lock, no one else can change that row. そのため、ペシミスティック同時実行制御は、レコードをプログラムで処理する場合のようにロック時間が短いときの実装に適しています。For this reason, pessimistic concurrency is best implemented when lock times will be short, as in programmatic processing of records. ペシミスティック同時実行制御は、ユーザーがデータと対話していてレコードが比較的長時間ロックされる場合には適していません。Pessimistic concurrency is not a scalable option when users are interacting with data and causing records to be locked for relatively large periods of time.

注意

同じ操作で複数の行を更新する必要がある場合は、ペシミスティック同時実行制御でロックするよりも、トランザクションを作成する方が有効です。If you need to update multiple rows in the same operation, then creating a transaction is a more scalable option than using pessimistic locking.

これに対して、オプティミスティック同時実行制御を使用するユーザーは、行を読み取るときに行をロックしません。By contrast, users who use optimistic concurrency do not lock a row when reading it. ユーザーが行を更新するときは、行の読み取り後に別のユーザーがその行を変更したかどうかをアプリケーションで確認する必要があります。When a user wants to update a row, the application must determine whether another user has changed the row since it was read. オプティミスティック同時実行制御は、一般に、データの競合が少ない環境で使用されます。Optimistic concurrency is generally used in environments with a low contention for data. オプティミスティック同時実行制御を使用すると、レコードをロックする必要がないためパフォーマンスが向上します。レコードをロックするには、サーバー リソースを追加する必要があります。Optimistic concurrency improves performance because no locking of records is required, and locking of records requires additional server resources. また、レコードのロックを管理するためにデータベース サーバーに永続的に接続している必要があります。Also, in order to maintain record locks, a persistent connection to the database server is required. オプティミスティック同時実行制御モデルでは、サーバーとの接続が固定していないため、より短い時間で多数のクライアントを扱うことができます。Because this is not the case in an optimistic concurrency model, connections to the server are free to serve a larger number of clients in less time.

オプティミスティック同時実行制御モデルでは、ユーザーがデータベースから値を受け取った後、その値を変更する前に別のユーザーがその値を変更した場合に、違反が発生したと見なされます。In an optimistic concurrency model, a violation is considered to have occurred if, after a user receives a value from the database, another user modifies the value before the first user has attempted to modify it. サーバーが同時実行制御違反を解決する方法がよくわかる例を次に示します。How the server resolves a concurrency violation is best shown by first describing the following example.

オプティミスティック同時実行制御の例を次の表に示します。The following tables follow an example of optimistic concurrency.

午後 1 時に、User1 が、次の値を持つデータベースから行を読み取ります。At 1:00 p.m., User1 reads a row from the database with the following values:

CustID LastName FirstNameCustID LastName FirstName

101 Smith Bob101 Smith Bob

列名Column name 元の値Original value 現在の値Current value データベース内の値Value in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob BobBob BobBob

午後 1 時 1 分に、User2 が同じ行を読み取ります。At 1:01 p.m., User2 reads the same row.

User2 の変更、午後 1時 03分FirstName "Robert"には、"Bob"から、データベースを更新します。At 1:03 p.m., User2 changes FirstName from "Bob" to "Robert" and updates the database.

列名Column name 元の値Original value 現在の値Current value データベース内の値Value in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob RobertRobert BobBob

更新時のデータベース内の値は User2 が持つ元の値と一致しているため、更新は成功します。The update succeeds because the values in the database at the time of update match the original values that User2 has.

午後 1 時 5 分に、User1 が "Bob" の名を "James" に変更し、行の更新を試みます。At 1:05 p.m., User1 changes "Bob"'s first name to "James" and tries to update the row.

列名Column name 元の値Original value 現在の値Current value データベース内の値Value in database
CustIDCustID 101101 101101 101101
LastNameLastName SmithSmith SmithSmith SmithSmith
FirstNameFirstName BobBob JamesJames RobertRobert

この時点で、データベース内の値 ("Robert") は User1 が期待していた元の値 ("Bob") と一致していないため、User1 による更新はオプティミスティック同時実行制御違反となります。At this point, User1 encounters an optimistic concurrency violation because the value in the database ("Robert") no longer matches the original value that User1 was expecting ("Bob"). 同時実行制御違反は、更新が失敗したことを通知するだけです。The concurrency violation simply lets you know that the update failed. ここで、User2 による変更を User1 による変更で上書きするか、または User1 による変更をキャンセルするかの決定が必要になります。The decision now needs to be made whether to overwrite the changes supplied by User2 with the changes supplied by User1, or to cancel the changes by User1.

オプティミスティック同時実行制御違反テストTesting for Optimistic Concurrency Violations

オプティミスティック同時実行制御違反をテストするには、いくつか方法があります。There are several techniques for testing for an optimistic concurrency violation. 1 つは、テーブルにタイムスタンプ列を含める方法です。One involves including a timestamp column in the table. 一般に、データベースには、レコードを最後に更新した日付と時刻を識別するために使用するタイムスタンプ機能が用意されています。Databases commonly provide timestamp functionality that can be used to identify the date and time when the record was last updated. この機能を使用すると、timestamp 列がテーブル定義に組み込まれます。Using this technique, a timestamp column is included in the table definition. レコードが更新されると、タイムスタンプが更新されて現在の日付と時刻が反映されます。Whenever the record is updated, the timestamp is updated to reflect the current date and time. オプティミスティック同時実行制御違反テストでは、テーブル内容についてのクエリによって timestamp 列が返されます。In a test for optimistic concurrency violations, the timestamp column is returned with any query of the contents of the table. 更新しようとすると、データベースのタイムスタンプ値と、変更行に格納されている元のタイムスタンプ値が比較されます。When an update is attempted, the timestamp value in the database is compared to the original timestamp value contained in the modified row. 一致した場合、更新が実行され、timestamp 列が現在の時刻に更新されてその更新が反映されます。If they match, the update is performed and the timestamp column is updated with the current time to reflect the update. 一致しなかった場合は、オプティミスティック同時実行制御違反が発生します。If they do not match, an optimistic concurrency violation has occurred.

オプティミスティック同時実行制御違反をテストするためのもう 1 つの方法は、行のすべての列の元の値が、データベース内の値とまだ一致しているかどうかを検証することです。Another technique for testing for an optimistic concurrency violation is to verify that all the original column values in a row still match those found in the database. たとえば、次のクエリを見てみましょう。For example, consider the following query:

SELECT Col1, Col2, Col3 FROM Table1  

内の行を更新するときに、オプティミスティック同時実行制御違反をテストするTable1、次の UPDATE ステートメントを実行します。To test for an optimistic concurrency violation when updating a row in Table1, you would issue the following UPDATE statement:

UPDATE Table1 Set Col1 = @NewCol1Value,  
              Set Col2 = @NewCol2Value,  
              Set Col3 = @NewCol3Value  
WHERE Col1 = @OldCol1Value AND  
      Col2 = @OldCol2Value AND  
      Col3 = @OldCol3Value  

元の値がデータベース内の値と一致する間は、更新が実行されます。As long as the original values match the values in the database, the update is performed. 値が変更された場合は、WHERE 句の条件と一致する行を見つけることができないため更新できません。If a value has been modified, the update will not modify the row because the WHERE clause will not find a match.

クエリで、常に一意の主キー値を返すことをお勧めします。Note that it is recommended to always return a unique primary key value in your query. 一意の主キーを返さないと、UPDATE ステートメントによって複数の行が更新されることがあり、意図に反した結果となります。Otherwise, the preceding UPDATE statement may update more than one row, which might not be your intent.

データ ソースの列で null が使用できる場合は、ローカル テーブルおよびデータ ソースで一致する null 参照がないかどうかをチェックするように WHERE 句を拡張する必要があります。If a column at your data source allows nulls, you may need to extend your WHERE clause to check for a matching null reference in your local table and at the data source. たとえば、次の UPDATE ステートメントは、ローカル行の null 参照がデータ ソースの null 参照とまだ一致しているかどうか、つまり、ローカル行の値がデータ ソースの値とまだ一致しているかどうかをチェックします。For example, the following UPDATE statement verifies that a null reference in the local row still matches a null reference at the data source, or that the value in the local row still matches the value at the data source.

UPDATE Table1 Set Col1 = @NewVal1  
  WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1  

オプティミスティック同時実行制御モデルを使用するときは、より制限の少ない抽出条件を適用するように選択することもできます。You may also choose to apply less restrictive criteria when using an optimistic concurrency model. たとえば、WHERE 句で主キー列だけを使用すると、前回のクエリ実行後に主キー以外の列が更新されたかどうかに関係なく、データが上書きされます。For example, using only the primary key columns in the WHERE clause causes the data to be overwritten regardless of whether the other columns have been updated since the last query. WHERE 句を特定の列だけに適用することもできます。特定の列だけに WHERE 句を適用した場合は、特定のフィールドが前回のクエリ実行後に更新されていない限りデータが上書きされます。You can also apply a WHERE clause only to specific columns, resulting in data being overwritten unless particular fields have been updated since they were last queried.

DataAdapter.RowUpdated イベントThe DataAdapter.RowUpdated Event

RowUpdatedのイベント、DataAdapterオブジェクトは、オプティミスティック同時実行制御違反のアプリケーションに通知を提供する前に説明した手法と組み合わせて使用できます。The RowUpdated event of the DataAdapter object can be used in conjunction with the techniques described earlier, to provide notification to your application of optimistic concurrency violations. RowUpdatedを更新するには、各再試行の後に発生、 Modifiedから行をデータセットします。RowUpdated occurs after each attempt to update a Modified row from a DataSet. これにより、例外発生時の処理、カスタム エラー情報の追加、再試行ロジックの追加などの特別の処理コードを追加できます。This enables you to add special handling code, including processing when an exception occurs, adding custom error information, adding retry logic, and so on. RowUpdatedEventArgsオブジェクトを返します、 RecordsAffectedプロパティ テーブルで変更された行の特定の更新コマンドによって影響を受ける行の数を格納します。The RowUpdatedEventArgs object returns a RecordsAffected property containing the number of rows affected by a particular update command for a modified row in a table. オプティミスティック同時実行性をテストする更新コマンドを設定して、 RecordsAffectedプロパティは、その結果の値を返す 0、オプティミスティック同時実行制御違反が発生したときにレコードが更新されないためです。By setting the update command to test for optimistic concurrency, the RecordsAffected property will, as a result, return a value of 0 when an optimistic concurrency violation has occurred, because no records were updated. この場合、例外がスローされます。If this is the case, an exception is thrown. RowUpdatedイベントを使用すると、この状況の発生を処理し、適切な設定によって、例外を回避RowUpdatedEventArgs.Statusなどの値UpdateStatus.SkipCurrentRowします。The RowUpdated event enables you to handle this occurrence and avoid the exception by setting an appropriate RowUpdatedEventArgs.Status value, such as UpdateStatus.SkipCurrentRow. 詳細については、 RowUpdatedイベントを参照してくださいDataAdapter イベントの処理します。For more information about the RowUpdated event, see Handling DataAdapter Events.

必要に応じて、設定することができますDataAdapter.ContinueUpdateOnErrortrueを呼び出す前にUpdate、に格納されているエラー情報および応答するRowError行の場合に、特定のプロパティ、 Updateが完了します。Optionally, you can set DataAdapter.ContinueUpdateOnError to true, before calling Update, and respond to the error information stored in the RowError property of a particular row when the Update is completed. 詳細については、次を参照してください。行エラー情報します。For more information, see Row Error Information.

オプティミスティック同時実行制御の例Optimistic Concurrency Example

設定する単純な例を次に、 UpdateCommandDataAdapterをオプティミスティック同時実行性、テストし、使用して、 RowUpdatedをテストするためのイベントオプティミスティック同時実行制御違反。The following is a simple example that sets the UpdateCommand of a DataAdapter to test for optimistic concurrency, and then uses the RowUpdated event to test for optimistic concurrency violations. アプリケーションは、オプティミスティック同時実行制御違反が発生した場合に、設定、 RowErrorオプティミスティック同時実行制御違反を反映するように、更新が実行されている行のできます。When an optimistic concurrency violation is encountered, the application sets the RowError of the row that the update was issued for to reflect an optimistic concurrency violation.

UPDATE コマンドの WHERE 句に渡されるパラメーターの値にマップされますが、それぞれの列の値。Note that the parameter values passed to the WHERE clause of the UPDATE command are mapped to the Original values of their respective columns.

' Assumes connection is a valid SqlConnection.  
Dim adapter As SqlDataAdapter = New SqlDataAdapter( _  
  "SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", _  
  connection)  
  
' The Update command checks for optimistic concurrency violations  
' in the WHERE clause.  
adapter.UpdateCommand = New SqlCommand("UPDATE Customers " &  
  "(CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " & _  
  "WHERE CustomerID = @oldCustomerID AND CompanyName = " &  
  "@oldCompanyName", connection)  
adapter.UpdateCommand.Parameters.Add( _  
  "@CustomerID", SqlDbType.NChar, 5, "CustomerID")  
adapter.UpdateCommand.Parameters.Add( _  
  "@CompanyName", SqlDbType.NVarChar, 30, "CompanyName")  
  
' Pass the original values to the WHERE clause parameters.  
Dim parameter As SqlParameter = dataSet.UpdateCommand.Parameters.Add( _  
  "@oldCustomerID", SqlDbType.NChar, 5, "CustomerID")  
parameter.SourceVersion = DataRowVersion.Original  
parameter = adapter.UpdateCommand.Parameters.Add( _  
  "@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName")  
parameter.SourceVersion = DataRowVersion.Original  
  
' Add the RowUpdated event handler.  
AddHandler adapter.RowUpdated, New SqlRowUpdatedEventHandler( _  
  AddressOf OnRowUpdated)  
  
Dim dataSet As DataSet = New DataSet()  
adapter.Fill(dataSet, "Customers")  
  
' Modify the DataSet contents.  
adapter.Update(dataSet, "Customers")  
  
Dim dataRow As DataRow  
  
For Each dataRow In dataSet.Tables("Customers").Rows  
    If dataRow.HasErrors Then   
       Console.WriteLine(dataRow (0) & vbCrLf & dataRow.RowError)  
    End If  
Next  
  
Private Shared Sub OnRowUpdated( _  
  sender As object, args As SqlRowUpdatedEventArgs)  
   If args.RecordsAffected = 0  
      args.Row.RowError = "Optimistic Concurrency Violation!"  
      args.Status = UpdateStatus.SkipCurrentRow  
   End If  
End Sub  
// Assumes connection is a valid SqlConnection.  
SqlDataAdapter adapter = new SqlDataAdapter(  
  "SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID",  
  connection);  
  
// The Update command checks for optimistic concurrency violations  
// in the WHERE clause.  
adapter.UpdateCommand = new SqlCommand("UPDATE Customers Set CustomerID = @CustomerID, CompanyName = @CompanyName " +  
   "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", connection);  
adapter.UpdateCommand.Parameters.Add(  
  "@CustomerID", SqlDbType.NChar, 5, "CustomerID");  
adapter.UpdateCommand.Parameters.Add(  
  "@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");  
  
// Pass the original values to the WHERE clause parameters.  
SqlParameter parameter = adapter.UpdateCommand.Parameters.Add(  
  "@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");  
parameter.SourceVersion = DataRowVersion.Original;  
parameter = adapter.UpdateCommand.Parameters.Add(  
  "@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");  
parameter.SourceVersion = DataRowVersion.Original;  
  
// Add the RowUpdated event handler.  
adapter.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);  
  
DataSet dataSet = new DataSet();  
adapter.Fill(dataSet, "Customers");  
  
// Modify the DataSet contents.  
  
adapter.Update(dataSet, "Customers");  
  
foreach (DataRow dataRow in dataSet.Tables["Customers"].Rows)  
{  
    if (dataRow.HasErrors)  
       Console.WriteLine(dataRow [0] + "\n" + dataRow.RowError);  
}  
  
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)  
{  
  if (args.RecordsAffected == 0)   
  {  
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";  
    args.Status = UpdateStatus.SkipCurrentRow;  
  }  
}  

関連項目See Also

ADO.NET でのデータの取得および変更Retrieving and Modifying Data in ADO.NET
DataAdapter によるデータ ソースの更新Updating Data Sources with DataAdapters
行エラー情報Row Error Information
トランザクションと同時実行Transactions and Concurrency
ADO.NET のマネージド プロバイダーと DataSet デベロッパー センターADO.NET Managed Providers and DataSet Developer Center