3

私はウェブアプリケーションを持っています。その中でフォームを処理するフローは次のようになります。

  1. 検証
  2. エラーの一覧表示またはデータの挿入/更新

この特定のシナリオでは、ユーザー登録プロセスを開発していますが、データベース テーブル内の一意の値の可用性を確認することに基づいて、すべての種類のフォームに共通のソリューションを見つけようとしています。

このユーザー登録では、ユーザーのログインは一意である必要があります。検証フェーズでは、アプリケーションはデータベース テーブルでの可用性をチェックし、使用可能な場合は行を挿入します。パスワードやパスワードの確認など、他のフィールドも検証する必要があります。すべての検証は、1 つの HTTP 要求で 1 回行われます。

問題は、アプリケーションがその可用性をチェックした、最初のユーザーのプロセスがそれを挿入する前に、並列プロセスで別のユーザーによって取得されていないことを確認できないことです。2 人のユーザーが同じミリ秒で同じログインを入力する可能性が非常に低いことは理解していますが、いつか、数千のユーザーが同時に何らかのフォームにデータを入力する別のフォームでこれが起こる可能性があります。

検証にすでに合格している場合、ログインがすでに登録されていることを示すエラー メッセージがユーザーに表示されることはありません。

私が解決しようとしているのは、その可用性を確認した後、1 つのHTTP 要求に挿入する前に、一意の値が使用可能であることを確認することです。最初のユーザーが自分のパスワードとパスワードの確認をいじっていたときに、別のユーザーが同じ一意のログインを登録しても問題ありません。

この問題は、既存の行を使用して簡単に解決できます。SELECT で UPDATE を実行でき、トランザクション中にロックされるからです。しかし、存在しない行で同じことを行うことはできません。それが問題だ。これを解決するにはどうすればよいですか?


ここに私が知っているいくつかの解決策があります。それらのどれが最高かはわかりません。さらに、最善の方法が私に知られているかどうかはわかりませんので、あなたが知っている方法を共有してください.

テーブルのロック

過去にこの問題をテーブルロックで解決していましたが、これが最善の方法であったかどうかはわかりません。プロセスは次のようになりました。

  1. 書き込み用にテーブルをロックする
  2. 空室状況を確認する
  3. エラーを返すか、行を挿入します
  4. テーブルのロックを解除

テーブル全体をロックすることが最悪の解決策だと言う人もいます。たぶんそうかもしれませんが、それが私が自分で思いついた唯一の方法です。

ロックは 1 つの HTTP リクエストの間だけ維持され、もちろん複数のリクエストの間では維持されません。

エラーを挿入してキャッチする

この方法は、他の人から提案されました。彼らは、その列を一意のインデックス列にし、検証と一意性のチェックを 2 つのフェーズに分けることを提案しました。プロセスは次のようになります。

  1. データを検証する
  2. 検証がOKの場合、行を挿入します
  3. 行の挿入に失敗した場合、一意の値が利用できないというエラーが表示されます

もちろん、列を一意のインデックス列にしました。しかし、これは、データベースの機能を使用して検証時にエラーをスローするという意味ではありません。アプリケーションレベルで行う必要があります。

値の可用性をチェックして挿入するプロセスに例外は何もないため、このシナリオでは例外を試行してキャッチする方法が好きではないため、この方法は好きではありません。私はそれがチェック・アンド・リザーブ・アンド・インサートの方法であるべきだと信じています。ユーザー入力の検証は、例外に基づくべきではないと私は信じています。

間違っているかもしれませんが、これが私の現在の見解です。私が明らかに間違っていると思うなら、その理由を教えてください。

4

3 に答える 3

1

まず最初に、テーブルのロックは理想的なソリューションとはほど遠いものです。これを数千の同時ユーザーに拡張することが予想される場合、テーブル全体をロックすることは、データベースを停止させる確実な方法です。適切にスケーラブルなアプリケーションを作成するには、テーブル ロックからできるだけ遠ざける必要があります。

Try/catch は、一意のキーにバインドされた挿入を実行する方法です。私の意見では、それが最良の方法です。認識しなければならないことは、行レベルのロックを利用するトランザクション データベースは、いつでもデッドロックに対して脆弱 であるということです。通常の、おかしなことのない普通のクエリでも。これを念頭に置いて、トランザクション データベースを利用するアプリケーションは、技術的には、実行されたすべての書き込みクエリを try/catch ブロック内に配置する必要があります。

もちろん、通常の日常的な使用では、これはそれほど頻繁に発生しないため、そのように開発する人は多くありません. しかし、データベースの「エラー」は、何か間違ったことをしたという本当の意味でのエラーであるとは限りません。これらは、データの状態を伝える通常の方法です。

肝心なのは、回避できるロックが多いほど、アプリケーションのスケーラビリティが向上するということです。存在しない値に対して使用できたとしてもSELECT...FOR UPDATE、そのテーブルでのデッドロックの数が大幅に増加する可能性があります。これは、try/catch を使用して簡単に回避できるため、私は常に try/catch を使用しています。また、データベース ドライバー用の一般的なエラー ハンドラー ラッパーを作成して、一意のキーやデッドロックなどの一般的なエラーを検出し、それらを適切に処理するのは非常に簡単です。

于 2010-01-27T06:19:23.333 に答える
0

あなたが言うように、「エラーを挿入してキャッチする」はコーディングが簡単で、ユーザーをロックアウトしません。ステップ 1 をスキップして、単に挿入または失敗しようとすると、非常に重要なラウンドトリップを節約できます。ロードされたサーバー。

もう 1 つのオプションは、一時的に予約された ID のリストをメモリに保持することです。これは、しばらくして永続的に要求されない場合に解放されます。他のユーザーに注意する必要があるため、スレッドセーフなコレクションを使用し、おそらくユーザー セッションでそれをキーにする必要があります。

于 2010-01-27T06:13:47.567 に答える
0

列が ID、ClaimedUserName、TimeStamp、SessionID である新しいテーブルAvailableUsersを作成するか、オブジェクトの形式でセッションに保存します。

検証後、新しいユーザーが既に要求されているユーザー名で最初に投稿した場合は、セッションまたはデータベースから確認できます。

于 2010-01-27T06:18:22.257 に答える