yumimueの日記

2007-11-02

[][]トランザクションと更新処理にかかる時間 09:04 はてなブックマーク - トランザクションと更新処理にかかる時間 - yumimueの日記

SQLiteでは明示的にトランザクションを開始しない場合、INSERTUPDATEDELETEの前後に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

予想以上の差。更新処理を繰り返す場合は、明示的にトランザクションを開始した方がよさそう。

k3ck3c2007/11/02 12:10タイトルの括弧が一つ足りないようです。
SQLiteの話題、参考になります。

2007-11-01

[][]トランザクション 21:23 はてなブックマーク - トランザクション - yumimueの日記

トランザクションとは、関連のあるデータベースの更新処理を一つにまとめたもの。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

[][]SQL変数を直接埋め込まない 16:36 はてなブックマーク - SQLに変数を直接埋め込まない - yumimueの日記

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

[][]sqlite3-rubySQLiteをいじる 14:32 はてなブックマーク - sqlite3-rubyでSQLiteをいじる - yumimueの日記

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