Perl における Prepared Statement のベンチマーク

MySQLSQLite について測定してみた。「np」がプリペアードステートメントを使わない場合、「p」が使った場合。「;srv」というのはサーバサイドプリペアードステートメントを有効にした場合。

$ ./dbi-prepared-bench.pl 
               Rate   sqlite:np    mysql:np mysql:p;srv    sqlite:p     mysql:p
sqlite:np    7519/s          --        -28%        -50%        -51%        -57%
mysql:np    10417/s         39%          --        -31%        -32%        -41%
mysql:p;srv 15152/s        102%         45%          --         -2%        -14%
sqlite:p    15385/s        105%         48%          2%          --        -12%
mysql:p     17544/s        133%         68%         16%         14%          --

全般的にプリペアードステートメントを使って DBI::st オブジェクトをキャッシュした方が速い。DBD::mysql で (SQL構文解析結果全体をキャッシュしうる) サーバサイドプリペアードステートメントを有効にした場合よりも、クライアントサイド版の方が速いというのはおもしろい。サーバと交換するパケットの量が増えるのかな。

以下、ソースコード全掲。

#! /usr/bin/perl

use strict;
use warnings;

use Benchmark qw/:all/;
use DBI;

my $cl_dbh = DBI->connect('dbi:mysql:test')
    or die DBI->errstr;
my $srv_dbh = DBI->connect('dbi:mysql:test;mysql_server_prepare=1')
    or die DBI->errstr;
my $sqlite_dbh = DBI->connect('dbi:SQLite:dbname=/tmp/foo.db')
    or die DBI->errstr;

$cl_dbh->do('drop table if exists t')
    or die $cl_dbh->errstr;
$cl_dbh->do('create table t (v int not null)')
    or die $cl_dbh->errstr;
$cl_dbh->do('insert into t values (1)')
    or die $cl_dbh->errstr;
{ local $sqlite_dbh->{PrintError}; $sqlite_dbh->do('drop table t'); }
$sqlite_dbh->do('create table t (v int not null)')
    or die $sqlite_dbh->errstr;
$sqlite_dbh->do('insert into t (v) values (1)')
    or die $sqlite_dbh->errstr;

sub non_prepared {
    my ($dbh) = @_;
    $dbh->selectall_arrayref('select * from t where v=1')
}

sub prepared {
    my ($dbh, $sthref) = @_;
    unless ($$sthref) {
        $$sthref = $dbh->prepare('select * from t where v=?')
            or die $dbh->errstr;
    }
    $$sthref->execute(1)
        or die $dbh->errstr;
    $$sthref->fetchall_arrayref;
}

my ($cl_sth, $srv_sth, $sqlite_sth);

cmpthese(10000, {
    'mysql:np'    => sub { non_prepared($cl_dbh) },
    'mysql:p'     => sub { prepared($cl_dbh, \$cl_sth) },
    'mysql:p;srv' => sub { prepared($srv_dbh, \$srv_sth) },
    'sqlite:np'   => sub { non_prepared($sqlite_dbh) },
    'sqlite:p'    => sub { prepared($sqlite_dbh, \$sqlite_sth) },
});