38

一意として定義されている電子メールアドレスのフィールドを持つMySQLテーブルがあります。この例では、私のフォームが行うのは、ユーザーが自分の電子メールアドレスをテーブルに挿入できるようにすることだけだとしましょう。

電子メールフィールドは一意であるため、同じ電子メールを2回入力しようとすると、クエリは失敗するはずです。2つのシナリオ間のトレードオフについて知りたいです。

1)挿入を実行する前に、クイックSELECTステートメントを実行します。INSERTselectが結果を返す場合は、ユーザーに通知し、ステートメントを実行しないでください。

2)INSERTステートメントを実行し、重複エントリエラーがないか確認します

// snippet uses PDO
if (!$prep->execute($values))
{
    $err = $prep->errorInfo();
    if (isset($err[1]))
    {
        // 1062 - Duplicate entry
        if ($err[1] == 1062)
            echo 'This email already exists.';
    }
}

また、通常の使用を想定してください。つまり、重複するエントリは最小限に抑える必要があります。したがって、最初のシナリオでは、挿入ごとに追加のクエリを実行するオーバーヘッドがあるのは明らかですが、 2番目のシナリオではエラー処理に依存しています。

また、コーディングスタイルについての考えを聞きたいです。私の心は言う'防御的なプログラマーになりなさい!挿入する前にデータを確認してください!」私の脳は「うーん、MySQLにデータのチェックを任せたほうがいいかもしれない」と言っています。

編集-これは「これをどのように行うか」の質問ではなく、「なぜこれを特定の方法で行う必要があるのか​​」という質問であることに注意してください。私が含めた小さなコードスニペットは機能しますが、私が興味を持っているのは、問題を解決するための最良の方法です。

4

5 に答える 5

63

これは、trycatchブロックを使用して実行できます。

try {
   $prep->execute($values);
   // do other things if successfully inserted
} catch (PDOException $e) {
   if ($e->errorInfo[1] == 1062) {
      // duplicate entry, do something else
   } else {
      // an error other than duplicate entry occurred
   }
}

「INSERTIGNORE」や「INSERT...ON DUPLICATE KEY UPDATE」などの代替案を調べることもできますが、これらはMySQL固有であり、PDOを使用することの移植性に反すると思います。 。

編集:あなたの質問にもっと正式に答えるために、私にとって、ソリューション#1(防御プログラマー)を完全に使用すると、そもそも一意性制約のポイントが効果的に排除されます。したがって、MySQLにデータチェックを任せるというあなたの考えに同意します。

于 2012-05-27T15:41:35.313 に答える
11

INSERT+ステータスを確認することをお勧めします。SELECT+を使用すると、別のスレッドでとのINSERT間に同じ値を挿入できます。つまり、これら2つのステートメントをテーブルロックでラップする必要があります。SELECTINSERT

コーディングの防御が多すぎるという側面で誤りを犯しがちです。Pythonには、「許可を求めるよりも許しを求める方が簡単である」という言葉があり、この哲学は実際にはPython固有のものではありません。

于 2012-05-27T16:10:55.557 に答える
4

AUTO_INCREMENT列があり、InnoDBを使用している場合、新しい行が追加されていなくても、INSERTが失敗すると「次のauto-id」値がインクリメントされることに注意してください。たとえば、 InnoDBでのINSERT ... ONDUPLICATEKEYおよびAUTO_INCREMENTの処理に関するドキュメントを参照してください。これにより、AUTO_INCREMENT IDにギャップが生じます。これは、IDが不足する可能性があると思われる場合に懸念される可能性があります。

したがって、既存の行を挿入しようとするのが一般的であり、AUTO_INCREMENT IDのギャップを可能な限り回避したい場合は、プリエンプティブチェックと例外処理の両方を実行できます。

$already_exists = false;
$stmt = $pdo->prepare("SELECT id FROM emails WHERE email = :email");
$stmt->execute(array(':email' => $email));
if ($stmt->rowCount() > 0) {
    $already_exists = true;
}
else {
    try {
        $stmt = $pdo->prepare("INSERT INTO emails (email) VALUES (:email)");
        $stmt->execute(array(':email' => $email));
    } catch (PDOException $e) {
        if ($e->errorInfo[1] == 1062) {
            $already_exists = true;
        } else {
            throw $e;
        }
    }
}

最初のクエリは、電子メールがすでに存在することが確実にわかっている場合に、電子メールの挿入を試みないようにします。2番目のクエリは、まだ存在していないように見える場合に電子メールを挿入しようとします。競合状態(上記のスニペットを並行して実行している複数のクライアントまたはスレッド)の場合でも重複が発生する可能性があるため、2番目のクエリで例外をチェックする必要があります。

このアプローチにより、コードが堅牢になりますが、競合状態のまれなケースを除いて、AUTO_INCREMENTIDにギャップが生じることはありません。また、新しい電子メールを挿入するよりも既存の電子メールを挿入する方が一般的である場合は、これが最速のアプローチです。既存の電子メールを挿入しようとすることがまれであり、AUTO_INCREMENT IDのギャップを気にしない場合は、プリエンプティブチェックは必要ありません。

于 2019-02-27T13:44:08.217 に答える
1

私にとってうまくいく簡単な方法は、重複するステータスコードの配列に対してエラーコードをチェックすることです。そうすれば、PDOが何を返すかを心配する必要はありません。

$MYSQL_DUPLICATE_CODES=array(1062,23000);
try {
   $prep->execute($values);
   // do other things if successfully inserted
} catch (PDOException $e) {
   if (in_array($e->getCode(),$MYSQL_DUPLICATE_CODES)) {
      // duplicate entry, do something else
   } else {
      // an error other than duplicate entry occurred
   }
}

ありがとう!:-)

于 2019-04-11T11:25:17.647 に答える
0

コードで確認するのではなく、次のようなcatchブロックを使用して、主キーの重複エントリをキャプチャし、そのエラーコードを使用しました。1062 Duplicate entry

catch (PDOException $e) {

  if(str_contains($e, '1062 Duplicate entry')) {
       header("Location: login.php");
  }
die("Error inserting user details into database: " .  $e->getMessage());

}
于 2021-10-15T11:48:11.570 に答える