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

0 件のコメント:

コメントを投稿