PHPのPDOのprepareについて

2007年06月09日 written by fjkktkys

スクリプトのprepareとサーバのprepare

PHPのPDOを使いながらクエリーログを見ていたら、
見慣れないものが目に入ってきました。

CODE:
  1. 391 Connect     admin@localhost on pdo_test
  2. 391 Prepare     [1] INSERT INTO users SET name = ?
  3. 391 Execute     [1] INSERT INTO users SET name = '9f732d76d9f2f0b4e62c8091c99a2334'
  4. 391 Quit

Prepareという行なのですが、PerlのDBIを使っていた分には見たことがありませんでした。

そこで以下を参照して、
MySQL 関数 (PDO_MYSQL)

CODE:
  1. $pdo = new PDO('mysql:host=localhost;dbname=pdo_test;charset=utf-8;unix_socket=/tmp/mysql.sock', "username", "password");
  2. $pdo->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, true);

上のようにPDOの設定をして同じSQLを実行すると

CODE:
  1. 392 Connect     admin@localhost on pdo_test
  2. 392 Query       INSERT INTO users SET name = 'f071c916f507876b8f67735faa0e015f'
  3. 392 Quit

いつものようなログが取れましたとさ。

ここで認識できるのは、スクリプト側でのprepareとMySQLサーバサイドでのprepareが別物だということ。恥ずかしながらそうとも知らずに今までPerlのDBIでprepareを使っておりました。
(prepareを使う理由は、プレースホルダーを使って外部入力を安全にSQLに挿入するためです。)

ベンチマークしてみる

で、以下のようなSQLを用意して、
init.sql

CODE:
  1. USE pdo_test;
  2.  
  3. DROP TABLE IF EXISTS users;
  4. CREATE TABLE users (
  5.     id   INT(16) PRIMARY KEY AUTO_INCREMENT,
  6.     name VARCHAR(32) UNIQUE NOT NULL
  7. );

以下のような、スクリプトを用意します。
pdo_test.php

CODE:
  1. <?php
  2. $pdo = new PDO('mysql:host=localhost;dbname=pdo_test;charset=utf-8;unix_socket=/tmp/mysql.sock', "username", "password");
  3. //$pdo->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, true);
  4.  
  5.  
  6.  
  7. //foreach ( range(1, 100000) as $i) {
  8. insertByPrepare($pdo);
  9. //insertByQuery($pdo);
  10. //}
  11.  
  12. //insertByPrepareLoop($pdo);
  13.  
  14. function insertByPrepare ($pdo) {
  15.     $sth = $pdo->prepare("INSERT INTO users SET name = ?");
  16.     $sth->execute( array(hash('md5', microtime())) );
  17. }
  18.  
  19. function insertByQuery ($pdo) {
  20.     $pdo->query("INSERT INTO users SET name = '" . hash('md5', microtime()) . "'");
  21. }
  22.  
  23. function insertByPrepareLoop ($pdo) {
  24.     $sth = $pdo->prepare("INSERT INTO users SET name = ?");
  25.     foreach ( range(1, 100000) as $i) {
  26.         $sth->execute( array(hash('md5', microtime())) );
  27.     }
  28. }
  29. ?>

ベンチマークには疎いので、適当で手動なんですが、
このスクリプトのコメントアウトのあたりを外したりつけたりして、
コマンドラインから以下を繰り返しです。

CODE:
  1. # mysql <init.sql
  2. # 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:
  1. $sth = $pdo->prepare('SELECT * FROM users LIMIT ?');

とやるとステートメントハンドラ自体が帰ってきません。
5.2.xでは解決されているバグのようです。

  • No Related Posts

track feed

Leave a Reply

add to hatena hatena.comment (1) add to del.icio.us (1) add to livedoor.clip (0) add to Yahoo!Bookmark (0) Total: 2