2007-11-02
■ [ruby][sqlite]トランザクションと更新処理にかかる時間 
SQLiteでは明示的にトランザクションを開始しない場合、INSERT、UPDATE、DELETEの前後にBEGIN、COMMITが実行される。ということは
data.each {|d| db.execute('insert into test values(?, ?)', *d) }
こう書くよりも
db.transaction do data.each {|d| db.execute('insert into test values(?, ?)', *d) } end
こう書いた方が処理時間が短くなるはず。
実際にどれぐらいの差があるかを調べてみる。
#!/usr/bin/ruby require 'sqlite3' require 'benchmark' db1 = SQLite3::Database.new('test1.db') db2 = SQLite3::Database.new('test2.db') sql = <<SQL create table test( name varchar(255), mail varchar(255) ); SQL db1.execute(sql) db2.execute(sql) sql = "insert into test values('hoge', 'hoge@hogehoge.com')" Benchmark.bm do |x| x.report('no transaction') { 100.times {db2.execute(sql) } } x.report('transaction ') { db1.transaction do 100.times {db1.execute(sql) } end } end puts db1.get_first_value('select count(*) from test') puts db2.get_first_value('select count(*) from test')
user system total real
transaction 0.431000 0.000000 0.431000 ( 0.431000)
no transaction 17.895000 0.000000 17.895000 ( 17.894000)
100
100
予想以上の差。更新処理を繰り返す場合は、明示的にトランザクションを開始した方がよさそう。
2007-11-01
■ [ruby][sqlite]トランザクション 
トランザクションとは、関連のあるデータベースの更新処理を一つにまとめたもの。BEGINでトランザクション処理を開始し、COMMITで一連の更新処理の結果をデータベースに反映する。また途中で問題が生じた場合はROLLBACKでデータベースをトランザクション処理を行う前の状態に戻すことができる。これらのことにより、全て成功するか、全て失敗するかの二択であることが保証され、データの整合性を保つことができる。
sqlite3-rubyでトランザクション処理を行うには以下のようにする。
db.transaction # トランザクションを開始する begin db.execute('insert into test_a values(?, ?)', *data_a) db.execute('insert into test_b values(?, ?)', *data_b) db.execute('insert into test_c values(?, ?)', *data_c) db.commit # 処理を確定する rescue db.rollback # 例外が発生したら処理を取り消す end
ブロックを使って書くこともできる。
db.transaction do db.execute('insert into test_a values(?, ?)', *data_a) db.execute('insert into test_b values(?, ?)', *data_b) db.execute('insert into test_c values(?, ?)', *data_c) end
本当に例外が発生したら処理が取り消されるか試してみる。
#!/usr/bin/ruby require 'sqlite3' def transaction_test(data) db = SQLite3::Database.new('test.db') db.execute('delete from test') begin db.transaction do data.each do |d| raise if d.nil? db.execute('insert into test values(?, ?)', *d) end puts 'insert ok' end rescue => e puts 'insert error' end db.close db = SQLite3::Database.new('test.db') p db.execute('select * from test') end data = [ ['b', 'b@hoge.com'], ['c', 'c@hoge.com'], ['d', 'd@hoge.com'], ] transaction_test(data) puts transaction_test(data << nil)
insert ok [["b", "b@hoge.com"], ["c", "c@hoge.com"], ["d", "d@hoge.com"]] insert error []
2007-10-31
■ [ruby][sqlite]SQLに変数を直接埋め込まない 
SQLに変数を埋め込むと、エラーになる可能性があるし、セキュリティの点からもよろしくない。
db.execute("insert into test values ('#{name}, '#{name}')") # × db.execute('insert into test values(?, ?)', name, mail) # 〇
?の部分(プレースホルダ)が第二引数以降(バインド変数)で置き換えられる。その際、変数がSQLの特殊文字を含んでいても普通の文字として扱われる。
#!/usr/bin/ruby require 'sqlite3' db = SQLite3::Database.new('test.db') print 'name:'; name = gets.chomp print 'mail:'; mail = gets.chomp begin db.execute("insert into test values ('#{name}, '#{name}')") puts 'insert ok' rescue SQLite3::SQLException => e puts e end begin db.execute("insert into test values (?, ?)", name, mail) puts 'insert ok' rescue SQLite3::SQLException => e puts e end
$ ruby sqlite_test.rb name:hoge's mail:hoge@hogehoge.com near "s": syntax error insert ok
■ [ruby][sqlite]sqlite3-rubyでSQLiteをいじる 
sqlite-3-rubyのインストール
$ wget http://rubyforge.org/frs/download.php/17097/sqlite3-ruby-1.2.1.tar.gz $ tar xzf sqlite3-ruby-1.2.1.tar.gz $ cd sqlite3-ruby-1.2.1 $ ruby setup.rb config $ ruby setup.rb setup $ ruby setup.rb install
データベースへの接続
require 'sqlite3' db = SQLite3::Database.new('test.db')
SQLiteではデータベース毎にファイルを一つ作成する。この場合、カレントディレクトリのtest.dbというファイルをデータベースとしてオープンする。またファイルがない場合は新しく作成される。
SQL文の実行
テーブルの作成
sql = <<SQL create table test ( title varchar(255), url varchar(255) ); SQL db.execute(sql)
レコードの追加、変更、削除
db.execute("insert into test values ('yahoo', 'http://www.yahoo.co.jp')") db.execute("update test set title='Yahoo' where title='yahoo'") db.execute("delete from test where title='Yahoo'")
レコードとは関連するデータの集まり。上の例だとtitleとurlという2つのフィールドが集まって1件のレコードになる。
複数のSQLの実行
sql = <<SQL insert into test values ('Yahoo', 'http://www.yahoo.co.jp'); insert into test values ('Google', 'http://www.google.co.jp'); SQL db.execute_batch(sql)
SELECT文の実行結果を取得
SELECT文の実行結果はブロックを使って受け取ることができる。
db.execute('select * from test') do |r| p r end
["Yahoo", "http://www.yahoo.co.jp"] ["Google", "http://www.google.co.jp"]
最初の列だけを取得
p db.get_first_row('select * from test') #=> ["Yahoo", "http://www.yahoo.co.jp"]
最初の値だけを取得
p db.get_first_value('select * from test') #=> "Yahoo"
例外処理
SQLのエラーは例外として補足できる。
db.execute('create table hoge(id)') begin db.execute('create table hoge(id)') rescue SQLite3::SQLException => e puts e #=> table hoge already exists end
require 'rubygems'
require 'httpclient'
c = HTTPClient.new
puts c.get_content("http://d.hatena.ne.jp/rubikitch/searchdiary", "word"=>"ruby")
puts c.post_content("http://www.rubyist.net/~rubikitch/rbm/rbm.cgi", "name"=>"self", "from"=>"rubikitch@ruby-lang.org", "url"=>"http://www.rubyist.net/~rubikitch/", "body"=>"httpclient test")