たごもりすメモ

コードとかその他の話とか。

RubyでMySQLに繋ぐためのruby-mysqlとmysql2

このエントリは MySQL Casual Advent Calendar 2011 - MySQL Casual の10日目の記事です。

こんばんは。tagomorisです。さとしです。タゴモリスの s はさとしの s です(実話)。Twitterで #さとし というハッシュタグが流れるたび、ひそかにびくっとしてます。

RubyからMySQLに繋ぐときにどうするの、ととりあえず gem search -r mysql とかやると思います。そして大量にあれこれ出てきてどうすればいいんだ! という気分になると思います。そういう気分になったことがあるので、現状を簡単にまとめてみました。

ruby-mysql

昔からの定番ですね。作者は id:tmtms のとみたまさひろさん。rubygemsとか使われる前から Ruby/MySQL というライブラリ名で知られていました。Googleで検索するとトップに出てきたのがこちらですが、これはだいぶ古い内容のドキュメントですね。現行の2.9系(3.0系アルファ版?)のリポジトリgithubにあるようです。

tmtm/ruby-mysql · GitHub

このバージョンについての説明などはRuby/MySQL 2.9 - @tmtms のメモを読むのがいいと思います。約2年前のエントリですが、今読んでも特に違和感はない感じ。Ruby 1.9対応のPure Rubyライブラリです。
ただし上記のエントリにも書かれている通り MySQL 5.1 との組合せがサポート対象ということで、MySQL 5.5と組合せてみると自分の環境では動きませんでした。エラー内容は……ちょっと探すのがめんどくさい本筋から外れるので省略しますが。かじゅある!!

コード例 with ruby-mysql

gem install ruby-mysql とやるとインストールされます。githubのreadmeにあるコード例をほぼそのまま引用しますとこんな感じ。

require 'mysql'
client= Mysql.connect('hostname', 'username', 'password', 'dbname')
client.query("SELECT col1, col2 FROM tblname").each do |col1, col2|
  p col1, col2
end
stmt = client.prepare('INSERT INTO tblname (col1,col2) VALUES (?,?)')
stmt.execute 123, 'abc'

非常にわかりやすい、いい例ですね!

mysql

これもちょっとだけ紹介。これは従来から MySQL/Ruby として知られていたもので、バージョンは 2.8.12.8.2 が最新のようです。先程紹介した ruby-mysql が2.9系として後継のナンバリングになっていることから、このライブラリはもうあまり変更されなさそうな空気を感じます。(※追記、コメント欄参照)

現状、メジャーなものの中ではこれが一番高速に動作するようです。RubyのC拡張ライブラリで、libmysqlclientを使っています。ただし ruby 1.9 のM17Nに対応していないというのが最大の問題で、自分にはもう選択肢としては存在していない感じですね。Ruby 1.9イイヨ!

mysql2

最近はこのライブラリに人気があるようです。Railsで普通に使えるとか、C拡張ライブラリでlibmysqlclient を使っているのでPure Rubyruby-mysql より高速に動作するとか、非同期呼び出しのAPIをもっていてそっち系のWebアプリケーションフレームワークと相性がよいとか、そのあたりが人気の理由なんですかね。

brianmario/mysql2 · GitHub

MySQL 5.5と組合せても普通に使えました。libmysqlclientを使っている関係上 gem install mysql2 したときに mysql.h が見付からなくてインストールに失敗する例があるようです。ぐぐると解決方法が出てくるので、うまくいかない人はぐぐると良いでしょう。でもこのエントリ読む人はみんな MySQL は手元のマシンにインストール済みだから大丈夫だよね!
あと速度の面で mysql(2.8系) に及ばない旨がreadmeに書いてありますが、正直そこが問題になるケースがどれだけあるかなーと思うと、だいたいの人には大丈夫なんじゃないでしょうか。

コード例 with mysql2

さっきの ruby-mysql のときのコード例とほぼ同じコードを mysql2 を使って書いてみましょう。

require 'mysql2'
client = Mysql2::Client.new(:host => "hostname", :username => "root", :password => "password", :database => "dbname")
client.query("SELECT col1, col2 FROM tblname").each do |col1, col2|
  p col1, col2
end
val1 = 123
val2 = client.escape('abc')
client.query("INSERT INTO tblname (col1,col2) VALUES (#{val1},'#{val2}')")

非常にわかりやすい、いい例ですね!
……と言いたいところでしたが、なんていうか、ひっかかります。ひっかかりますよね。なんでprepared statement使わないのかと。

使えないんです。prepareするAPIが無いんです。マジで無い。本当にびびるけど無い。ライブラリのrubyコードもCコードも読んでみたうえでirbで client.methods の結果を注視するところまでやってみたけど無い。ありません。できません。ユーザ入力はクライアントオブジェクトのエスケープメソッドを通した上でエスケープ済み文字列としてSQLに展開しましょう。

マジで?*1

(2012/04/20追記: mysql2-cs-bind released! - tagomorisのメモ置き場)

まとめ

MySQL 5.1しか相手にしなくて速度もそんなにいらないなら信頼と実績の ruby-mysql が使えます。Ruby 1.9でも大丈夫。
Ruby 1.8系で書いててがっつり速度が欲しいなら mysql を使うのがよいでしょう。
MySQL 5.5使ってたり非同期処理したかったりする場合は mysql2 が選択肢になります。Ruby 1.9でも大丈夫。ただし prepared statement は使えません。

ここでさくっとprepareできるようにパッチ書いたぜ! とか言えると超カッコいいと思って実は昨日やり始めてみたんですが、libmysqlclientのAPIとして、prepared statement の場合は通常の一発クエリ実行とは結果の取得とかも違う方法になるんですな。超絶めんどくさい、というか実装規模がだいぶでかい。1時間くらいで諦めました。かじゅある!!!

明日は @oinume さんです。それでは自分はこれで。かじゅある!(挨拶)

*1:「いや実はこれこれこうするべきで、お前が知らないだけ」というツッコミを心よりお待ち申し上げております。