PHP上でMDB2を使用してMySQLにアクセスしているとき、あるタイミングでだけ、通らなくなるクエリ(プリペアドステートメント)が出てきて原因がわからず、ほぼ半日無駄にしてしまった。
■追記
MDB2: 2.5.0b5 (beta)
MDB2_Driver_mysql: 1.5.0b4 (beta)
を入れたら、あっさりと不具合が解消されました、くそう……。
でもWeb版PEARからだとアップデートできなかったので、無理やり上書きなんだよな、ちとそれが不安…。
結論としては、
- 処理の途中で、MDB2のコネクションを張りなおすと、特定のクエリ(プリペアドステートメント)が通らなくなる。
- 具体的には、$i:=0 のようなユーザ変数を含んだプリペアドステートメントを使うと発生する。
一見、「:=」とプレースホルダーを混同しているかのようにも思えるが、それにしては、一回目の接続時には問題なく通るのが解せない…。
→【追記】プリペアドステートメントを使わずに&$mdb2->query($query);で直接クエリ発行した場合は何回目でも正常に動作するので、やはりプリペアドステートメントの解釈部分の不具合のようだ。 - 対処方法は…とにかく一通りの処理が終わるまで、MDB2のコネクションを維持しておくしかない?
ということ、らしい。うーむ。
ちなみにバージョンは、
MDB2: 2.4.1 (stable)
MDB2_Driver_mysql: 1.4.1 (stable)
MySQLサーバ:5.0.51a-log - MySQL Community Server (GPL)
MDB2を最新にしてみたほうがいいんだろうか……(stable)じゃなくて、2.5.0b5 (beta)なのが気になるのだけれども。
再現用PHPソースコード
id, name というカラムを持つ company というテーブルに対してのSQLクエリを発行するもの。クエリ自体には意味なし。
<?php require_once('MDB2.php'); header('Content-Type: text/plain; charset=utf-8'); $db_dsn = array( // DSN: Data Source Name 'phptype' => 'mysql', 'database' => '*your-database*', 'hostspec' => '*your-hostspec*', 'username' => '*your-username*', 'password' => '*your-password*', 'charset' => 'utf8', 'new_link' => true, ); $db_options = array( 'portability' => MDB2_PORTABILITY_ALL ^ MDB2_PORTABILITY_EMPTY_TO_NULL, ); function is_error(&$dbobj, $message='') { if (!PEAR::isError($dbobj)) return false; echo sprintf("%s\n", $message); echo sprintf("Standard Message: %s\n", $dbobj->getMessage()); echo sprintf("Standard Code: %s\n", $dbobj->getCode()); return true; } function &mdb2_connect($db_dsn, $db_options) { for (;;) { $mdb2 = &MDB2::connect($db_dsn, $db_options); if (is_error($mdb2, 'Error in MDB2::connect()')) { $mdb2 = null; break; } $mdb2->query("SET NAMES 'utf-8'"); $mdb2->loadModule('Extended'); $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC); break; } return $mdb2; } function test($message='', $query) { global $db_dsn, $db_options; $in_val_dict = array('company_id' => 8); $in_val_type_dict = array('company_id' => 'integer'); $out_val_type_list = array('integer', 'integer', 'text'); echo sprintf("■ %s: \"%s\"\n", $message, $query); for (;;) { $mdb2 = &mdb2_connect($db_dsn, $db_options); if (!$mdb2) break; $db_dth = &$mdb2->prepare( $query , $in_val_type_dict , $out_val_type_list ); $db_res = &$db_dth->execute($in_val_dict); $db_dth->free(); if (is_error($db_res, 'Error in $db_dth->execute()')) { break; } var_dump($db_res->fetchAll()); break; } if ($mdb2) { $mdb2->disconnect(); $mdb2->free(); unset($mdb2); } echo "=====================================================================\n\n"; } $query_A = "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3"; $query_B = "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id"; test('1回目', $query_A); test('2回目', $query_B); test('3回目', $query_A); test('4回目', $query_B); ?>
実行結果
1回目は問題なく通ったクエリが、3回目にエラーになっている。
■ 1回目: "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3" array(1) { [0]=> array(3) { ["row_num"]=> int(1) ["id"]=> int(8) ["name"]=> string(15) "ダミー企業" } } ===================================================================== ■ 2回目: "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id" array(1) { [0]=> array(3) { ["row_num"]=> int(1) ["id"]=> int(8) ["name"]=> string(15) "ダミー企業" } } ===================================================================== ■ 3回目: "SELECT row_num, id, name FROM (SELECT *, @i:=@i+1 AS row_num FROM (SELECT id, name FROM company WHERE id = :company_id) AS t1, (SELECT @i:=0) AS t2) AS t3" Error in $db_dth->execute() Standard Message: MDB2 Error: unknown error Standard Code: -1 ===================================================================== ■ 4回目: "SELECT 1 AS row_num, id, name FROM company WHERE id = :company_id" array(1) { [0]=> array(3) { ["row_num"]=> int(1) ["id"]=> int(8) ["name"]=> string(15) "ダミー企業" } } =====================================================================