スクリプトのprepareとサーバのprepare
PHPのPDOを使いながらクエリーログを見ていたら、
見慣れないものが目に入ってきました。
CODE:
-
391 Connect admin@localhost on pdo_test
-
391 Prepare [1] INSERT INTO users SET name = ?
-
391 Execute [1] INSERT INTO users SET name = '9f732d76d9f2f0b4e62c8091c99a2334'
-
391 Quit
Prepareという行なのですが、PerlのDBIを使っていた分には見たことがありませんでした。
そこで以下を参照して、
MySQL 関数 (PDO_MYSQL)
CODE:
-
$pdo = new PDO('mysql:host=localhost;dbname=pdo_test;charset=utf-8;unix_socket=/tmp/mysql.sock', "username", "password");
-
$pdo->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, true);
上のようにPDOの設定をして同じSQLを実行すると
CODE:
-
392 Connect admin@localhost on pdo_test
-
392 Query INSERT INTO users SET name = 'f071c916f507876b8f67735faa0e015f'
-
392 Quit
いつものようなログが取れましたとさ。
ここで認識できるのは、スクリプト側でのprepareとMySQLサーバサイドでのprepareが別物だということ。恥ずかしながらそうとも知らずに今までPerlのDBIでprepareを使っておりました。
(prepareを使う理由は、プレースホルダーを使って外部入力を安全にSQLに挿入するためです。)
ベンチマークしてみる
で、以下のようなSQLを用意して、
init.sql
CODE:
-
USE pdo_test;
-
-
DROP TABLE IF EXISTS users;
-
CREATE TABLE users (
-
id INT(16) PRIMARY KEY AUTO_INCREMENT,
-
name VARCHAR(32) UNIQUE NOT NULL
-
);
以下のような、スクリプトを用意します。
pdo_test.php
CODE:
-
<?php
-
$pdo = new PDO('mysql:host=localhost;dbname=pdo_test;charset=utf-8;unix_socket=/tmp/mysql.sock', "username", "password");
-
//$pdo->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, true);
-
-
-
-
//foreach ( range(1, 100000) as $i) {
-
insertByPrepare($pdo);
-
//insertByQuery($pdo);
-
//}
-
-
//insertByPrepareLoop($pdo);
-
-
function insertByPrepare ($pdo) {
-
$sth = $pdo->prepare("INSERT INTO users SET name = ?");
-
$sth->execute( array(hash('md5', microtime())) );
-
}
-
-
function insertByQuery ($pdo) {
-
$pdo->query("INSERT INTO users SET name = '" . hash('md5', microtime()) . "'");
-
}
-
-
function insertByPrepareLoop ($pdo) {
-
$sth = $pdo->prepare("INSERT INTO users SET name = ?");
-
foreach ( range(1, 100000) as $i) {
-
$sth->execute( array(hash('md5', microtime())) );
-
}
-
}
-
?>
ベンチマークには疎いので、適当で手動なんですが、
このスクリプトのコメントアウトのあたりを外したりつけたりして、
コマンドラインから以下を繰り返しです。
CODE:
-
# mysql <init.sql
-
# time php pdo_test.php
結果
当たり前ですが、prepareしたステートメントハンドラをループで回すという、
prepareの意味通りに使うパターンが一番早くなりました。
insertByQuery [PDO::MYSQL_ATTR_DIRECT_QUERY, true]
php pdo_test.php 10.14s user 4.24s system 32% cpu 44.907 total
insertByQuery
php pdo_test.php 13.83s user 8.65s system 33% cpu 1:07.68 total
insertByPrepareLoop [PDO::MYSQL_ATTR_DIRECT_QUERY, true]
php pdo_test.php 8.48s user 4.03s system 30% cpu 41.258 total
insertByPrepareLoop
php pdo_test.php 8.14s user 4.40s system 30% cpu 40.570 total
insertByPrepare [PDO::MYSQL_ATTR_DIRECT_QUERY, true]
php pdo_test.php 12.84s user 4.34s system 34% cpu 49.931 total
insertByPrepare
php pdo_test.php 18.05s user 10.15s system 36% cpu 1:17.68 total
ループを回さないところでprepareをすると少し遅いようです。
基本的に「PDO::MYSQL_ATTR_DIRECT_QUERY, true」にしておいて、サーバ側でprepareしないようにしておくと良さそうですね。
#余談
PHP5.1.xだと、LIMITなどで
CODE:
-
$sth = $pdo->prepare('SELECT * FROM users LIMIT ?');
とやるとステートメントハンドラ自体が帰ってきません。
5.2.xでは解決されているバグのようです。