web-dev-qa-db-ja.com

UNIQUE制約とINSERT前のチェック

Id、Property、Property_Valueの列を持つSQLサーバーテーブルRealEstateがあります。このテーブルには約500万から1000万の行があり、将来さらに増える可能性があります。 Id、Property、Property_Valueの組み合わせがこのテーブルに存在しない場合にのみ、行を挿入したいと思います。

表の例-

1,Rooms,5
1,Bath,2
1,Address,New York
2,Rooms,2
2,Bath,1
2,Address,Miami

挿入2,Address,Miamiは許可されるべきではありません。だが、 2,Price,2billion 大丈夫。これを行うための「最良の」方法とwhyを知りたいです。なぜその部分が私にとって最も重要なのか。チェックの2つの方法は-

  1. アプリケーションレベル-アプリは、行を挿入する前に、行が存在するかどうかを確認する必要があります。
  2. データベースレベルで-3つの列すべてに一意の制約を設定し、person/appの代わりにデータベースにチェックを実行させます。

一方が他方よりも優れているシナリオはありますか?

ありがとう。

PS:すでに同様の質問があることは知っていますが、それは私の問題に答えません- 一意の制約と事前チェック また、UNIQUEはすべてのデータベースに適用できると思うので、mysqlタグとOracleタグを削除する必要はないと思います。

26
Steam

ほとんどの場合、この2つの違いは十分に小さいので、コードを初めて見る人にとって最も理解しやすい実装を選択することによって選択を決定する必要があります。

ただし、例外処理にはいくつかの利点があると思いますsmall利点:

  • 例外処理により、潜在的な競合状態が回避されます。別のプロセスがチェックと挿入の間にレコードを挿入すると、「チェックしてから挿入」メソッドが失敗する場合があります。したがって、「チェックしてから挿入」を実行している場合でも、挿入で例外処理が必要であり、とにかくすでに例外処理を実行している場合は、最初のチェックを省略した方がよいでしょう。

  • コードがストアドプロシージャではなく、ネットワークを介してデータベースと対話する必要がある場合(つまり、アプリケーションとデータベースが同じボックス上にない場合)、2つの別々のネットワーク呼び出し(1つはチェック用と1つはチェック用)を避けたい場合があります。その他の挿入)および例外処理を介してそれを行うと、単一のネットワーク呼び出しですべてを処理する簡単な方法が提供されます。現在、2番目のネットワーク呼び出しを回避しながら「checkthen insert」メソッドを実行する方法はたくさんありますが、例外をキャッチすることが最も簡単な方法である可能性があります。

一方、例外処理には一意の制約(実際には一意のインデックス)が必要であり、パフォーマンスのトレードオフが伴います。

  • 一意の制約の作成は、非常に大きなテーブルでは遅くなり、そのテーブルへのすべての挿入でパフォーマンスが低下します。本当に大規模なデータベースでは、制約を適用するために使用される一意のインデックスによって消費される余分なディスクスペースの予算も立てる必要があります。
  • 一方、クエリでそのインデックスを利用できる場合は、テーブルからの選択が速くなる可能性があります。

また、実際に実行したいのが「update else insert」である状況にある場合(つまり、一意の値を持つレコードがすでに存在する場合は、そのレコードを更新する必要があります。それ以外の場合は、新しいレコードを挿入します。レコード)次に、実際に使用したいのは、特定のデータベースのUPSERTメソッド(ある場合)です。 SQL ServerとOracleの場合、これはMERGEステートメントになります。

14
ivanatpr

#1(ルックアップを行う)のコストが妥当であるかどうかに応じて、私は両方を行います。少なくとも、私が最も経験を積んだデータベースであるOracleでは。

理論的根拠:

  • 一意/主キーはデータモデル設計のコア部分である必要があります。それらを実装しない理由はわかりません。データが多すぎて一意のインデックスを維持することでパフォーマンスが低下する場合:
    • それはロットのデータです
    • パーティションを作成するか、OLTP作業から離れた場所にアーカイブします
  • 制約が多いほど、アプリケーションのロジックエラーに対してデータが安全になります。
  • 行が最初に存在することを確認すると、その行から他の情報を簡単に抽出してエラーメッセージの一部として使用したり、アプリケーションロジックをフォークして重複に対処したりできます。
  • Oracleでは、DMLステートメントのロールバックは比較的コストがかかります。これは、Oracleがデフォルトで成功することを期待しているためです(つまり、書き込まれたCOMMIT変更)。
6
Ben

これは質問に直接答えるものではありませんが、ウィキペディアよりも優れており、リンクがいつか死んでしまう可能性があるため、ここに投稿すると役立つかもしれないと思いました。

リンク- http://www.celticwolf.com/blog/2010/04/27/what-is-a-race-condition/

ウィキペディアには競合状態についての適切な説明がありますが、プログラミングの基本を理解していないと理解するのが困難です。上記のように識別子を生成する例を使用して、あまり専門的ではない用語で説明しようと思います。また、人間の活動へのアナロジーを使用して、アイデアを伝えようとします。

競合状態とは、2つ以上のプログラム(または単一のプログラムの独立した部分)がすべて同時に何らかのリソースを取得しようとし、その結果、誤った回答または競合が発生することです。このリソースは、次に利用可能な予約時間などの情報にすることも、スプレッドシートなどの何かへの排他的アクセスにすることもできます。 Microsoft Excelを使用して共有ドライブ上のドキュメントを編集したことがある場合は、他の誰かがすでにスプレッドシートを編集しているとExcelから言われた経験があるでしょう。このエラーメッセージは、潜在的な競合状態を適切に処理し、エラーを防止するExcelの方法です。

プログラムの一般的なタスクは、ある種の次に利用可能な値を識別し、それを割り当てることです。この手法は、請求書番号や学生IDなどに使用されます。これは以前に解決された古い問題です。最も一般的な解決策の1つは、データを格納しているデータベースが数値を生成できるようにすることです。他にも解決策があり、それらにはすべて長所と短所があります。

残念ながら、この分野を知らない、または単にプログラミングが苦手なプログラマーは、自分でロールしようとすることがよくあります。賢い人は、それが見た目よりもはるかに複雑な問題であることをすぐに発見し、既存の解決策を探します。悪い人は問題を決して見ないか、一度見たら、エラーを修正せずに実行不可能な解決策をこれまで以上に複雑にすることを主張します。学生IDの例を見てみましょう。初心者プログラマーは、「次の学生番号を知るために、最後の学生番号を取得してインクリメントするだけです」と言います。ボンネットの下で何が起こるかは次のとおりです。

  1. ベティ、管理者。入学事務局のアシスタントが学生管理プログラムを開始します。これは実際には彼女のPCで実行されるプログラムの単なるコピーであることに注意してください。学校のネットワークを介してデータベースサーバーと通信しますが、他のPCで実行されているプログラムの他のコピーと通信する方法はありません。
  2. Bettyは、Bob Smithの新しい学生レコードを作成し、すべての情報を入力します。
  3. ベティがデータ入力をしている間、別の管理者であるジョージ。アシスタントは、自分のPCで学生管理プログラムを起動し、GinaVerdeのレコードの作成を開始します。
  4. ジョージはより速いタイピストなので、ベティと同時にフィニッシュします。両方が同時に「保存」ボタンを押します。
  5. Bettyのプログラムはデータベースサーバーに接続し、使用中の最大の学生数である5012を取得します。
  6. ジョージのプログラムは、同時に、同じ質問に対して同じ答えを取得します。
  7. どちらのプログラムも、保存するレコードの新しい学生IDを5013にすることを決定します。その情報をレコードに追加してから、データベースに保存します。
  8. 現在、Bob Smith(Bettyの学生)とGina Verde(Georgeの学生)は同じ学生IDを持っています。

この学生IDは、成績から食堂の食事カードまで、あらゆる種類の記録に添付されます。最終的にこの問題が明らかになり、誰かがそれらの1つに新しいIDを割り当て、混同されたレコードを分類するために多くの時間を費やす必要があります。

私がこの問題を人々に説明するとき、通常の反応は「しかし、それは実際にはどのくらいの頻度で起こりますか?決して、そうではありませんか?」違う。まず、データ入力がスタッフによって行われている場合、通常、データ入力は比較的短い期間に全員によって行われます。これにより、オーバーラップの可能性が高くなります。問題のアプリケーションが一般に公開されているWebアプリケーションである場合、2人が同時に[保存]ボタンを押す可能性はさらに高くなります。最近、これを本番システムで見ました。これはパブリックベータ版のWebアプリケーションでした。利用率は非常に低く、毎日数人しか登録していませんでした。それにもかかわらず、6組の人々が数か月の間に同一のIDを取得することができました。不思議に思うかもしれませんが、いいえ、私も私のチームの誰もそのコードを書きませんでした。しかし、その問題が何度も発生したことに私たちは非常に驚いていました。後から考えると、そうすべきではありませんでした。これは本当にマーフィーの法則の単純な適用です。

この問題をどのように回避できますか?最も簡単な方法は、十分にテストされた問題に対する既存のソリューションを使用することです。すべての主要なデータベース(MS SQL Server、Oracle、MySQL、PostgreSQLなど)には、重複を作成せずに数値をインクリメントする方法があります。 MS SQLサーバーはこれを「ID」列と呼び、MySQLはこれを「自動番号」列と呼びますが、機能は同じです。新しいレコードを挿入するたびに、新しい識別子が自動的に作成され、一意であることが保証されます。これにより、上記のシナリオが次のように変更されます。

  1. ベティ、管理者。入学事務局のアシスタントが学生管理プログラムを開始します。これは実際には彼女のPCで実行されるプログラムの単なるコピーであることに注意してください。学校のネットワークを介してデータベースサーバーと通信しますが、他のPCで実行されているプログラムの他のコピーと通信する方法はありません。
  2. Bettyは、Bob Smithの新しい学生レコードを作成し、すべての情報を入力します。
  3. ベティがデータ入力をしている間、別の管理者であるジョージ。アシスタントは、自分のPCで学生管理プログラムを起動し、GinaVerdeのレコードの作成を開始します。
  4. ジョージはより速いタイピストなので、ベティと同時にフィニッシュします。両方が同時に「保存」ボタンを押します。
  5. Bettyのプログラムはデータベースサーバーに接続し、保存するレコードを渡します。
  6. 同時に、ジョージのプログラムは、保存する他のレコードを引き渡します。
  7. データベースサーバーは両方のレコードをキューに入れ、一度に1つずつ保存して、次に使用可能な番号を割り当てます。
  8. これで、Bob Smith(Bettyの学生)はID 5013を取得し、Gina Verde(Georgeの学生)はID5014を取得します。

このソリューションでは、重複の問題はありません。データベースサーバーごとにこれを行うコードは、製造元とユーザーの両方によって、長年にわたって繰り返しテストされてきました。世界中の何百万ものアプリケーションがこれに依存しており、毎日ストレステストを続けています。誰もが彼らの自家製の解決策について同じことを言うことができますか?

データベースではなくソフトウェアで識別子を作成するための十分にテストされた方法が少なくとも1つあります:uuids(Universally Unique Identifiers)。ただし、uuidはxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxの形式を取ります。ここで、「x」は16進数(0-9およびa-f)を表します。これを請求書番号、学生ID、または一般に公開されているその他の識別子に使用しますか?おそらくそうではありません。

要約すると、競合状態は、2つのプログラム、またはプログラムの2つの独立した部分が、ある情報へのアクセスまたはリソースへのアクセスを同時に試みたときに発生し、計算が正しくない、識別子が重複している、アクセスが競合しているなどのエラーが発生しますリソースに。ここで示したよりも多くの種類の競合状態があり、それらはソフトウェアとハ​​ードウェアの他の多くの領域に影響を与えます。

3
Steam