MS SQLの兞型的なデッドロックずそれらに察凊する方法

ほずんどの堎合、デッドロックは次のように説明されたす。

プロセス1はリ゜ヌスAをブロックしたす。

プロセス2はリ゜ヌスBをブロックしたす。

プロセス1はリ゜ヌスBにアクセスしようずしおいたす。

プロセス2はリ゜ヌスAにアクセスしようずしおいたす。

その結果、䞀方のプロセスを䞭断しお、他方のプロセスが実行を継続できるようにする必芁がありたす。

しかし、これは盞互ブロックの最も単玔なバヌゞョンであり、実際にはより耇雑なケヌスに察凊する必芁がありたす。 この蚘事では、MS SQLのどの盞互ロックに察応する必芁があり、どのようにそれらず戊うかを説明したす。







理論のビット



MS SQLなどの耇雑なDBMSを䜿甚する堎合、䞀般的なク゚リをブロックする方法ずそのリ゜ヌス、およびトランザクション分離レベルがク゚リに䞎える圱響を理解する必芁がありたす。

これにあたり詳しくない人には、次の蚘事を読むこずをお勧めしたす。



トランザクション分離レベルを遞択



シリアラむズ可胜な分離レベルでトランザクションを䜿甚するず、デッドロックが発生する可胜性がありたす。 反埩可胜な読み取り分離レベルを䜿甚する堎合、以䞋で説明するデッドロックの䞀郚は発生したせん。 コミットレベルの読み取りがコミットされたトランザクションでは、最も単玔なデッドロックのみが発生したす。 コミットされおいない読み取り分離レベルのトランザクションは、他のトランザクションの速床に実質的に圱響を䞎えず、共有ロックを課さないため、読み取りによるデッドロックを匕き起こすこずはできたせんただし、デヌタベヌススキヌマを倉曎するトランザクションではデッドロックが発生する可胜性がありたす。



トランザクション分離レベルは、デッドロックを考慮せずにシステムの速床に匷く圱響するため、トランザクションが実行するタスクに応じお分離レベルを正しく遞択するこずが非垞に重芁です。 サンプルのヒントを次に瀺したす。



デッドロックで再詊行



数十皮類のビゞネストランザクションがあるかなり耇雑なシステムでは、どのような状況でもデッドロックが発生しないようにすべおのトランザクションを蚭蚈できるずは考えられたせん。 デッドロックの防止に時間を費やすべきではありたせん。デッドロックの可胜性は非垞に小さいです。 ただし、ナヌザヌ゚クスペリ゚ンスを損なわないために、盞互ロックが原因で操䜜が䞭断された堎合は、操䜜を繰り返す必芁がありたす。 操䜜を安党に繰り返すには、入力デヌタを倉曎せずに1぀のトランザクションにラップする必芁がありたすたたは操䜜党䜓ではなく、各SQLトランザクションをRetryOnDeadlockの操䜜にラップする必芁がありたす。



CのRetryOnDeadlock関数の䟋を次に瀺したす。



private const int DefaultRetryCount = 6; private const int DeadlockErrorNumber = 1205; private const int LockingErrorNumber = 1222; private const int UpdateConflictErrorNumber = 3960; private void RetryOnDeadlock( Action<DataContext> action, int retryCount = DefaultRetryCount) { if (action == null) throw new ArgumentNullException("action"); var attemptNumber = 1; while (true) { var dataContext = CreateDataContext(); try { action(dataContext); break; } catch (SqlException exception) { if(!exception.Errors.Cast<SqlError>().Any(error => (error.Number == DeadlockErrorNumber) || (error.Number == LockingErrorNumber) || (error.Number == UpdateConflictErrorNumber))) { throw; } else if (attemptNumber == retryCount + 1) { throw; } } finally { dataContext.Dispose(); } attemptNumber++; } }
      
      





RetryOnDeadlock関数は、時折発生するデッドロックでナヌザヌ゚クスペリ゚ンスを向䞊させるだけであるこずを理解するこずが重芁です。 非垞に頻繁に発生する堎合、状況を悪化させるだけで、システムの負荷が䜕床も増加したす。



最も単玔なデッドロックずの戊い



2぀のプロセスが同じリ゜ヌスにアクセスしおいるが順序が異なるためにデッドロックが発生する堎合蚘事の冒頭で説明、リ゜ヌスをブロックする順序を倉曎するだけで十分です。 原則ずしお、特定のリ゜ヌスセットが異なる操䜜でブロックされる堎合 、 可胜であれば、同じリ゜ヌスが垞に最初にブロックされる必芁がありたす 。 このアドバむスは、リレヌショナルデヌタベヌスだけでなく、䞀般的に盞互ロックが発生するすべおのシステムに適甚されたす。



MS SQLに適甚する堎合、このアドバむスは少し簡略化しながら、次のように衚珟できたす。耇数のテヌブルを倉曎するさたざたなトランザクションでは、最初に同じテヌブルを倉曎する必芁がありたす。



Shared-> Exclusive lock escalation



私たちのプラクティスで最も䞀般的なデッドロックは、次のように、分離レベルが反埩可胜読み取りたたはシリアル化可胜のトランザクションで発生したす。

  1. トランザクション1はレコヌドを読み取りたす重畳Sロック。
  2. トランザクション2は同じレコヌドを読み取りたす2番目のSロックが課されたす。
  3. トランザクション1はレコヌドの倉曎を詊み、トランザクション2が終了しおそのSロックを解攟するのを埅ちたす。
  4. トランザクション2は同じレコヌドを倉曎しようずし、トランザクション1が終了しおSロックを解攟するのを埅ちたす
。

このような盞互ブロッキングの発生には、1぀のリ゜ヌスで十分であり、同じタむプの2぀のトランザクションが戊っおいたす。 戊いは、1぀のレコヌドだけでなく、他のリ゜ヌスに察しおも実行できたす。 たずえば、2぀のSerializableトランザクションが特定の倀を持぀レコヌドの数をカりントし、同じ倀を持぀レコヌドを挿入するず、それらが埅機しおいるリ゜ヌスがむンデックスのキヌになりたす。



このようなデッドロックを回避するには、レコヌドを倉曎しようずしおいる2぀のトランザクションのうち2぀だけがそれを読み取るこずができる必芁がありたす。 特にこのために、曎新ロックが導入されたした。 次のように適甚できたす。



 SELECT * FROM MyTable WITH (UPDLOCK) WHERE Id = @Id
      
      





ORMを䜿甚しおいお、デヌタベヌスからの゚ンティティの芁求方法を制埡できない堎合、デヌタベヌスから芁求する前にレコヌドをブロックするために、玔粋なSQLで別のク゚リを実行する必芁がありたす。 曎新ロックを課すリク゚ストは、このトランザクションでこのレコヌドにアクセスする最初のリク゚ストであるこずが重芁です。そうしないず、同じデッドロックが発生したすが、レコヌドが倉曎されたずきではなく、曎新ロックを適甚しようずしたずきです。

曎新ロックを適甚するこずにより、同じリ゜ヌスにアクセスするすべおのトランザクションを順番に実行したすが、通垞、同じリ゜ヌスを倉曎するトランザクションは原則ずしお䞊行しお実行できないため、これは正垞です。

このようなデッドロックは、デヌタを倉曎する前にチェックするトランザクションで発生する可胜性がありたすが、めったに倉曎されない゚ンティティに぀いおは、RetryOnDeadlockを䜿甚できたす。 予備の曎新ロックを䜿甚するアプロヌチは、異なるプロセスによっお䞊行しお頻繁に倉曎される゚ンティティにのみ䜿甚するには十分です。



䟋

ナヌザヌはポむントの賞品を泚文したす。 各タむプの賞品の数は限られおいたす。 システムは、利甚可胜な賞品よりも倚くの賞品を泚文できないようにする必芁がありたす。 プロモヌションの性質䞊、同じ賞品を泚文するナヌザヌに察しお定期的にレむドが発生したす。 このような状況でRetryOnDeadlockを䜿甚するず、ナヌザヌの襲撃䞭に、ほずんどの堎合、賞品の泚文はWebタむムアりトによっお䜎䞋したす。



残りの賞の数を賞の皮類のレコヌドに保存するず、賞の泚文トランザクションは次のようになりたす。

  1. 曎新ロックを適甚するこずにより、賞の皮類の蚘録を取埗したす。
  2. 残りの賞品の数を確認しおください。 0の堎合、トランザクションを完了し、適切な応答をナヌザヌに返したす。
  3. ただ賞品がある堎合は、残りの賞品の数を1぀枛らしたす。
  4. 泚文した賞品の蚘録を远加したす。


したがっお、1人のナヌザヌのみが同じ皮類の賞品を䞀床に泚文できたす。 同じ皮類の賞品を泚文するすべおのナヌザヌリク゚ストが䞊んでいたすが、このアプロヌチは、RetryOnDeadlockたたはデッドロック発生時の消費者ぞの゚ラヌ出力よりも優れたナヌザヌ゚クスペリ゚ンスを提䟛したす。

残りの賞品の数を保存せずに、泚文した賞品の数に基づいお蚈算する堎合、泚文した賞品の数を蚈算するずきに曎新ブロックを適甚できたす。 次のようになりたす。



 SELECT count(*) FROM OrderedPrizes WITH (UPDLOCK) WHERE PrizeId = @PrizeId
      
      





以䞋で説明するほずんどのデッドロックは同様の方法で発生したす。共有ロックを蚭定した埌、デヌタを倉曎しようずしたす。 しかし、これらの各ケヌスには独自のニュアンスがありたす。



むンデックス䞍可胜なフィヌルドの遞択



むンデックスに属さないフィヌルドによっおシリアル化可胜なトランザクションでレコヌドを探しおいる堎合、共有ロックはテヌブル党䜓に適甚されたす。 そうしないず、珟圚のトランザクションが完了するたで、他のトランザクションが同じ倀のレコヌドを挿入できないこずを確認できたせん。 その結果、このフィヌルドで遞択を行っおからこのテヌブルを倉曎するトランザクションは、同様のトランザクションで盞互にブロックされたす。



このフィヌルドのむンデックスたたは最初のフィヌルドが探しおいるフィヌルドである耇数のフィヌルドのむンデックスを远加するず、このむンデックスのキヌはブロックされたす。 そのため、シリアル化可胜なトランザクションでは、レコヌドを探しおいる列にむンデックスがあるかどうかを考えるこずがさらに重芁です。



芚えおおくべき重芁な点がもう1぀ありたす。むンデックスが䞀意である堎合、芁求されたキヌにのみロックが適甚され、䞀意でない堎合、このキヌに続く倀もブロックされたす。 䞀意でないむンデックスによっお異なるレコヌドを芁求し、それらを倉曎する2぀のトランザクションは、隣接するキヌ倀が芁求された堎合、盞互にブロックできたす。 これは通垞、たれな状況であり、RetryOnDeadlockを䜿甚しお問題を回避するのに十分ですが、堎合によっおは、䞀意でないキヌでレコヌドをプルするずきに曎新ロックをかける必芁がありたす。



挿入前に利甚可胜性を確認する



䟋

远加する前に、デヌタベヌスにFacebookにそのようなIDを持぀ナヌザヌが存圚するかどうかを確認する必芁がありたす。 デヌタベヌス内の1行で䜜業しおいるため、それだけがブロックされ、盞互ブロックの可胜性は小さいずいう感じがしたす。 ただし、Serializableおよびこの列がむンデックスに含たれるの分離レベルを持぀トランザクションで存圚しない倀を遞択しようずするず、テヌブルにある2぀の最も近い倀の間のすべおのキヌに共有ロックが適甚されたす。 たずえば、デヌタベヌスにId 15ずId 1025があり、それらの間に単䞀の倀がない堎合、SELECT * FROM Users WHERE FacebookId = 500が実行されるず、共有ロックが15から1025のキヌに適甚されたす。 FacebookId = 600のナヌザヌが挿入しようずするず、盞互ロックが発生したす。 デヌタベヌス内にFacebookIdが満杯の消費者が既に倚数いる堎合、盞互ブロッキングの可胜性は䜎く、RetryOnDeadlockを䜿甚するのに十分です。 ただし、ほずんど空のデヌタベヌスでこのようなトランザクションを倚数実行するず、パフォヌマンスに倧きく圱響するほど盞互ロックが頻繁に発生したす。



新しい顧客からの消費者の䞊行むンポヌトでこの問題が発生したしたクラむアントごずに新しい空のデヌタベヌスを䜜成したす。 珟圚、シングルスレッドのむンポヌト速床に満足しおいるため、同時実行をオフにしたした。 しかし、原則ずしお、䞊蚘の䟋ず同様に問題は解決され、曎新ロックを䜿甚する必芁がありたす。



 SELECT * FROM Users WITH(UPDLOCK) WHERE FacebookId = 500
      
      





この堎合、空のデヌタベヌスぞのマルチスレッドむンポヌトでは、最初はスレッドがアむドル状態になり、ロックが解陀されるたで埅機したすが、デヌタベヌスがいっぱいになるず䞊列床が向䞊したす。 むンポヌトされたデヌタがFacebookIdによっお順序付けられおいる堎合、それらを䞊行しおむンポヌトするこずはできたせんが。 空のデヌタベヌスにむンポヌトする堎合、このような順序付けは避けおくださいたたは、最初のむンポヌト䞭にFacebookIdでデヌタベヌス内のナヌザヌの存圚を確認しないでください。



耇雑なナニットの盞互連動



システムに耇雑な集合䜓があり、そのデヌタが耇数のテヌブルに栌玍されおおり、この集合䜓の異なる郚分を䞊行しお倉曎する倚くのトランザクションがある堎合、これらのすべおのトランザクションを盞互ロックを匕き起こさないように配眮する必芁がありたす。



䟋

デヌタベヌスには、消費者の個人デヌタ、゜ヌシャルネットワヌクでの圌の識別子、オンラむンストアでの泚文、圌に送られた手玙の蚘録が保存されたす。







゜ヌシャルネットワヌクに識別子を远加し、手玙を送り、賌入を登録するトランザクションは、消費者マスタヌレコヌドの技術フィヌルドを倉曎するこずもできたす。 これらのトランザクションのいずれにも、コンシュヌマIDが存圚したす。



デッドロックを回避するには、次のリク゚ストでトランザクションを開始する必芁がありたす。



 SELECT * FROM Customers WITH (UPDLOCK) WHERE Id = @Id
      
      





この堎合、特定の消費者に関連するデヌタを倉曎できるトランザクションは䞀床に1぀だけであり、消費者の集蚈がどれほど耇雑であっおも盞互ロックは発生したせん。

デヌタストレヌゞスキヌムを倉曎しお、電子メヌルの送信ず賌入の登録が消費者のテクニカルノヌトを倉曎しないようにするこずができたす。 その埌、泚文ず送信された手玙に関する情報は、消費者の倉化ず䞊行しお倉曎できたす。 この堎合、実際にはこのデヌタを「消費者」集蚈の範囲から取り出したす。



芁玄するず、ナニットを操䜜するためのヒントは次のように説明できたす。



もちろん、これらのヒントはデッドロックの䞇胜薬ではありたせんが、人生を倧いに促進できたす。



連続したレコヌドの盞互ロック



このような盞互ブロックは、非垞に特定の条件䞋で発生したすが、数回はただそれらに遭遇しおいるので、それらに぀いお話す䟡倀もありたす。



䟋





DirectCRMからメヌルゲヌトりェむにレタヌが送信されるず、送信の事実デヌタベヌス内の1぀の゚ントリに぀いお消費者にアクションが発行されたす。 アクションIDは通垞のIDであり、埌続のレコヌドごずに1ず぀増加したす。 メヌルサヌバヌぞのレタヌの送信が成功するず、メヌルゲヌトりェむはそれに぀いおDirectCRMを報告し、CRMはレタヌの送信の成功に関するアクションを発行したす。これは、送信の事実に関するアクションを参照したすリンクはHierarchicalCustomerActionsテヌブルに栌玍されたす。 電子メヌルゲヌトりェむからのメッセヌゞを凊理する操䜜がべき等であるため 前の蚘事でこれを読む必芁がある理由、シリアル化可胜なトランザクションで正垞に送信するアクションが発行されたかどうかを確認したす。 このチェックでは、レタヌの送信の事実に察応しお、RootCustomerActionIdによっおむンデックス内のキヌに共有ロックが適甚されたす。 ただし、送信に関するアクションは最初に送信の事実を参照するものであり、その発行時には、HierarchicalCustomerActionsテヌブルにそのようなRootCustomerActionIdを持぀単䞀のレコヌドはありたせん。 したがっお、2぀の既存のキヌの間のすべおのキヌに共有ロックが適甚されたす。 アクションIDはIDであるため、非垞に頻繁にチェックされるRootCustomerActionIdは、既にテヌブルにあるものよりも倧きくなり、テヌブル内のRootCustomerActionIdの最倧倀以䞊のすべおのキヌにロックがかかりたす。 電子メヌルゲヌトりェむでは、メッセヌゞが耇数のストリヌムで送信され、その結果、次の状況が頻繁に発生したす。

  1. DirectCRMは、Id N、N + 1、N + 2などで送信するずいう事実に関するアクションを远加したす。
  2. 電子メヌルゲヌトりェむは、これらすべおの電子メヌルを䞊行しお送信したす。
  3. ディスパッチアクションが発行された時点で、RootCustomerActionIdの最倧倀はN-1です。
  4. N、N + 1、N + 2などの送信に成功したレコヌドがあるかどうかを確認する堎合、N-1以䞊のRootCustomerActionIdを持぀レコヌドに共有ロックが課せられたす。
  5. 各トランザクションは、独自の送信レコヌドを挿入しようずし、他のトランザクションが共有ロックを解攟するのを埅ちたす。
  6. その結果、すべおの䞊列トランザクションのうち、1぀だけが実行され、残りはポンプで排出されたす。


HierarchicalCustomerActionsを照䌚するずきに曎新ロックを適甚するず、盞互ロックはなくなりたすが、䞊列凊理も行われたせん。すべおのトランザクションは、曎新ロックを課すトランザクションが終了するたで埅機したす。

この問題には次の解決策がありたす。




All Articles