1

次のような MySQL クエリを生成する PHP スクリプトがあります。

INSERT INTO `dailydb`.`probact_actionstaken`(`dispatcher`) VALUES ('kryan');

SET @actionTakenId = LAST_INSERT_ID();

INSERT INTO `dailydb`.`probact_actionstaken_types`(`actionTaken`,`type`)
VALUES
    (@actionTakenId, 2);

INSERT INTO `dailydb`.`probact_actionstaken_problems`(`actionTaken`,`problem`)
VALUES
    (@actionTakenId, 2);

INSERT INTO `dailydb`.`probact_actionstaken_actions`(`actionTaken`,`action`)
VALUES
    (@actionTakenId, 3);

INSERT INTO `dailydb`.`probact_actionstaken_text`(`actionTaken`,`input`,`value`)
VALUES
    (@actionTakenId, 3, '7576'),
    (@actionTakenId, 4, 'B61'),
    (@actionTakenId, 5, '4'),
    (@actionTakenId, 6, 'kryan'),
    (@actionTakenId, 7, 'test'),
    (@actionTakenId, 8, 'testing new debug');

SELECT `index`, `timestamp`
FROM `dailydb`.`probact_actionstaken`
WHERE `index`=@actionTakenId;

(このクエリを表示する目的で、:someId準備済みステートメントを使用しているため、すべての値は手動で検索して対応する値に置き換えた結果であることに注意してください)

( probact_actionstaken.indexは自動インクリメントの主キーであり、probact_actionstaken. のtimestampデフォルトはCURRENT_TIMESTAMP)

クエリの目的は、( に格納されているprobact_actionstaken.の自動値に基づいて) 大量の関連データを挿入し、自動生成されて使用できるようにすることです (スクリプトは、この出力を使用する AJAX 呼び出しで呼び出されます)。 )。index@actionTakenIdindextimestamp

このクエリは、ほとんどの部分で正しく実行されます。5 つのINSERTステートメントはすべて、データベースに正しい値を挿入します。私の問題はその最後のSELECT声明です。次の PHP コードを使用して、クエリの結果を調べてみました。

$statement = $dbh->prepare($query);
$statement->execute($params);

$output['results'] = array();
do
{
    $output['results'][] = array();
    while ( $result = $statement->fetch(PDO::FETCH_ASSOC) )
    {
        $output['results'][count($output['results'])-1][] = $result;
        if ( isset($result['index']) )
        {
            $output['index'] = intval($result['index']);
        }
        if ( isset($result['timestamp']) )
        {
            $output['timestamp'] = $result['timestamp'];
        }
    }
} while ( $statement->nextRowset() );

echo json_encode($output);

アイデアは、do...whileブロックを使用してINSERTステートメントからの結果を取得し、その最終SELECTステートメントの結果を取得して my に格納すること$outputです。

私のローカル マシン (PHP 5.4.4 を実行) では、これは完全に機能します。PHP 5.3.10 を搭載したサーバーでは$statement->nextRowset()毎回 false が返されるため、最初の行セットのみを取得します (これはINSERTステートメントからのものであるため空です)。実際の挿入は関係なく正しく機能することに注意してください。手動で入力した上記のクエリを phpMyAdmin の SQL 機能で実行すると、正しい結果 (挿入されたインデックスとタイムスタンプで構成されるテーブル) が返されます。

これらの問題について読むと、このような複数のステートメントは悪い考えであり、実際には真の準備済みステートメントでは機能しないようです。私はまだデフォルトのエミュレートされた準備済みステートメントを使用しています。$dbh->prepareそのため、との個別の呼び出しに切り替えることができました$statement->execute。これで問題は解決しますが、競合状態が心配です。サーバーは Nginx を使用しており、多くのユーザーが同時にエントリを入力する可能性があります。LAST_INSERT_ID()他のユーザーのエントリを返して、これらのフィールドを間違ったアクションに関連付けたくありません。

競合状態の問題を防ぐために、MySQL または PHP 内に何らかの理由がありますか? 私が取ることができるいくつかのステップ?または、複数の準備済みステートメントを使用する安全な方法はありますか? 後者の場合、PHP 3.5.10 で動作しますか?

4

1 に答える 1

1

LAST_INSERT_ID() が他のユーザーのエントリを返し、これらのフィールドを間違ったアクションに関連付けることは望ましくありません。

LAST_INSERT_ID() 関数は、別の MySQL セッションではなく、現在のセッションでのみ生成された最新の ID を返します。

http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-idを参照してください。

生成された ID は、接続ごとにサーバーに保持されます。これは、関数によって特定のクライアントに返される値が、そのクライアントによって AUTO_INCREMENT 列に影響を与える最新のステートメントに対して生成された最初の AUTO_INCREMENT 値であることを意味します。この値は、他のクライアントが独自の AUTO_INCREMENT 値を生成したとしても、影響を受けることはありません。この動作により、各クライアントは、他のクライアントのアクティビティを気にせず、ロックやトランザクションを必要とせずに、独自の ID を取得できるようになります。

競合状態が発生する可能性があるため、このようにスコープが制限されていない場合、この関数はほとんど役に立ちません。

PHP 5.3 と PHP 5.4 の間で複数結果のクエリを処理する際に見られる違いについての答えはわかりません。マルチクエリを避けることがベストプラクティスと考えられています。

また、本番環境にデプロイする開発者環境で、同じバージョンの PHP (およびすべてのソフトウェア) を使用することも賢明であると考えられています。

于 2013-08-26T20:50:10.547 に答える