モダンPerlの世界へようこそ

第35回 DBI:生のSQLが散らばると言う前に

この記事を読むのに必要な時間:およそ 5 分

DBIの便利機能

DBIにはほかにも高速化のためにコネクションなどをキャッシュする機構や,プロファイリングやトレーシングといった開発を支援する機構が標準で用意されています。

たとえば,大規模サービス向けではありませんが,永続的な環境でデータベースの接続を張りっぱなしにしておきたいときは,connectのかわりにconnect_cachedを使うと,可能な限り同じ接続を使い回せるようになります(データベースの接続が切れても必要に応じて接続を張り直してくれます⁠⁠。

my $dbh = DBI->connect_cached(...);

また,DBIに同梱されているDBD::Goferを使うと,いまのところトランザクションなどに制約があるものの,リモートのデータベースにアクセスする際にロードバランサやキャッシュなどの仕掛けを加えられるようになります(CPANにはあがっていませんが,Gearmanを使って転送するためのモジュールもあるようです⁠⁠。

特定のSQLの実行にかかった時間を知りたい場合は,同じくDBIに同梱されているDBI::Profileが使えます。単純なスクリプトで,実行するSQL文もそれほど多くないなら,DBI_PROFILE環境変数に適切な値を指定しておけば実行後にプロファイリングの結果が表示されます。

> DBI_PROFILE=2 perl foo.pl

特定のSQLだけプロファイリングしたい場合は,そのSQLを実行するハンドルのProfileアトリビュートに値を指定するか,

my $dbh = DBI->connect(...);
$dbh->{Profile} = 6;
my $rows = $dbh->selectall_arrayref(...)
$dbh->{Profile} = 0;

山口徹氏作のDBIx::ProfileManagerを利用するとよいでしょう。

my $dbh = DBI->connect(...);
my $pm = DBIx::ProfileManager->new;
$pm->profile_start;
my $rows = $dbh->selectall_arrayref(...)
$pm->profile_stop;
local $, = "\n";
print $pm->data_formatted;

また,デバッグの際に便利なオプションとして,最近のDBIでは接続時にShowErrorStatementというアトリビュートを渡しておくと,エラーが発生したときに問題を起こしたSQLを表示できます。

my $dbh = DBI->connect('dbi:SQLite::memory:', '', '', {
  RaiseError => 1,
  ShowErrorStatement => 1, 
});

ほかにも,エラー処理まわりに手を入れたり,各種のメソッドにコールバックをかませたりと,詳しく解説していくとそれだけで本が一冊書けてしまうほどDBIには多くの機能が用意されています。DBI::FAQにも書いてあるように,DBIは,ODBCなどに比べるとはるかに高レベルなところまで面倒を見てくれるインタフェースであるといってよいでしょう。

DBIの課題

もっとも,リリース当時に比べればDBIもずいぶんと拡張されてきたとはいえ,データベースまわりの仕様や標準もこの15年ほどの間にずいぶん変化しました。SQLひとつとってみても,DBIの検討が始まった当時のSQL92から,1995年のSQL/CLI,1996年のSQL/PSM,そしてSQL1999,SQL2003,SQL2008と拡張が続けられてきましたし,MicrosoftのODBCにしても,RDO(Remote Data Objects)やODBCDirect,あるいはADO(ActiveX Data Objects)のように,その時々の問題意識に沿った形でさまざまな拡張や後継となるAPIが登場しています。当然DBIにも未解決の課題が見られるようになっていきます。

そのため,DBIが10歳を迎えた2004年には新たにDBI::Roadmapというファイルが用意され,今後の拡張予定が共有されました。そのなかにはスレッド対応や非同期インタフェースの定義といったパフォーマンスを改善するためのものから,各種ドライバの品質を高めるための共通テスト,既存のDBD::MultiplexDBD::Proxyといった可用性を高めるためのツールの改善,統一的なUnicode対応,バッチ処理対応,拡張性の向上,デバッグ支援機構の改善,移植性の改善,知財問題の対処といったものまで,今後も安心してDBIを使っていくうえでの大きなポイントとなりそうな項目が列挙されています。

また,このドキュメントにはParrot,Perl 6の時代を見据えて,ほかの軽量言語でも共通して使えるDBDI,DataBase Driver Interfaceの制定を検討していることも記されています。当時はPerl 5のDBIをベースにすることになっていましたが,これはその後実績もありユーザ数も多いJavaのJDBCを参考にする方向で話が進んでいます。ティム・バンス氏のブログに最近java2perl6apiプロジェクトの概要について説明された記事が掲載されているので,未見の方はあわせてご覧ください。昨今のPerl界が新しいユーザ層の獲得先をどこに求めようとしているかという点でも興味深い記事になっていると思います。

素のDBIを使うことが悪いのではありません

DBIは,ベンダごとに異なっていたデータベースまわりのインタフェースを共通化することで移行コストや学習コストを軽減できることがメリットだったのですから,本来はDBIのインタフェースを使ってアプリケーションを書くことは,推奨されるべきことではあっても,けなされるべきことではありません。

とはいえ,DBIはかなり低レベルなところまで手を入れられるようになっているため,インタフェースとしては冗長な部分もありますし,SQLも,かなりのところまで標準化されているとはいえ,やはりベンダごとに違う部分は残っています。DBIにも最近はDBI::SQL::Nanoという最低限のSQLを解釈するためのモジュールが同梱されていますが,DBI::Roadmapにも書いてあるように,SQLのすべての方言をDBIのレイヤーで吸収することはできません。本当に移植性を考慮する必要があるのなら,もう少し上のレイヤーでSQLの差違を吸収してやる必要はありますし,一般的に言って,データベースを扱う部分は何らかのモデルクラス(群)にまとめてやるのがベストプラクティスです。

ただし,素のHTML片が散らばっているウェブアプリケーションはよろしくない,というのと同じ理屈で,素のSQL片が散らばっているアプリケーションはよろしくない,ということはできますが,素のHTML片をCGI.pmが提供するヘルパー関数群に置き換えたからといって本質的な解決にはならないのと同じ理由で,単にSQLをPerlコードで隠蔽したからといってふつうは本質的な解決にはなりません。

CPANにはDBIをベースにしたモデルクラスをつくるときに便利に使えるツールがいくつも公開されていますし,そのうちのいくつかについては次回以降の記事で取り上げていきますが,このようなツールにも,インタフェースが統一されていないため移行コストが高くなるとか,オブジェクトの生成などに時間がかかるため,ものによっては体感できるほど処理が重くなる,実際に発行されるSQLが隠蔽されてしまうため,効率の悪いSQLを発行していても気づかない,あるいは,Perlにはあまり詳しくないデータベースの専門家の支援を受けづらくなる,といったトレードオフは存在します。小さなスクリプトくらいであればDBIを使って生のSQLを書くほうがメンテナンス性が高くなる,ということも十分にありえますので,適材適所を心がけていただければと思います。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/