PHP5について質問です。



DB情報(table名:site)
id
title
url

このようなDBがあるとします。
このDBにユーザーが新しいサイト情報を入れていくページをPHP作っています。


すでに存在するURLの場合は登録を受け付けないようにしたいのですが、その場合はどのようなスクリプトを書けばいいでしょうか。


入力されたURLを$urlとします。
$sql = "SELECT * FROM site WHERE url = '$url'";
これでもし一件もヒットしなければその後INSERTで登録・・という流れでしょうか。


INSERTで登録する辺りは書けますので、その前の部分だけで構いません。
何か上手い書き方は無いでしょうか?


皆様よろしくお願いいたします。

回答の条件
  • 1人5回まで
  • 登録:2007/10/12 15:26:49
  • 終了:2007/10/14 22:56:10

ベストアンサー

id:ton-boo No.3

ton-boo回答回数55ベストアンサー獲得回数52007/10/13 16:42:36

ポイント50pt

既に他の回答にあるように、カラムにユニーク制約をつけてしまうのがこの場合最適のように私も思います。

その上で、何らかの事情で今からそういう変更をするのができないこともあるかもしれないので、「SELECT文とINSERT文を実行する間に、他のユーザにより先にINSERTされてしまうケースへの配慮が必要」という件について補足します。

トランザクションA トランザクションB
1 SELECT文でURLが存在しないことを確認⇒OK
2 SELECT文でURLが存在しないことを確認⇒OK(まだAによる行インサートが行われていないから)
3 列インサート
4 列インサート⇒チェックしたはずなのに、同じURLの行ができてしまう

※ 実際には1と2、3と4はどちらが先でも同じ話になります。

こういう事態を回避するために配慮が必要なわけですが、具体的には、トランザクション実行中には他のトランザクションがSELECTできないようにする必要があるわけです。

そのためには、RDBMSの種類によりますが、テーブルの排他ロックを取得したり、トランザクションの隔離レベルをシリアライザブルにしたり、という手順が必要となります。

いずれにしても、PHPの話というよりもRDBMSの話なので、これ以上の詳細については「どんなDBを使っているのか」という情報があったほうがよいかもしれません。


なお、テーブルロックは本当に必要な間だけ取得するようにしないと(シリアライザブルなトランザクションブロックはできるだけ短くしないと)、アクセス集中時に性能ボトルネックになりやすいことにも注意してください。

id:tokyosmash

回答ありがとうございます。表のおかげでトランザクション処理の必要性が一目瞭然でした。


chuken_kenkouさんへのコメント欄で書いたのですが、今回自分が作るシステムに必要かどうかわからないのですが、勉強だと思って少し調べてみます。


丁寧にありがとうございました。勉強になりました!

2007/10/14 22:53:21

その他の回答(2件)

id:chuken_kenkou No.1

chuken_kenkou回答回数722ベストアンサー獲得回数542007/10/12 16:00:24

ポイント50pt

SELECT文で存在するかどうか確認後、INSERTする方法もありますが、SELECT文とINSERT文を実行する間に、他のユーザにより先にINSERTされてしまうケースへの配慮が必要です。

重複チェックをアプリケーション側で行うのでなく、RDBMS側に任せてしまった方が、アプリケーション側での操作は楽になると思います。

RDBMSが何なのか分かりませんが、プライマリ・キー以外にユニーク・インデクスを定義できると思います。

主要なRDBMSでは、以下のようなSQLで定義します。

CREATE UNIQUE INDEX インデクス名 ON site(url) 

あとは、RDBMSで「重複エラー時に、どういうコードやメッセージが返却されるか?」を調べ、重複エラー時の処理をアプリケーション側で記述します。

id:tokyosmash

回答ありがとうございます。以前よくお世話になっていたものです。DBはMySQL5を使っています。


いわゆる「トランザクション」と言うものなのでしょうか。金融系などクリティカルなシステムでは必須であるとか、そのような話を耳にした事があります。


DB側で重複チェックをするという発想は今までありませんでした。そのような機能があるのも知りませんでした。

質問文では「ユーザーが」と書いてありますが、実際には自分でURLを1件1件流し込んでいくようなシステムになります。つまりton-booさんの回答にあるようなAとBという二つのトランザクションが被ってしまう事はありえないはずです。

一通りMySQLにおけるトランザクション処理について調べてみて、もし敷居が高そうならばKUROXさんの回答を参考に簡単な処理で済ませてしまおうと思ってます。


トランザクションについて勉強になりました。ありがとうございました。

2007/10/14 22:50:14
id:KUROX No.2

KUROX回答回数3542ベストアンサー獲得回数1402007/10/12 18:15:27

ポイント30pt

$sql = "SELECT count(*) as cnt FROM site WHERE url = '$url'";

面倒なら、count()を使ってレコード数を取得してください。

0件ならINSERTです。

id:tokyosmash

なるほどこれは簡単ですね・・。なぜ思いつかなかったのでしょうか。

トランザクションが必要なければこの方法でいこうと思います。回答ありがとうございました!

2007/10/14 22:54:10
id:ton-boo No.3

ton-boo回答回数55ベストアンサー獲得回数52007/10/13 16:42:36ここでベストアンサー

ポイント50pt

既に他の回答にあるように、カラムにユニーク制約をつけてしまうのがこの場合最適のように私も思います。

その上で、何らかの事情で今からそういう変更をするのができないこともあるかもしれないので、「SELECT文とINSERT文を実行する間に、他のユーザにより先にINSERTされてしまうケースへの配慮が必要」という件について補足します。

トランザクションA トランザクションB
1 SELECT文でURLが存在しないことを確認⇒OK
2 SELECT文でURLが存在しないことを確認⇒OK(まだAによる行インサートが行われていないから)
3 列インサート
4 列インサート⇒チェックしたはずなのに、同じURLの行ができてしまう

※ 実際には1と2、3と4はどちらが先でも同じ話になります。

こういう事態を回避するために配慮が必要なわけですが、具体的には、トランザクション実行中には他のトランザクションがSELECTできないようにする必要があるわけです。

そのためには、RDBMSの種類によりますが、テーブルの排他ロックを取得したり、トランザクションの隔離レベルをシリアライザブルにしたり、という手順が必要となります。

いずれにしても、PHPの話というよりもRDBMSの話なので、これ以上の詳細については「どんなDBを使っているのか」という情報があったほうがよいかもしれません。


なお、テーブルロックは本当に必要な間だけ取得するようにしないと(シリアライザブルなトランザクションブロックはできるだけ短くしないと)、アクセス集中時に性能ボトルネックになりやすいことにも注意してください。

id:tokyosmash

回答ありがとうございます。表のおかげでトランザクション処理の必要性が一目瞭然でした。


chuken_kenkouさんへのコメント欄で書いたのですが、今回自分が作るシステムに必要かどうかわからないのですが、勉強だと思って少し調べてみます。


丁寧にありがとうございました。勉強になりました!

2007/10/14 22:53:21

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません