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

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

ただいまの
回答率

90.52%

  • PHP

    20307questions

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

  • MySQL

    5844questions

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

LOCK TABLESの記述をすると、エラーがでます

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 764

mi_

score 50

たびたびお世話になっています。

身内の拠点間のやりとりで、ショッピングカートの機能を使って、商品のやりとりを行おうとしています。

この機能の最後と思われる、販売と、販売の詳細にデータを書きこむ部分で長い間つまづいています。

一番下のお目汚しのコードで、

下の方のロックする部分と、その下のロックを解除する部分をコメントアウトすると、販売テーブル、販売の詳細のテーブルに正常にデータが書き込まれるのですが、下の方のロックと、ロックを解除する部分を入れると、

SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll(). Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.

このエラーが出てしまいます。

ネットで自分で調べれる範囲では、解決しませんでした。

もともとは、「気づけばプロ並みPHPショッピングカート作りにチャレンジ」という本の中のコードを参考に拡張?して作っています。
この書籍のコードはすべて一度作り終えていまして、この書籍のコードでは実行してもエラーはでません。エラーがでない書籍のコードと、今回のコードの違いを長い間探していますが、LOCKテーブルのあたりの記述や、構造はほとんど同じと思います。
$rec=$stmt->fetch(PDO::FETCH_ASSOC);
などの記述も同じです。

ただし、書籍では
$dsn='mysql:dbname=shop;host=localhost';
$user='root';
$password='';
$dbh=new PDO($dsn,$user,$password);
$dbh->query('SET NAMES utf8');
のような記載があったので、そもそも大きく違うのかもしれません。

とりとめもないですが、何かヒントをいただければと思います。
よろしくお願いします。

(実際には、このテーブルの書き込み操作が終われば、最後にメールを送る部分のコードがあるのですが、検証の為削除しています。)

ここと

//テーブルをロック
$sql='LOCK TABLES sales WRITE ,sales_p WRITE,pro WRITE';
$stmt=$dbh->prepare($sql);
$stmt->execute();

ここをコメントアウトしていない時は上記の長文のエラーになります。

//テーブルのロックを解除    
$sql='UNLOCK TABLES';
$stmt=$dbh->prepare($sql);
$stmt->execute();
<?php

require_once( __DIR__.'/../common/h.php' );

try
{

$post=h($_POST)

//$sales_nameは注文した人の名前を入力しています。
$sales_name=$post['sales_name'];
//注文時点の消費税を念の為書き込んでいます。消費税のテーブルから期間で取得しています。
$sales_rateb=$post['sales_rateb']; 

$cart=$_SESSION['cart'];    

$men_id=$_SESSION['men_id'];
$men_no=$_SESSION['men_no'];
$men_name_disp=$_SESSION['men_name_disp'];
$men_grant=$_SESSION['men_grant'];
$men_key=$_SESSION['men_key'];

//サブキーがある場合。FCオーナーや、エリアマネージャーでログインしている場合
//サブキーをメインキーに代入
if(isset($_SESSION['subkey'])){
    $men_key=$_SESSION['subkey'];
}

//店舗アカウントで注文する場合と、店舗を統括している人が店舗画面から注文する場合を想定しています。    
if(isset($_SESSION['sub_no'])){
    $men_no=$_SESSION['sub_no'];
}

if(isset($_SESSION['sub_name_disp'])){
    $men_name_disp=$_SESSION['sub_name_disp'];
}

require_once( __DIR__.'/../common/database.php' ); 
$dbh=new PDO($dsn,$dbUser,$dbPass);
$dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

//men_noに一致する店舗の情報を取り出し    
$sql='SELECT * FROM men WHERE men_no=:men_no';
$stmt=$dbh->prepare($sql);
$stmt->bindValue(':men_no',$men_no,PDO::PARAM_STR);
$stmt->execute();    


$rec=$stmt->fetch(PDO::FETCH_ASSOC);

print $sales_name.'様<br />';
print 'ご注文ありがとうございました。<br />';

if(isset($rec['men_email1'])){
    print $rec['men_email1'];
    $men_email1=$rec['men_email1'];
}
if(isset($rec['men_email2'])){
    print $rec['men_email2'];
    $men_email2=$rec['men_email2'];
}
if(isset($rec['men_email3'])){
    print $rec['men_email3'];
    $men_email3=$rec['men_email3'];    
}

print 'に、下記のメールを送りましたのでご確認ください。<br />';
print '商品は以下の住所に発送させていただきます。<br><br />';
print $rec['men_postal1'].'-'.$rec['men_postal2'].'<br />';
print $rec['men_address'].'<br />';
print $rec['men_ate'].'<br />';    


$honbun='';
$honbun.=$sales_name."様\n\nこのたびはご注文ありがとうございました。\n";
$honbun.="\n\n";
$honbun.="ご注文商品\n";
$honbun.="-------------\n";

$cart=$_SESSION['cart'];
$kazu=$_SESSION['kazu'];    
$sales_tax=$_SESSION['kazu'];    
$max=count($cart);


for($i=0;$i<$max;$i++)
{
    //セッションの中身に一致する商品情報を取り出して、本文に追加。ループ。
    $sql='SELECT * FROM pro WHERE pro_no=:pro_no';
    $stmt=$dbh->prepare($sql);
    $stmt->bindValue(':pro_no',$cart[$i],PDO::PARAM_STR);
    $stmt->execute();       

    $recb=$stmt->fetch(PDO::FETCH_ASSOC);

    $pro_name =$recb['pro_name'];
    $pro_price =$recb['pro_price'];
    $pro_tax =$recb['pro_tax'];

    $kakaku[]=$pro_price;
    $suryo=$kazu[$i];
    $shokei=$pro_price*$suryo;

    $honbun.=$pro_name.' ';
    $honbun.=$pro_price.'円×';
    $honbun.=$suryo.'個=';
    $honbun.=$shokei."円\n";
}

//確認用 メールじゃないので改行はされない。
//print $honbun;

//テーブルをロック
$sql='LOCK TABLES sales WRITE ,sales_p WRITE,pro WRITE';
$stmt=$dbh->prepare($sql);
$stmt->execute();

//注文テーブルに、注文店舗のキー、    
$sql='INSERT INTO sales(sales_men_key,sales_name)VALUES(:sales_men_key,:sales_name)';    
$stmt=$dbh->prepare($sql);
$stmt->bindValue(':sales_men_key',$men_key,PDO::PARAM_STR);
$stmt->bindValue(':sales_name',$sales_name,PDO::PARAM_STR);
$stmt->execute();

//上の注文テーブルで書き込まれたキーを取り出す。    
$sql='SELECT LAST_INSERT_ID()';
$stmt=$dbh->prepare($sql);
$stmt->execute();
$rec=$stmt->fetch(PDO::FETCH_ASSOC);
$lastcode=$rec['LAST_INSERT_ID()'];

for($i=0;$i<$max;$i++)
{

    //課税かどうかを取り出す
    $sql="SELECT * FROM pro WHERE pro_no =:pro_no";
    $stmt=$dbh->prepare($sql);
    $stmt->bindValue(':pro_no',$cart[$i],PDO::PARAM_STR);    
    $stmt->execute(); 

    $rec=$stmt->fetch(PDO::FETCH_ASSOC);
    if($rec==false)
    {
        break;
    }
    $pro_tax = $rec['pro_tax'];    

  //販売詳細をデータベースに書き込み
    $sql='INSERT INTO sales_p(sales_code,sales_pro_no,sales_tax,sales_rate,sales_price,sales_quan) VALUES (:sales_code,:sales_pro_no,:sales_tax,:sales_rate,:sales_price,:sales_quan)';
    $stmt=$dbh->prepare($sql);
    $stmt->bindValue(':sales_code',$lastcode,PDO::PARAM_STR);    
    $stmt->bindValue(':sales_pro_no',$cart[$i],PDO::PARAM_STR);    
    $stmt->bindValue(':sales_tax',$pro_tax,PDO::PARAM_STR);
    $stmt->bindValue(':sales_rate',$sales_rateb,PDO::PARAM_STR);    
    $stmt->bindValue(':sales_price',$kakaku[$i],PDO::PARAM_STR);    
    $stmt->bindValue(':sales_quan',$kazu[$i],PDO::PARAM_STR);    

    $stmt->execute();

}


//テーブルのロックを解除    
$sql='UNLOCK TABLES';
$stmt=$dbh->prepare($sql);
$stmt->execute();

$dbh=null;

}
catch(Exception $e)
{
    print 'ただいま障害により大変ご迷惑をおかけしております。';
    print $e->getMessage();
    exit();
}

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

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

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

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

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

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

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

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

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

LOCK TABLES 構文は、MySQL のプリペアド・ステートメントとしてサポートされていないためです。

mysql> PREPARE stmt1 FROM 'LOCK TABLES hoge WRITE';
ERROR 1295 (HY000): This command is not supported in the prepared statement protocol yet

以下のページの
"準備済みステートメント内で許可される SQL 構文"
という項に LOCK TABLES 構文が記載されていないことからも、それが分かります。
https://dev.mysql.com/doc/refman/5.6/ja/sql-syntax-prepared-statements.html


で、どうすれば良いかと言うと、
salessales_pproテーブルがいずれも InnoDBストレージエンジン(※)であるならば、
maiko0318様の回答にある通り LOCK TABLES の代わりにトランザクションを使用するのが良いでしょう。
※ 私の知る限り、トランサクションをサポートしているのは InnoDB のみです。
https://dev.mysql.com/doc/refman/5.6/ja/innodb-introduction.html
https://dev.mysql.com/doc/refman/5.6/ja/storage-engines.html

//テーブルをロック
// $sql='LOCK TABLES sales WRITE ,sales_p WRITE,pro WRITE';
// $stmt=$dbh->prepare($sql);
// $stmt->execute();
$dbh->beginTransaction();

(中略)

//テーブルのロックを解除    
// $sql='UNLOCK TABLES';
// $stmt=$dbh->prepare($sql);
// $stmt->execute();
$dbh->commit();

$dbh=null;

}
catch(Exception $e)
{
    $dbh->rollback(); // 追加
    print 'ただいま障害により大変ご迷惑をおかけしております。';
    print $e->getMessage();
    exit();
}


LOCK TABLES には

  • 取得・更新対象のレコードだけでなく、テーブル全体をアクセス不可能にしてしまう。
  • 処理の途中にエラーが発生した際に、ロールバックできない

といったデメリットがあります。

そのため、何らかの理由により InnoDB 以外のストレージエンジンを使用していたり、
「メンテナンスのためにテーブル全体をロックしたい」
といった特別な動機がない限り、使用する必要はありません。


最後に、

この書籍のコードはすべて一度作り終えていまして

これは素晴らしいことです。

「プログラミングを習得したい」
という情熱が伺えます。

しかしながら、質問欄にご提示のコードや以下の徳丸氏のブログを読む限り、
http://hemipteron26.rssing.com/chan-13974855/all_p5.html
該当の書籍は
「とにかく1つ、動くアプリケーションを作れるようになる」
ことに主眼を置いており、セキュリティをはじめ、いわゆる「品質の高いコード」の書き方については目をつぶっている部分が多々あるように見受けます。

この本だけで満足せず、たゆまず勉強を続けられることをお勧めします。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 10:22

    ご回答ありがとうございます!

    そもそもサポートされていないとは、まったく思いもしませんでした。

    今日はパソコンに触れないので、明日さっそくとりかかりたいと思います。

    理解不足の点が多いため、ご提示いただいたリンク先や、言葉について勉強したいと思います。

    ありがたいお言葉にも大変励まされます。モチベーション高く勉強していきたいと思います。

    キャンセル

  • 2017/02/14 11:08

    ご提示いただいたブログのリンク先から、書籍のセキュリティ面での修正点などの副読本がダウンロードできました。
    このようなきっかけを与えていただき、大変ありがたく思います。

    キャンセル

  • 2017/02/14 11:39

    b

    キャンセル

0

そもそもロックテーブルの意味をご存知でしょうか?
他者によるテーブル全体を読み書き不能にするコードです。

誰かがアクセス中なら待ちますけど、時間制限でエラーで返ってきてしまいます。
そもそもロックしなくともinsertだけなら問題ないと思いますがね。

必要なのはロックではなくてトランザクション処理の方かと思いますね。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/14 10:07

    ご回答ありがとうございます!

    販売詳細のテーブルに書き込む内容に、販売テーブルの番号が必要だったので、書籍のようにロックするのがベストと思っていました。

    タイミング的にかぶることはかなりなさそうなのでロックしないほうがいい気がしてきましたが、ロックする場合はどのようなことを併用した方がいいか気になります。

    時間制限がどの程度か、意識していませんでしたが、ロックの仕組みだけでは、かぶったときに即座にエラーになりそうなことでしょうか?

    キャンセル

  • 2017/02/14 10:15

    KiyoshiMotokiさんの回答で、必要なのはロックではなくトランザクション処理というそのものズバリのアドバイスの意味がわかりました。
    蛇足な質問をして申し訳ないです。

    キャンセル

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

  • ただいまの回答率 90.52%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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

  • PHP

    20307questions

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

  • MySQL

    5844questions

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