2014年9月3日水曜日

rails4のActiveRecordでselect for updateを実行

mysqlでselect for updateを使ったトランザクション処理をしたかったので調べました。

まずはrailsのバージョン
$ bundle exec rails -v
Rails 4.1.4

mysqlのテーブル
mysql> desc seq_sometest3s;
+--------+------------+------+-----+---------+-------+
| Field  | Type       | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| id     | int(10)    | NO   | PRI | 0       |       |
| seq_id | bigint(10) | YES  |     | NULL    |       |
+--------+------------+------+-----+---------+-------+
2 rows in set (0.01 sec)
 
mysql> select * from seq_sometest3s;
+----+--------+
| id | seq_id |
+----+--------+
|  1 | 100049 |
+----+--------+
1 row in set (0.00 sec)

まずネットで調べた以下のやり方だとエラーになりました。
tbl = base.find(1, lock: true)
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.2ms)  BEGIN
  SeqSometest3 Load (0.7ms)  SELECT `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` IN (1, '---\n:lock: true\n')
   (0.2ms)  ROLLBACK

次に出てきた以下のやり方だとエラーは出ませんでした。
ただ、プライマリーキーで指定しているのでorder byが不要です、
tbl = base.where("id =1").lock(true).first
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.1ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE (id =1)  ORDER BY `seq_sometest3s`.`id` ASC LIMIT 1 FOR UPDATE
  SQL (0.2ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100046 WHERE `seq_sometest3s`.`id` = 1
   (0.4ms)  COMMIT

次のやり方でもエラーは出ませんでしたが、
order byはつかなくなったけど2回selectが実行されてしまいます。
後半のSQLだけでよいのに。
tbl = base.find(1).lock!
tbl.increment!(:seq_id)
tbl.seq_id

   (0.1ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1 FOR UPDATE
  SQL (0.2ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100047 WHERE `seq_sometest3s`.`id` = 1
   (0.6ms)  COMMIT

このやり方だとselectも1回しか発行されず、order byもつかないSQLが発行できました。
tbl = base.lock.find(1)
tbl.increment!(:seq_id)
tbl.seq_id
 
   (0.1ms)  BEGIN
  SeqSometest3 Load (0.4ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 1 LIMIT 1 FOR UPDATE
  SQL (0.3ms)  UPDATE `seq_sometest3s` SET `seq_id` = 100049 WHERE `seq_sometest3s`.`id` = 1
   (0.3ms)  COMMIT

ちなみに存在しないレコードにロックをかけようとすると、
RecordNotFoundのエラーが出ました。
tbl = base.lock.find(2)
tbl.increment!(:seq_id)
tbl.seq_id

   (0.2ms)  BEGIN
  SeqSometest3 Load (0.2ms)  SELECT  `seq_sometest3s`.* FROM `seq_sometest3s`  WHERE `seq_sometest3s`.`id` = 2 LIMIT 1 FOR UPDATE
   (0.1ms)  ROLLBACK
ActiveRecord::RecordNotFound (Couldn't find SeqSometest3 with 'id'=2):


参考URL
http://apidock.com/rails/ActiveRecord/Locking/Pessimistic
http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

2014年9月1日月曜日

rails4のActiveRecordで特定のカラムだけ取得する

ActiveRecordで普通にfindしたりすると、すべてのカラムが取得できますが、
DBの負荷を考えると必要なカラムだけ取得したいので、そのやり方を調べました。

まずはrailsのバージョンを確認
$ bundle exec rails -v
Rails 4.1.4

DBの中身はこんな感じです。
mysql> select * from sometest3s where id=2;
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
| id | name | content                                                                                    | view_flg | ins_date            |
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
|  2 | john | {"shop_id"=>[100, 200, 300], "item_id"=>[500, 600, 700, 800], "name"=>"jon", "user"=>2000} | 0        | 2014-08-20 02:17:17 |
+----+------+--------------------------------------------------------------------------------------------+----------+---------------------+
1 row in set (0.00 sec)

取得の仕方はこうで、pluckを使うと結果は配列に入ります。
mapss = Sometest3.select(:id,:name).where(:id => 2).pluck(:id,:name)
puts mapss # => 2 john
puts mapss[0] # => 2 john
puts mapss[0][0] # => 2
puts mapss[0][1] # => john 

その際に発行されるSQLがこんな感じです。
   (0.3ms)  SELECT `sometest3s`.`id`, `sometest3s`.`name` FROM `sometest3s`  WHERE `sometest3s`.`id` = 2

ちなみにwhereとselectを入れ替えてみても発行されるSQLは同じでした。
mapss = Sometest3.where(:id => 2).select(:id,:name).pluck(:id,:name)

   (0.3ms)  SELECT `sometest3s`.`id`, `sometest3s`.`name` FROM `sometest3s`  WHERE `sometest3s`.`id` = 2


参考URL
http://6rats.blog62.fc2.com/blog-entry-75.html
http://d.hatena.ne.jp/suginoy/20120605/p3