perlとsqlのいろいろ
TRANSCRIPT
Perl と SQL のいろいろTakuya Tsuchida(tsucchi)
概要
Perl でアプリケーションを開発している人は、SQL も書いている人が多いのではないか、と思います。本トークでは、初心者から中級者を対象に、Perl と SQL にまつわる tips を紹介します。
初心者が陥りがちな良くない書き方をどのように改善していくか、SQL にまつわる困ったことが起きたときにどのように対応するか、簡単な SQL を簡単に済ませるにはどうしたらよいか、といったことをお話したいと思います。
ちなみに...
超豪華裏番組開催中です...
体が3つくらい欲しいですねw
自己紹介
● 名前: 土田 拓也● tsucchi(twitter, github, CPAN)とか● tsucchi1022(はてなとか)
● 某印刷会社で Perl とか SQL とか書いてます● 去年も発表させていただきました
● あんなテスト、こんなテスト● http://yapcasia.org/2011/talk/47
目次
●DBI/DBD のインストール●プレースホルダ●トランザクション●SQLを見たい!●SQLの置き場所●DBI の意外と便利な機能など●テストとチューニング
DBI/DBD のインストール
DBI/DBD のインストール
● 慣れれば簡単だが、ハマるポイントが結構多い
1.クライアントライブラリを入れる2.DBI を入れる3.DBD ::* を入れる
※細かい手順はググればきっと出てくるので調べてね
libmysqlclient とか Oracle (Instant) Client とか
• MySQL なら DBD::mysql• PostgreSQL なら DBD::pg• Oracle なら DBD::Oracle
DBI/DBD のインストール
●ありがちなハマりどころ
1.クライアントライブラリを入れる→ クライアントライブラリが入っていない or 入れようとした際にうまくいかない
2.DBI を入れる3.DBD ::* を入れる
→インストール時に流れるテストコードが失敗する
環境によって対応方法は異なるので、ググったりして頑張って解決してください
• DB に実際に接続して行うテストが入っている事がある• ログやメッセージを見て、それが確認できた場合は強制インストールでよい(と思う)• cpan なら force install, cpanm なら -f オプションを使う
DBI/DBD のインストール
●ありがちなハマりどころ(2)
ありがちではないかも、ですが。。。
クライアントライブラリを入れるところでハマると、Pure Perl のDBD が使いたくなるかもしれないが・・・
●必ず XS のもの(先ほど挙げたもの)を使うこと!●逆に言うと、Pure Perl のドライバは絶対に使わないこと!
● DBD::mysqlPP(使っちゃダメ!)● DBD::pgPP(使っちゃダメ!)少なくとも、MySQL のドライバに関しては、過去セキュリティホールがありました。また、複雑なクエリを投げるとハングすることがあることが確認されています。
ま、メンテナは僕なのですがね!(一緒にメンテしてくれる方絶賛募集中!)
プレースホルダ
プレースホルダ
●よくない例 my $dbh = DBI->connect(...); my $sql = "SELECT id, name FROM employee WHERE id = $id"; my $sth = $dbh->prepare($sql); $sth->execute();
変数をそのまま SQL 中に埋め込むのはダメ!
プレースホルダ
●なぜ良くないか● セキュリティ● $id が外部からくる値(たとえばフォームの入力)の場合、
どんな値が渡されるか分からない● たとえば「0 OR 1=1」のような値が渡されると、テーブ
ルの情報をすべて抜かれてしまう● SQLインジェクション
● 他のテーブルに何かされたり、データを盗まれたり、破壊される場合も
my $dbh = DBI->connect(...); my $sql = "SELECT id, name FROM employee WHERE id = $id"; my $sth = $dbh->prepare($sql); $sth->execute();
プレースホルダ
●文字列化?(サニタイズ?)
● SQL の変数部分を文字列化して、この問題を防ごうとするのは止めましょう
● 数値型に文字列を渡すと、インデックスが効かなくなることがある(遅くなる。逆の場合も同じ)
● たとえば「\' OR 1=1 --」という入力を渡すとこの場合も全件出力されてしまう(無意味)
my $sql = "SELECT id, name FROM employee WHERE id = '$id'";
プレースホルダ
●解決策:プレースホルダ
●先ほどの怪しい入力を渡すとエラーになる●型のミスマッチで遅くなることが無い●使い方もそんなに難しくない
my $dbh = DBI->connect(...);my $sql = "SELECT id, name FROM employee WHERE id = ?";my $sth = $dbh->prepare($sql);$sth->execute($id);
? がプレースホルダです
SQL で「?」で指定した値をここで渡す
トランザクション
トランザクション
●トランザクションって?
●例えば、3番目の INSERT の途中でプロセスがクラッシュすると、中途半端なデータになってしまう
● 4件入るはずが2件しか入らない● 成功時はすべてのデータが入ってほしい● 失敗時は(中途半端なデータができるくらいなら)データが
無いほうがマシ● これを実現するのがトランザクション
my $dbh = DBI->connect($dsn, $user, $pass, { AutoCommit => 1, ... } );my $sth = $dbh->prepare("INSERT INTO detective (id, name) VALUES (?, ?)");$sth->execute(1, 'シャーロック');$sth->execute(2, 'ネロ');$sth->execute(3, 'エリー');$sth->execute(4, 'コーデリア');
トランザクション
●begin_work/commit
●begin_work/commit で囲んだ区間がトランザクションになる● 何らかの原因で処理が失敗すると、処理が無かったことに
される(ロールバック)● ただ、この書き方だとちょっと怪しいかな。
my $dbh = DBI->connect($dsn, $user, $pass, { AutoCommit => 1, ... } );$dbh->begin_work();my $sth = $dbh->prepare("INSERT INTO detective (id, name) VALUES (?, ?)");$sth->execute(1, 'シャーロック');$sth->execute(2, 'ネロ');$sth->execute(3, 'エリー');$sth->execute(4, 'コーデリア');$dbh->commit();
begin_work()で開始
commit で確定
トランザクション
●処理内容に応じて、明示的にロールバックさせる方が良いmy $dbh = DBI->connect($dsn, $user, $pass, { AutoCommit => 1, ... } );my $sth = $dbh->prepare("INSERT INTO detective (id, name) VALUES (?, ?)");$dbh->begin_work(); # トランザクション開始$sth->execute(1, 'シャーロック');$sth->execute(2, 'ネロ');$sth->execute(3, 'エリー');$sth->execute(4, 'コーデリア');if( has_toys() ) { #探偵さんがトイズを持って入れば OK $dbh->commit(); # トランザクションOK}else { #トイズが無いので探偵失格 $dbh->rollback(); # ロールバック}
何らかの条件に応じて..,
commit したり...
rollback したり
トランザクション
●ロールバックすると、begin_work() から rollback() までの処理が「なかったこと」になる●ただ、この書き方もちょっと怪しい●通常は Try::Tiny 等をつかって、エラー処理と組み合わせる
トランザクション
●Try::Tiny との組み合わせuse Try::Tiny;my $dbh = DBI->connect($dsn, $user, $pass, { AutoCommit => 1, ... } );my $sth = $dbh->prepare("INSERT INTO detective (id, name) VALUES (?, ?)");try { $dbh->begin_work(); # トランザクション開始 # 何か処理(1) $sth->execute(1, 'シャーロック'); $sth->execute(2, 'ネロ'); $sth->execute(3, 'エリー'); $sth->execute(4, 'コーデリア'); # 何か処理(2) $dbh->commit(); # トランザクションOK} catch { $dbh->rollback; #エラーが起きたので rollback # 必要ならその他エラー処理 die $_; #例外を上位に投げ直す};
● エラー処理対象としたい部分をを try {} で囲む● エラー処理を catch に書く
● こんな感じで、エラー時にロールバックする● この場合、処理(1)や(2)でエラーがあった場合もロールバックされる
SQLを見たい!
SQLを見たい!
●みんな大好きコンビ print デバッグ & Data::Dumper
●手軽●問題となってる SQL の場所が分かっている場合はある程度有効●bind されている値は見づらい
● bind 変数がずれてるとか、そういった問題は見つけにくい
my $dbh = DBI->connect(...);my $sql = "SELECT id, name FROM detective WHERE id = ?";use Data::Dumper; warn Dumper($sql, \@binds);my $sth = $dbh->prepare($sql);$sth->execute(@binds);
SQLを見たい!
●$DBI::trace 変数$ env DBI_TRACE=1 perl a.plDBI 1.620-nothread default trace level set to 0x0/1 (pid 28328 pi 0) at DBI.pm line 276 via a.pl line 5... <- prepare('SELECT id, name FROM detective WHERE id = ?')= ( DBI::st=HASH(0x15a13d8) ) [1 items] at a.pl line 11 <- execute('1234')= ( '0E0' ) [1 items] at a.pl line 12...
SQLを見たい!
●$DBI::trace 変数●環境変数以外にも、$dbh にセットする方法もある●割と手軽●情報が豊富
● (DBI_TRACE=2 にするとさらに豊富になる)● 豊富過ぎて逆に見づらいことも。。。
● bind されている値は見えるのだが、分かりにくい
SQLを見たい!
●DBIx::QueryLog
●上記の方法以外にも、スクリプトのどこかで use する方法もある●これも手軽●実行時間も分かる●bind されている値はもっとも見やすい●SQL の中身を見る目的ならこれがベスト
$ perl -MDBIx::QueryLog a.pl[2012-08-24T12:27:47] [main] [0.000045] SELECT id, name FROM detective WHERE id = '1234' at a.pl line 12
SQLの置き場所
SQLの置き場所
●SQL が長いと、コードが読みにくくなることがあるmy $sql = "SELECT detective.first_name , detective.family_name , toys.name , ...(カラムの情報いっぱい) FROM detective LEFT JOIN toys ON toys.id = detective.toys_id ...(JOIN がいっぱい) WHERE ...(検索条件いっぱい)";
あれ?この関数なにするんだっけ??
こんな感じの長いSQLがあると...
SQLの置き場所
●ヒアドキュメント?●関数 or メソッドにする?
● 何らかの事情で、モジュールを入れられない場合は有効かもしれない
● SQL::Library● Data::Section::Simple
SQLの置き場所
●SQL::Library●SQL を別のファイルに置いて、簡単に取り出せる
[detective_detail]/* 先ほどから挙げてる長い SQL。このファイル名を detective.sql とします */SELECT detective.first_name , detective.family_name , toys.name...
この ini ファイルのセクション風のものが SQL名
SQLの置き場所
●SQL::Library
●長いSQL を考えずに、数行のコードで呼び出せるので、本処理が読みやすくなる●SQL の命名に気をつけないと、別ファイルになる分読みづらくなることもあるので注意
use SQL::Library;my $sql_lib = SQL::Library->new({ lib => 'detective.sql' });my $sql = $sql_lib->retr( 'detective_detail' );
さっきの SQL を呼び出すPerl コード
SQL ファイル名
SQL 名
SQLの置き場所
●Data::Section::Simpleuse Data::Section::Simple qw(get_data_section);
my $sql = get_data_section('employee_detail');warn $sql;
__DATA__
@@ employee_detailSELECT employee.first_name , employee.family_name , employee.age , ...(カラムの情報いっぱい) FROM employee LEFT JOIN xx ON ... ...(JOIN がいっぱい) WHERE ...(検索条件いっぱい) SQL を __DATA__ に書いて...
セクションの名前を指定
セクション名を指定して SQL を呼び出す
SQLの置き場所
●Data::Section::Simple● 長いSQL が1行で呼び出せるので、本処理が見やすくなる● 同一ファイルに置くならベストと思われる● mod_perl 環境では、__DATA__ が読めないため使えません
使いてーorz(私は mod_perl 環境を使っています)
DBI の意外と便利な機能など
DBI の意外と便利な機能など
my $row_href = $dbh->selectrow_hashref($sql, undef, @binds); my @rows = @{ $dbh->selectall_arrayref($sql, { Slice => {} }, @binds) }; $dbh->do($sql);
●結構便利●ただちょっと癖がある
● 第二引数の undef とか意味わかんないですよね?
1行だけとってきたり...
全行hashref で取ったり● いい加減に insert 投げたり● DBMS のコマンドを直接なげたり
なんじゃこれ?
DBI の意外と便利な機能など
●SQL よりもビジネスロジックに集中したい● ORM とか使うと良いかも
● Teng● very simple ORMapper Teng(nihen さん)● http://yapcasia.org/2012/talk/show/3570fad2-d484-
11e1-964b-37a36aeab6a4● DBIx::Class● DBIx::Simple● いろいろあるので、調べてみると良いと思います
テストとチューニング
テストとチューニング
●テスト● DB をあまり意識したくない場合は DBD::Mock● DB を意識したテストが必要な場合はデータを入れて頑張
るしかない● チューニング
● まずは NYTProf 使う● 原因は SQL では無いかもしれない
● 原因が SQL の場合● DBMS についてくるツールを使う
● MySQL なら EXPLAIN● 原因が分かったらあとはインデックス張ったりロジック
を見直す
まとめ
●DBI/DBD インストールがんばろう● ちゃんとしたドライバを使おう
● プレースホルダを使おう● 自前エスケープ絶対駄目!
● 上手にトランザクションをかけよう● DBIx::QueryLog 便利だよ!(xaicron++)● SQL が長いときは上手く外に追い出してみよう● ORM とかも検討してみよう● テストは大変です
● でもそれはいつものことです!● チューニング頑張ろう
● 原因が SQL とは限らないから、切り分けからきっちりと
おしまい
ご清聴ありがとうございました!