前回、Railsにて、同じ値でデータ更新した場合、タイムスタンプカラム( updated_at
)が更新されないことを確認しました。
Railsにて、同じ値でデータ更新した場合、タイムスタンプカラム(updated_at)が更新されない - メモ的な思考的な
そんな中、 lock_version
カラムがある場合の挙動に関するコメントをいただきました。
「そういえば、挙動で気になることがある」と感じたため、調べてみたときのメモを残します。
目次
環境
- Rails 7.0.4
なお、今回使うモデルは、以下のコマンドで生成したものとします。
rails g model Apple name:string lock_version:integer
lock_versionカラムとは
最近 lock_version
について同僚から教わったのですが、 lock_version
という名前のカラムがあると、Railsが楽観的ロックを実行してくれます。
ActiveRecord::Locking::Optimistic
Railsガイドによると、 lock_version
カラムがあるときの挙動は以下となるようです。
楽観的ロックを使うには、テーブルにlock_versionという名前のinteger型カラムが必要です。Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やします。更新リクエストが発生したときのlock_versionの値がデータベース上のlock_versionカラムの値よりも小さい場合、更新リクエストは失敗し、以下のようにActiveRecord::StaleObjectErrorエラーが発生します。
12.1 楽観的ロック(optimistic) | Active Record クエリインターフェイス - Railsガイド
Rails console で動作確認
Railsガイドの記載のうち
Active Recordは、レコードが更新されるたびにlock_versionカラムの値を1ずつ増やします
という挙動について、「同じ値で更新した場合も、 lock_version
カラムの値は1増えるのかな?また、もし1増えるなら updated_at
も更新されるのかな?」と気になりました。
そこで、Rails console を使って動作を確認してみます。
データ作成時
lock_version
、 updated_at
とも値が設定されました。
irb(main):001:0> Apple.create(name: 'フジ') TRANSACTION (0.0ms) begin transaction Apple Create (0.2ms) INSERT INTO "apples" ("name", "lock_version", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["name", "フジ"], ["lock_version", 0], ["created_at", "2022-11-22 14:14:08.851151"], ["updated_at", "2022-11-22 14:14:08.851151"]] TRANSACTION (8.4ms) commit transaction => #<Apple:0x00007f7b57cda5a8 id: 1, name: "フジ", lock_version: 0, created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00, updated_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00>
同じ値でデータ更新
続いて、 name
に同じ値を渡して更新してみたところ、SQLのUPDATE文は発行されませんでした。
# 取得 irb(main):002:0> a = Apple.find(1) # 同じ値で更新 irb(main):003:0> a.update(name: 'フジ') => true
テーブルからデータを取得してみても、更新はありません。
# 取得 irb(main):004:0> a2 = Apple.find(1) Apple Load (0.1ms) SELECT "apples".* FROM "apples" WHERE "apples"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => #<Apple:0x00007f7b57b34820 # 確認 irb(main):005:0> a2 => #<Apple:0x00007f7b57b34820 id: 1, name: "フジ", lock_version: 0, created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00, updated_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00>
別の値でデータ更新
続いて、 name
を フジ
から 名月
へと更新したところ、UPDATE文が発行されました。
irb(main):006:0> a2.update(name: '名月') Apple Update (0.2ms) UPDATE "apples" SET "name" = ?, "updated_at" = ?, "lock_version" = ? WHERE "apples"."id" = ? AND "apples"."lock_version" = ? [["name", "名月"], ["updated_at", "2022-11-22 14:16:24.189472"], ["lock_version", 1], ["id", 1], ["lock_version", 0]] TRANSACTION (8.3ms) commit transaction => true
テーブルからデータを取得してみたところ、 lock_version
や updated_at
が更新されています。
# 取得 irb(main):007:0> a3 = Apple.find(1) Apple Load (0.1ms) SELECT "apples".* FROM "apples" WHERE "apples"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] => # 確認 irb(main):008:0> a3 => #<Apple:0x00007f7b57cade90 id: 1, name: "名月", lock_version: 1, created_at: Tue, 22 Nov 2022 14:14:08.851151000 UTC +00:00, updated_at: Tue, 22 Nov 2022 14:16:24.189472000 UTC +00:00>