php4とPostgreSQLでPreparedStatement

2005年08月29日(月) 12:59

hibernateがTorqueに比べて勝っている点は、唯一PreparedStatementを利用している点だ。
という職場でのやりとりから、rhacoのO/RはPreparedStatementを使用してみようと調査。

PreparedStatementを利用する利点は、主に二つ(あとは知らない)
  • SQL Injectionに対して強くなる
  • 同じ文のプロパティ違いSQLは劇的に速くなる(可能性がある)
php5ではphpの機能としてPreparedStatementが導入された模様ですが、php5を使う理由はどこにもない(だって、phpの利点は大抵のレンタルサーバで使えることでしょ?interfaceが欲しい?リフレクションが欲しい?php5は自分でインストールするから?=>他の言語を検討すれば?)

php4にはPreparedStatementの機能がないので、自分で実装するしかない。
PostgreSQLのSQL文でPREPAREという物があるので、これが使えそう。
PREPARE plan_name [ (datatype [, ...] ) ] AS statement
EXECUTE plan_name [ (parameter [, ...] ) ]
pg_execを二回投げて一つのSQL文が実行できるというもの。
次のような特徴があるため、名前の管理が必要になる。
  • 1セッションの間 PREPARE は有効。
  • ただし、plan_nameは1セッションの間に重複して宣言することは出来ない。
  • DEALLOCATEでplan_nameを解放することは出来るが、宣言していない物を解放しようとするとエラーとなる。
また、PREPAREでdatatypeを指定する必要があるので、JavaのPreparedStatementと同様に、ps->setInt(1, $value);のような型を指定するようなメソッドが無いとつらそう。
つまり、PreaparedStatementというクラスを作成して、SQL文とplan_nameと各引数となる値のhashオブジェクトを持たせることになる。
はたして、ここまでコストをかけて速くなるのだろうか?

簡単なテーブルにデータを1万行インサートしてみた所、案の定下記のようになった。
#planの生成にコストのかかるSQLに効果的なのはわかっるけど・・・
  • PreparedStatementを利用した物 => 28秒
  • sprintfしたSQLを投げまくった物 => 24秒
もう少し様々な条件でのテストと、PreparedStatementクラスのリファクタリングに励んでみよう。
以下、テストのガワ。

<?
require_once("config.php");
require_once("RhacoModel/RDBConnection.php");
require_once("test/UnitBench.php");

class Test4 extends UnitBench {
var $db;
var $sql;
var $name;
var $ps;

function benchPreparedStatement() {
$this->db = RDBConnection::getConnection();
$this->sql = "INSERT INTO pre_test (no, memo) values ($1, $2) ;";
$this->name = "insert_pre_test";
$this->ps = $this->db->createStatement($this->name, $this->sql);
for($i = 0;$i < 10000;$i++) {
$this->ps->clear(); //値のhashのみクリア
$this->ps->setInt(1, $i);
$this->ps->setText(2, sprintf("test(%s)", $i));
$this->ps->executeQuery(); //初回のみPREPARE文の組み立てと実行
}
}

function benchRawSql() {
$this->db = RDBConnection::getConnection();
for($i = 0;$i < 10000;$i++) {
$memo = sprintf("test(%s)", $i); //見やすいようにばらしてあります
$rawSql = sprintf("INSERT INTO pre_test (no, memo) values (%s, '%s') ;", $i, $memo);
$this->db->executeQuery($rawSql); //ただ実行するのみ!
}
}
}
$test = new Test4();
?>


 
ponybadge

Powered by

Feedbacks

Tweets

Tags

Calendar