質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
87.20%
MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

解決済

SELECT FOR UPDATE の使い方があっているか知りたいです

nikuatsu
nikuatsu

総合スコア160

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

1回答

-1評価

0クリップ

198閲覧

投稿2022/08/05 14:24

編集2022/08/09 11:28

前提

下記の テーブル構造 において、「fruits_name が重複しないときだけ INSERT」をしたいと考えています。

実現したいこと

この前提において、下記の SELECT FOR UPDATE を使ったコード であっているのか?間違っていればそれは何か?また懸念点があればどの部分か?などアドバイスを頂きたいです。

尚、これは 以前の質問 で「SELECT FOR UPDATE を使ってはどうか」と教えて頂いたもののそのソースコードはわからず、自分で書いたものになります。

発生している問題

特にネットでは「➀SELECT FOR UPDATE はデッドロックが発生しがち」や「②INSERT なのに SELECT FOR UPDATE を使う必要はない」などのツッコミを拝見するのですが、その詳細はわからず、上記の前提においてどうなのかも想定できません。

尚、上記の前提について①はまったくわからず、②は「SELECT FOR UPDATE を使うことでネクストキーロックがかかり重複回避できる様子なので便利っぽい」と考えていますが…どうなのでしょうか…

テーブル構造

SQL

-- フルーツテーブル CREATE TABLE my_fruits ( `ID` int AUTO_INCREMENT, `count_likes` int(100) DEFAULT 0, `fruits_name` varchar(1000) not null, # 1000文字なのでユニーク制限ができない PRIMARY KEY(`ID`)); INSERT INTO my_fruits (`count_likes`, `fruits_name`) VALUES (10, 'フルーツ1'), (56, 'フルーツ2'), (36, 'フルーツ3'); -- タグテーブル CREATE TABLE my_tags ( `ID` int AUTO_INCREMENT, `tag_kind_id` int default 1, `tag_name` varchar(100), PRIMARY KEY(`ID`), UNIQUE u_my_tags_01 (`tag_kind_id`, `tag_name`)); INSERT INTO my_tags (`tag_kind_id`, `tag_name`) VALUES (1, '赤'), (1, '青'), (1, '緑'), (1, '黄'), (1, '白'); -- リレーションテーブル CREATE TABLE my_tag_holders ( `fruits_ID` int, `tags_ID` int, PRIMARY KEY (`fruits_ID`, `tags_ID`), #「フルーツとタグの同じ組み合わせはない」という制限 INDEX idx_my_tag_holders_01 (`tags_ID`, `fruits_ID`), CONSTRAINT fk_my_tag_holders_01 FOREIGN KEY (`fruits_ID`) REFERENCES my_fruits(`ID`), CONSTRAINT fk_my_tag_holders_02 FOREIGN KEY (`tags_ID`) REFERENCES my_tags(`ID`) ); INSERT INTO my_tag_holders (`fruits_ID`, `tags_ID`) VALUES (1, 1),(1, 2), (2, 2),(2, 4),(2, 5), (3, 3);

SELECT FOR UPDATE を使ったコード

こちらがあっているか不安なコードです。

php

/* ●処理の流れ 1回目は $with_sleep = true、2回目は false にして 1回目の読み込みでsleepしている途中に2回目の読み込みを実行し $friuts_name が重複しないことを確認します。 ●なぜ重複が防止できるか? SELECT FOR UPDATE によって 既存のときはネクストキーロックがかかり 2回目の読み込みではINSERTされず重複が防止できている というつもりです。 */ // 実行 $with_sleep = true; // 1回目だけ sleep させて、その最中に2回目を実行する $fruits_name = 'フルーツ4'; $result = test_insert_on_select_for_update_ver_pdo( $fruits_name, $with_sleep ); var_dump($result); // 関数 function test_insert_on_select_for_update_ver_pdo( $fruits_name, $with_sleep ){ $result = ['status'=>'error']; try { // トランザクション $dbh = db_open(); $dbh->beginTransaction(); // 既存確認 $sql = " SELECT * FROM my_fruits WHERE fruits_name = :fruits_name FOR UPDATE ;"; $stmt = $dbh->prepare( $sql ); $stmt->bindValue(':fruits_name', $fruits_name); $flag = $stmt->execute(); if( $flag ) { // あれば既存IDを取得 $data = $stmt->fetch(); $result = [ 'status' => 'exists', 'fruits_id' => $data['ID'] ]; } else { throw new Exception("既存確認で失敗しました"); } // 1回目だけ sleep させて、その最中に2回目を実行する if ( $with_sleep ) sleep(40); // なければINSERT if ( $data === false ) { $sql = " INSERT INTO my_fruits ( fruits_name ) VALUES( :fruits_name ) ;"; $stmt = $dbh->prepare( $sql ); $stmt->bindValue(':fruits_name', $fruits_name); $flag = $stmt->execute(); if( $flag ) { $result = [ 'status' => 'inserted', 'fruits_id' => $dbh->lastInsertId() ]; } else { throw new Exception("INSERTで失敗しました"); } } $dbh->commit(); } catch (PDOException $e) { $result = [ 'status' => 'pdoerror', 'message' => $e->getMessage() ]; } catch (Exception $e) { $result = [ 'status' => 'error', 'message' => $e->getMessage() ]; $dbh->rollback(); } finally { $dbh = null; } return $result; }

ツールのバージョン

それぞれ次のバージョンです。

PHP 8.0
MySQL 5.7.2

宜しくお願い致します。

良い質問の評価を上げる

以下のような質問は評価を上げましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

まだ回答がついていません

会員登録して回答してみよう

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
87.20%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

PHP

PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。