新宿の会社の仕事で
PEAR::MDB2を使ってDBを更新するスクリプトを書いたら、思わぬ点ではまったので念のためメモ。
今回はDBがPostgreSQLだったのだが、prepare()→execute()を繰り返し使うスクリプトを動かしていると、稀にprepared statementの名前が衝突してスクリプトが異常終了してしまうという問題が発生。
そんでよくよく原因を調べてみると、MDB2のVer.2.41(最新のstable版)ではここが
$statement_name = sprintf($this->options['statement_format'], $this->phptype, md5(
time() + rand()));
$statement_name = strtolower($statement_name);
となってるのだが、ここの部分のMD5がたまに衝突してしまうために異常終了していた。普段はほとんど問題にならないのだが、今回のスクリプトでは1回の処理で数千回はprepare()を実行するので(なんでそんなにprepare()するんだ、という問題もあるのは理解してますが、そこの構造をいじるのはいろいろ難儀なので、今回はそこはスルー)、prepared statementが不要になったらきちんとDEALLOCATE(MDB2の場合はstatement objectでfree()を実行)してあげないとだめな模様。
ちなみにMDB2の最新のbeta(Ver.2.5.0b2)では、ここの部分は
$statement_name = sprintf($this->options['statement_format'], $this->phptype, $prep_statement_counter++ . sha1(microtime() + mt_rand()));
$statement_name = substr(strtolower($statement_name), 0, $this->options['max_identifiers_length']);
と変わっているのだが、実はフォーマットが「MDB2_Statement_pgsql_[番号]_[ハッシュ値]」という形になっていて、しかもハッシュ関数がsha1に変わっているので、[番号]の部分を除いた文字列の値が既に61文字もある。
そんで次の行でsubstrするのだが、この「max_identifiers_length」が63文字に設定されているので、[番号]の部分が3桁になると、末尾の文字列(ハッシュ値部分ですな)が尻切れトンボになってしまうという…。
[番号]の部分($prep_statement_counter)は関数の中でstaticな変数として宣言されているので、スクリプトが動き続ける限りこの変数はどんどん数が増えていくということは、やっぱりprepared statementの名前が衝突する可能性は(確率はだいぶ低くなったとはいえ)依然残っていると思われる。
というわけで、今回の教訓は「
prepare()でstatementを生成した場合、使用後はきちんとfree()しましょう」ということで。