データベース 同時アクセスの競合(パート1-問題の発見)

この記事では、SQLのロックの種類ではなく、異なる接続から同じデータにアクセスするときに問題を解決する方法を検討します。更新の一部が失われる可能性があります。 この記事は特定のデータベースに依存しておらず、多くの人にとって同様に興味深いかもしれません。



1つの接続でいくつかのレコードを読み取り、それらを更新しようとすると、常にこのような状況が発生する可能性があります。 しかし、現時点では、それらを編集してから保存しようとしましたが、同じ接続で同じレコードが既に更新されています。 つまり、最初のプロセスがデータを読み取り、その後、2番目のプロセスが同じデータを読み取り、2番目のプロセスが同じデータを更新してから1番目のプロセスがそれを実行できるようになると、最初のプロセスがこのデータを更新しようとすると競合が発生します。



複数の接続からデータベースにアクセスして変更を加えた場合、競合の発生は時間と運の問題にすぎません。



アプリケーション自体は、この競合を解決するために必要なアクションを決定する必要があります。 たとえば、サイト管理者が通常のユーザーのデータを表示するページに移動した場合(管理者はこのデータを更新できます)、管理ページがデータベースからユーザーデータを読み取り、通常のユーザーがユーザーデータを表示するページにアクセスした場合管理者が変更を保存すると、競合が発生します。 競合が発生しない場合、通常のユーザーの変更はブロックされ、失われます。 それ以外の場合-管理者の変更は失われます。 それぞれの場合にどのような振る舞いをするべきか-これは複雑な問題です。 最初のステップはそれを見つけることです。 2つ目は許可することです。 同時アクセスの競合を解決するには、楽観的と悲観的の2つの基本的なアプローチがあります。



この記事の最初の部分では、問題自体とその解決方法について説明します。 記事の2番目の部分では、LINQ to SQLの競合を解決する方法について説明します。 おそらく、Hibernateでの競合を解決する方法を同僚に説得して説得することもできます(ただし、これについては後ほどお知らせします)。 これらの記事はより多くのスペースを占めるため、最初の部分は非常に簡単ですが、個別に説明します。



楽観的なソリューション



楽観的な方法は、ほとんどの場合、競合がないという事実に基づいています。 したがって、ロックはレコードに設定されません。 そして、同じデータを更新しようとしたときに突然競合が発生した場合、それを行います。 楽観的並列アクセスの競合処理は悲観的よりも複雑ですが、多数のユーザーがいる現代のアプリケーションではより効率的に機能します。 店の製品を見るためには、他の誰かが現在それを見ているので、待たなければならないことを想像してください。



競合検出



特別な[バージョン]フィールド(または、たとえば、最終更新日)がある場合、プロンプトが表示されたら、読み取り後にレコードが変更されたかどうかを簡単に確認できます。 このフィールドがない場合は、競合検出に関係するフィールドを決定する必要があります。



WHERE句で主キーのみを指定する代わりに、レコードを更新する場合は、更新に関連するすべての列を指定します。



たとえば、 Customerオブジェクトを更新し、 CompanyName、ContactTitle、ContactNameの各フィールドに新しい値を割り当てたいとします。 そして、 CompanyName (常に)、 ContactName (更新時のみ)フィールドを競合検索に参加させ、 ContactTitleを関与させたくないとしましょう。 この場合、リクエストは次のようになります。



UPDATE Customers

SET CompanyName = 'Art Sanders Park',

ContactName = 'Samuel Arthur Sanders',

ContactTitle = 'President'

WHERE CompanyName = 'Lonesome Pine Restaurant' AND

ContactName = 'Fran Wilson' AND

CustomerID = 'LONEP'









この例では、 where句の列の値は、データベースから読み取られた列の初期値です。 ご覧のとおり、 ContactTitleフィールド競合検索に参加しませんでした。 私たちにとってはそれほど重要ではないと判断しました。



列を指定する代わりに、Version ...フィールドを使用して、バージョン番号または最後の更新の日付を保存することもできます。 この場合、いずれかの接続でバージョンを更新すると、このフィールドが更新されます。



読み取り後に誰かがレコードを更新した場合、リクエストはデータベース内のレコードを変更しません。 これを行うには、クエリの後、 @@ ROWCOUNTをチェックし、レコードが更新されているかどうかを確認します 。 そうでない場合は、並列アクセスの競合が発生しました。



競合が見つかったら、それを解決する必要があります。 競合の解決方法は異なる場合がありますが、記事の2番目の部分では、LINQ to SQLでこれがどのように行われるかを詳細に検討し、Hibernateでどのように行われるかを同僚が説明します(記事の3番目の部分)。



悲観的な解決方法



名前が示すように、悲観的なアプローチは、あなたが読んでいるレコードがその更新時に競合を引き起こす最悪の事態を示唆しています。 幸いなことに、これを行うことは難しくありません。1つのトランザクション内のデータベースで読み取りと更新を行うだけです。



並列処理に対する悲観的なアプローチでは、データベースがトランザクションによってブロックされているため、解決する必要のある競合はありません。



しかし、すでに述べたように、この方法は、複数の接続からのデータを同時に処理する機能が必要なアプリケーションにはあまり適していません。 ロックをインストールする場合、ロックが解除されるまで誰もデータを更新できません。



All Articles